import type { GitRunStackedActionResult, GitStackedAction, GitStatusResult,} from "@t3tools/contracts"; export type GitActionIconName = "commit" | "push" | "pr"; export type GitDialogAction = "commit" | "push" | "create_pr"; export interface GitActionMenuItem { id: "commit" | "push" | "pr"; label: string; disabled: boolean; icon: GitActionIconName; kind: "open_dialog" | "open_pr"; dialogAction?: GitDialogAction;} export interface GitQuickAction { label: string; disabled: boolean; kind: "run_action" | "run_pull" | "open_pr" | "show_hint"; action?: GitStackedAction; hint?: string;} const SHORT_SHA_LENGTH = 7;const TOAST_DESCRIPTION_MAX = 72; function shortenSha(sha: string | undefined): string | null { if (!sha) return null; return sha.slice(0, SHORT_SHA_LENGTH);} function truncateText( value: string | undefined, maxLength = TOAST_DESCRIPTION_MAX,): string | undefined { if (!value) return undefined; if (value.length <= maxLength) return value; if (maxLength <= 3) return "...".slice(0, maxLength); return `${value.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;} export function buildGitActionProgressStages(input: { action: GitStackedAction; hasCustomCommitMessage: boolean; hasWorkingTreeChanges: boolean; forcePushOnly?: boolean; pushTarget?: string;}): string[] { const shouldIncludeCommitStages = !input.forcePushOnly && (input.action === "commit" || input.hasWorkingTreeChanges); const commitStages = !shouldIncludeCommitStages ? [] : input.hasCustomCommitMessage ? ["Committing..."] : ["Generating commit message...", "Committing..."]; const pushStage = input.pushTarget ? `Pushing to ${input.pushTarget}...` : "Pushing..."; if (input.action === "commit") { return commitStages; } if (input.action === "commit_push") { return [...commitStages, pushStage]; } return [...commitStages, pushStage, "Creating PR..."];} const withDescription = (title: string, description: string | undefined) => description ? { title, description } : { title }; export function summarizeGitResult(result: GitRunStackedActionResult): { title: string; description?: string;} { if (result.pr.status === "created" || result.pr.status === "opened_existing") { const prNumber = result.pr.number ? ` #${result.pr.number}` : ""; const title = `${result.pr.status === "created" ? "Created PR" : "Opened PR"}${prNumber}`; return withDescription(title, truncateText(result.pr.title)); } if (result.push.status === "pushed") { const shortSha = shortenSha(result.commit.commitSha); const branch = result.push.upstreamBranch ?? result.push.branch; const pushedCommitPart = shortSha ? ` ${shortSha}` : ""; const branchPart = branch ? ` to ${branch}` : ""; return withDescription( `Pushed${pushedCommitPart}${branchPart}`, truncateText(result.commit.subject), ); } if (result.commit.status === "created") { const shortSha = shortenSha(result.commit.commitSha); const title = shortSha ? `Committed ${shortSha}` : "Committed changes"; return withDescription(title, truncateText(result.commit.subject)); } return { title: "Done" };} export function buildMenuItems( gitStatus: GitStatusResult | null, isBusy: boolean,): GitActionMenuItem[] { if (!gitStatus) return []; const hasBranch = gitStatus.branch !== null; const hasChanges = gitStatus.hasWorkingTreeChanges; const hasOpenPr = gitStatus.openPr !== null; const isBehind = gitStatus.behindCount > 0; const canCommit = !isBusy && hasChanges; const canPush = !isBusy && hasBranch && !hasChanges && !isBehind && gitStatus.aheadCount > 0; const canCreatePr = !isBusy && hasBranch && !hasChanges && !hasOpenPr && gitStatus.aheadCount > 0 && gitStatus.hasUpstream && !isBehind; const canOpenPr = !isBusy && hasOpenPr; return [ { id: "commit", label: "Commit", disabled: !canCommit, icon: "commit", kind: "open_dialog", dialogAction: "commit", }, { id: "push", label: "Push", disabled: !canPush, icon: "push", kind: "open_dialog", dialogAction: "push", }, hasOpenPr ? { id: "pr", label: "Open PR", disabled: !canOpenPr, icon: "pr", kind: "open_pr", } : { id: "pr", label: "Create PR", disabled: !canCreatePr, icon: "pr", kind: "open_dialog", dialogAction: "create_pr", }, ];} export function resolveQuickAction( gitStatus: GitStatusResult | null, isBusy: boolean, isDefaultBranch = false,): GitQuickAction { if (isBusy) { return { label: "Commit", disabled: true, kind: "show_hint", hint: "Git action in progress." }; } if (!gitStatus) { return { label: "Commit", disabled: true, kind: "show_hint", hint: "Git status is unavailable.", }; } const hasBranch = gitStatus.branch !== null; const hasChanges = gitStatus.hasWorkingTreeChanges; const hasOpenPr = gitStatus.openPr !== null; const isAhead = gitStatus.aheadCount > 0; const isBehind = gitStatus.behindCount > 0; const isDiverged = isAhead && isBehind; if (!hasBranch) { return { label: "Commit", disabled: true, kind: "show_hint", hint: "Create and checkout a branch before pushing or opening a PR.", }; } if (hasChanges) { if (hasOpenPr || isDefaultBranch) { return { label: "Commit & push", disabled: false, kind: "run_action", action: "commit_push" }; } return { label: "Commit, push & create PR", disabled: false, kind: "run_action", action: "commit_push_pr", }; } if (isDiverged) { return { label: "Sync branch", disabled: true, kind: "show_hint", hint: "Branch has diverged from upstream. Rebase/merge first.", }; } if (isBehind) { return { label: "Pull", disabled: false, kind: "run_pull", }; } if (isAhead) { if (hasOpenPr || isDefaultBranch) { return { label: "Push", disabled: false, kind: "run_action", action: "commit_push" }; } return { label: "Push & create PR", disabled: false, kind: "run_action", action: "commit_push_pr", }; } if (hasOpenPr && gitStatus.hasUpstream) { return { label: "Open PR", disabled: false, kind: "open_pr" }; } return { label: "Commit", disabled: true, kind: "show_hint", hint: "Branch is up to date. No action needed.", };} export function requiresDefaultBranchConfirmation( action: GitStackedAction, isDefaultBranch: boolean,): boolean { if (!isDefaultBranch) return false; return action === "commit_push" || action === "commit_push_pr";}
import type {
GitRunStackedActionResult,
GitStackedAction,
GitStatusResult,
} from "@t3tools/contracts";
export type GitActionIconName = "commit" | "push" | "pr";
export type GitDialogAction = "commit" | "push" | "create_pr";
export interface GitActionMenuItem {
id: "commit" | "push" | "pr";
label: string;
disabled: boolean;
icon: GitActionIconName;
kind: "open_dialog" | "open_pr";
dialogAction?: GitDialogAction;
}
export interface GitQuickAction {
label: string;
disabled: boolean;
kind: "run_action" | "run_pull" | "open_pr" | "show_hint";
action?: GitStackedAction;
hint?: string;
}
const SHORT_SHA_LENGTH = 7;
const TOAST_DESCRIPTION_MAX = 72;
function shortenSha(sha: string | undefined): string | null {
if (!sha) return null;
return sha.slice(0, SHORT_SHA_LENGTH);
}
function truncateText(
value: string | undefined,
maxLength = TOAST_DESCRIPTION_MAX,
): string | undefined {
if (!value) return undefined;
if (value.length <= maxLength) return value;
if (maxLength <= 3) return "...".slice(0, maxLength);
return `${value.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
}
export function buildGitActionProgressStages(input: {
action: GitStackedAction;
hasCustomCommitMessage: boolean;
hasWorkingTreeChanges: boolean;
forcePushOnly?: boolean;
pushTarget?: string;
}): string[] {
const shouldIncludeCommitStages =
!input.forcePushOnly && (input.action === "commit" || input.hasWorkingTreeChanges);
const commitStages = !shouldIncludeCommitStages
? []
: input.hasCustomCommitMessage
? ["Committing..."]
: ["Generating commit message...", "Committing..."];
const pushStage = input.pushTarget ? `Pushing to ${input.pushTarget}...` : "Pushing...";
if (input.action === "commit") {
return commitStages;
}
if (input.action === "commit_push") {
return [...commitStages, pushStage];
}
return [...commitStages, pushStage, "Creating PR..."];
}
const withDescription = (title: string, description: string | undefined) =>
description ? { title, description } : { title };
export function summarizeGitResult(result: GitRunStackedActionResult): {
title: string;
description?: string;
} {
if (result.pr.status === "created" || result.pr.status === "opened_existing") {
const prNumber = result.pr.number ? ` #${result.pr.number}` : "";
const title = `${result.pr.status === "created" ? "Created PR" : "Opened PR"}${prNumber}`;
return withDescription(title, truncateText(result.pr.title));
}
if (result.push.status === "pushed") {
const shortSha = shortenSha(result.commit.commitSha);
const branch = result.push.upstreamBranch ?? result.push.branch;
const pushedCommitPart = shortSha ? ` ${shortSha}` : "";
const branchPart = branch ? ` to ${branch}` : "";
return withDescription(
`Pushed${pushedCommitPart}${branchPart}`,
truncateText(result.commit.subject),
);
}
if (result.commit.status === "created") {
const shortSha = shortenSha(result.commit.commitSha);
const title = shortSha ? `Committed ${shortSha}` : "Committed changes";
return withDescription(title, truncateText(result.commit.subject));
}
return { title: "Done" };
}
export function buildMenuItems(
gitStatus: GitStatusResult | null,
isBusy: boolean,
): GitActionMenuItem[] {
if (!gitStatus) return [];
const hasBranch = gitStatus.branch !== null;
const hasChanges = gitStatus.hasWorkingTreeChanges;
const hasOpenPr = gitStatus.openPr !== null;
const isBehind = gitStatus.behindCount > 0;
const canCommit = !isBusy && hasChanges;
const canPush = !isBusy && hasBranch && !hasChanges && !isBehind && gitStatus.aheadCount > 0;
const canCreatePr =
!isBusy &&
hasBranch &&
!hasChanges &&
!hasOpenPr &&
gitStatus.aheadCount > 0 &&
gitStatus.hasUpstream &&
!isBehind;
const canOpenPr = !isBusy && hasOpenPr;
return [
{
id: "commit",
label: "Commit",
disabled: !canCommit,
icon: "commit",
kind: "open_dialog",
dialogAction: "commit",
},
{
id: "push",
label: "Push",
disabled: !canPush,
icon: "push",
kind: "open_dialog",
dialogAction: "push",
},
hasOpenPr
? {
id: "pr",
label: "Open PR",
disabled: !canOpenPr,
icon: "pr",
kind: "open_pr",
}
: {
id: "pr",
label: "Create PR",
disabled: !canCreatePr,
icon: "pr",
kind: "open_dialog",
dialogAction: "create_pr",
},
];
}
export function resolveQuickAction(
gitStatus: GitStatusResult | null,
isBusy: boolean,
isDefaultBranch = false,
): GitQuickAction {
if (isBusy) {
return { label: "Commit", disabled: true, kind: "show_hint", hint: "Git action in progress." };
}
if (!gitStatus) {
return {
label: "Commit",
disabled: true,
kind: "show_hint",
hint: "Git status is unavailable.",
};
}
const hasBranch = gitStatus.branch !== null;
const hasChanges = gitStatus.hasWorkingTreeChanges;
const hasOpenPr = gitStatus.openPr !== null;
const isAhead = gitStatus.aheadCount > 0;
const isBehind = gitStatus.behindCount > 0;
const isDiverged = isAhead && isBehind;
if (!hasBranch) {
return {
label: "Commit",
disabled: true,
kind: "show_hint",
hint: "Create and checkout a branch before pushing or opening a PR.",
};
}
if (hasChanges) {
if (hasOpenPr || isDefaultBranch) {
return { label: "Commit & push", disabled: false, kind: "run_action", action: "commit_push" };
}
return {
label: "Commit, push & create PR",
disabled: false,
kind: "run_action",
action: "commit_push_pr",
};
}
if (isDiverged) {
return {
label: "Sync branch",
disabled: true,
kind: "show_hint",
hint: "Branch has diverged from upstream. Rebase/merge first.",
};
}
if (isBehind) {
return {
label: "Pull",
disabled: false,
kind: "run_pull",
};
}
if (isAhead) {
if (hasOpenPr || isDefaultBranch) {
return { label: "Push", disabled: false, kind: "run_action", action: "commit_push" };
}
return {
label: "Push & create PR",
disabled: false,
kind: "run_action",
action: "commit_push_pr",
};
}
if (hasOpenPr && gitStatus.hasUpstream) {
return { label: "Open PR", disabled: false, kind: "open_pr" };
}
return {
label: "Commit",
disabled: true,
kind: "show_hint",
hint: "Branch is up to date. No action needed.",
};
}
export function requiresDefaultBranchConfirmation(
action: GitStackedAction,
isDefaultBranch: boolean,
): boolean {
if (!isDefaultBranch) return false;
return action === "commit_push" || action === "commit_push_pr";
}