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.pr?.state === "open"; 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.pr?.state === "open"; 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"; }