440 lines · 11.3 KB · TypeScript
RawDownload
import type {  GitRunStackedActionInput,  GitRunStackedActionResult,  GitStackedAction,  VcsStatusResult,} from "@t3tools/contracts";import { isTemporaryWorktreeBranch } from "@t3tools/shared/git"; 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;} export interface DefaultBranchActionDialogCopy {  title: string;  description: string;  continueLabel: string;} export type DefaultBranchConfirmableAction =  | "push"  | "create_pr"  | "commit_push"  | "commit_push_pr"; export type GitActionRequestInput = Pick<  GitRunStackedActionInput,  "action" | "commitMessage" | "featureBranch" | "filePaths">; export function buildGitActionProgressStages(input: {  action: GitStackedAction;  hasCustomCommitMessage: boolean;  hasWorkingTreeChanges: boolean;  pushTarget?: string;  featureBranch?: boolean;  shouldPushBeforePr?: boolean;}): string[] {  const branchStages = input.featureBranch ? ["Preparing feature branch..."] : [];  const pushStage = input.pushTarget ? `Pushing to ${input.pushTarget}...` : "Pushing...";  const prStages = [    "Preparing PR...",    "Generating PR content...",    "Creating GitHub pull request...",  ];   if (input.action === "push") {    return [pushStage];  }  if (input.action === "create_pr") {    return input.shouldPushBeforePr ? [pushStage, ...prStages] : prStages;  }   const shouldIncludeCommitStages = input.action === "commit" || input.hasWorkingTreeChanges;  const commitStages = !shouldIncludeCommitStages    ? []    : input.hasCustomCommitMessage      ? ["Committing..."]      : ["Generating commit message...", "Committing..."];  if (input.action === "commit") {    return [...branchStages, ...commitStages];  }  if (input.action === "commit_push") {    return [...branchStages, ...commitStages, pushStage];  }  return [...branchStages, ...commitStages, pushStage, ...prStages];} export function buildMenuItems(  gitStatus: VcsStatusResult | null,  isBusy: boolean,  hasOriginRemote = true,): GitActionMenuItem[] {  if (!gitStatus) return [];   const hasBranch = gitStatus.refName !== null;  const hasChanges = gitStatus.hasWorkingTreeChanges;  const hasOpenPr = gitStatus.pr?.state === "open";  const isBehind = gitStatus.behindCount > 0;  const canPushWithoutUpstream = hasOriginRemote && !gitStatus.hasUpstream;  const canCommit = !isBusy && hasChanges;  const canPush =    !isBusy &&    hasBranch &&    !hasChanges &&    !isBehind &&    gitStatus.aheadCount > 0 &&    (gitStatus.hasUpstream || canPushWithoutUpstream);  const canCreatePr =    !isBusy &&    hasBranch &&    !hasChanges &&    !hasOpenPr &&    gitStatus.aheadCount > 0 &&    !isBehind &&    (gitStatus.hasUpstream || canPushWithoutUpstream);  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: "View 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: VcsStatusResult | null,  isBusy: boolean,  isDefaultBranch = false,  hasOriginRemote = true,): 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.refName !== 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 (!gitStatus.hasUpstream && !hasOriginRemote) {      return { label: "Commit", disabled: false, kind: "run_action", action: "commit" };    }    if (hasOpenPr || isDefaultBranch) {      return { label: "Commit & push", disabled: false, kind: "run_action", action: "commit_push" };    }    return {      label: "Commit, push & PR",      disabled: false,      kind: "run_action",      action: "commit_push_pr",    };  }   if (!gitStatus.hasUpstream) {    if (!hasOriginRemote) {      if (hasOpenPr && !isAhead) {        return { label: "View PR", disabled: false, kind: "open_pr" };      }      return {        label: "Push",        disabled: true,        kind: "show_hint",        hint: 'Add an "origin" remote before pushing or creating a PR.',      };    }    if (!isAhead) {      if (hasOpenPr) {        return { label: "View PR", disabled: false, kind: "open_pr" };      }      return {        label: "Push",        disabled: true,        kind: "show_hint",        hint: "No local commits to push.",      };    }    if (hasOpenPr || isDefaultBranch) {      return {        label: "Push",        disabled: false,        kind: "run_action",        action: isDefaultBranch ? "commit_push" : "push",      };    }    return {      label: "Push & create PR",      disabled: false,      kind: "run_action",      action: "create_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: isDefaultBranch ? "commit_push" : "push",      };    }    return {      label: "Push & create PR",      disabled: false,      kind: "run_action",      action: "create_pr",    };  }   if (hasOpenPr && gitStatus.hasUpstream) {    return { label: "View 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 getGitActionDisabledReason(input: {  item: GitActionMenuItem;  gitStatus: VcsStatusResult | null;  isBusy: boolean;  hasOriginRemote: boolean;}): string | null {  const { item, gitStatus, isBusy, hasOriginRemote } = input;  if (!item.disabled) return null;  if (isBusy) return "Git action in progress.";  if (!gitStatus) return "Git status is unavailable.";   const hasBranch = gitStatus.refName !== null;  const hasChanges = gitStatus.hasWorkingTreeChanges;  const hasOpenPr = gitStatus.pr?.state === "open";  const isAhead = gitStatus.aheadCount > 0;  const isBehind = gitStatus.behindCount > 0;   if (item.id === "commit") {    if (!hasChanges) {      return "Worktree is clean. Make changes before committing.";    }    return "Commit is currently unavailable.";  }   if (item.id === "push") {    if (!hasBranch) {      return "Detached HEAD: checkout a branch before pushing.";    }    if (hasChanges) {      return "Commit or stash local changes before pushing.";    }    if (isBehind) {      return "Branch is behind upstream. Pull/rebase before pushing.";    }    if (!gitStatus.hasUpstream && !hasOriginRemote) {      return 'Add an "origin" remote before pushing.';    }    if (!isAhead) {      return "No local commits to push.";    }    return "Push is currently unavailable.";  }   if (hasOpenPr) {    return "View PR is currently unavailable.";  }  if (!hasBranch) {    return "Detached HEAD: checkout a branch before creating a PR.";  }  if (hasChanges) {    return "Commit local changes before creating a PR.";  }  if (!gitStatus.hasUpstream && !hasOriginRemote) {    return 'Add an "origin" remote before creating a PR.';  }  if (!isAhead) {    return "No local commits to include in a PR.";  }  if (isBehind) {    return "Branch is behind upstream. Pull/rebase before creating a PR.";  }  return "Create PR is currently unavailable.";} export function requiresDefaultBranchConfirmation(  action: GitStackedAction,  isDefaultBranch: boolean,): boolean {  if (!isDefaultBranch) return false;  return (    action === "push" ||    action === "create_pr" ||    action === "commit_push" ||    action === "commit_push_pr"  );} export function resolveDefaultBranchActionDialogCopy(input: {  action: DefaultBranchConfirmableAction;  branchName: string;  includesCommit: boolean;}): DefaultBranchActionDialogCopy {  const branchLabel = input.branchName;  const suffix = ` on "${branchLabel}". You can continue on this branch or create a feature branch and run the same action there.`;   if (input.action === "push" || input.action === "commit_push") {    if (input.includesCommit) {      return {        title: "Commit & push to default branch?",        description: `This action will commit and push changes${suffix}`,        continueLabel: `Commit & push to ${branchLabel}`,      };    }    return {      title: "Push to default branch?",      description: `This action will push local commits${suffix}`,      continueLabel: `Push to ${branchLabel}`,    };  }   if (input.includesCommit) {    return {      title: "Commit, push & create PR from default branch?",      description: `This action will commit, push, and create a PR${suffix}`,      continueLabel: "Commit, push & create PR",    };  }  return {    title: "Push & create PR from default branch?",    description: `This action will push local commits and create a PR${suffix}`,    continueLabel: "Push & create PR",  };} export function resolveThreadBranchUpdate(  result: GitRunStackedActionResult,): { branch: string } | null {  if (result.branch.status !== "created" || !result.branch.name) {    return null;  }   return {    branch: result.branch.name,  };} export function resolveLiveThreadBranchUpdate(input: {  threadBranch: string | null;  gitStatus: VcsStatusResult | null;}): { branch: string | null } | null {  if (!input.gitStatus) {    return null;  }   if (input.gitStatus.refName === null && input.threadBranch !== null) {    return null;  }   if (input.threadBranch === input.gitStatus.refName) {    return null;  }   if (    input.threadBranch !== null &&    input.gitStatus.refName !== null &&    !isTemporaryWorktreeBranch(input.threadBranch) &&    isTemporaryWorktreeBranch(input.gitStatus.refName)  ) {    return null;  }   return {    branch: input.gitStatus.refName,  };}