import { type GitRunStackedActionResult, type GitStackedAction, type GitStatusResult, type NativeApi,} from "@t3tools/contracts";import { queryOptions, useQuery, useQueryClient } from "@tanstack/react-query";import { useCallback, useEffect, useMemo, useRef, useState } from "react";import { CheckIcon, CloudUploadIcon, GitCommitIcon, Loader2Icon, XIcon,} from "lucide-react";import { GitHubIcon } from "./Icons"; interface GitActionsControlProps { api: NativeApi | undefined; gitCwd: string | null;} type GitActionIconName = "commit" | "push" | "pr"; interface GitActionMenuItem { id: "commit" | "push" | "pr"; label: string; disabled: boolean; icon: GitActionIconName; kind: "open_modal" | "run_action" | "open_pr"; action?: GitStackedAction;} interface GitModalActionOption { action: GitStackedAction; label: string; disabled: boolean; icon: GitActionIconName; detail?: string;} type GitProgressStepStatus = "pending" | "running" | "completed" | "skipped" | "failed"; interface GitProgressStep { id: "generate" | "commit" | "push" | "pr"; label: string; status: GitProgressStepStatus; detail?: string;} interface GitProgressToastAction { kind: "open_pr" | "create_pr"; label: "Open PR" | "Create PR";} function GitActionIcon(props: { icon: GitActionIconName; disabled: boolean }) { const toneClass = props.disabled ? "text-muted-foreground/45" : "text-foreground/85"; if (props.icon === "commit") { return <GitCommitIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; } if (props.icon === "push") { return <CloudUploadIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; } return <GitHubIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />;} function runningStepMessage(stepId: GitProgressStep["id"]): string { if (stepId === "generate") return "Generating commit message..."; if (stepId === "commit") return "Committing changes..."; if (stepId === "push") return "Pushing branch..."; return "Preparing pull request...";} function gitActionModalTitle(): string { return "Commit your changes";} function initialGitProgressSteps(action: GitStackedAction, includeGenerateStep: boolean): GitProgressStep[] { const steps: GitProgressStep[] = []; if (includeGenerateStep) { steps.push({ id: "generate", label: "Generate commit message", status: "pending", }); } steps.push({ id: "commit", label: "Commit changes", status: "pending", }); if (action !== "commit") { steps.push({ id: "push", label: "Push branch", status: "pending", }); } if (action === "commit_push_pr") { steps.push({ id: "pr", label: "Create or open PR", status: "pending", }); } return steps;} function updateProgressStep( steps: GitProgressStep[], id: GitProgressStep["id"], status: GitProgressStepStatus, detail?: string,): GitProgressStep[] { return steps.map((step) => { if (step.id !== id) return step; return { ...step, status, ...(detail ? { detail } : {}), }; });} function runActionLabel(action: GitStackedAction): string { if (action === "commit") return "Commit changes"; if (action === "commit_push") return "Commit and push"; return "Commit and create PR";} function canCreatePrFromStatus(gitStatus: GitStatusResult | null): boolean { if (!gitStatus) return false; const hasBranch = gitStatus.branch !== null; const hasOpenPr = gitStatus.openPr !== null; return ( hasBranch && !hasOpenPr && !gitStatus.hasWorkingTreeChanges && gitStatus.hasUpstream && gitStatus.behindCount === 0 );} export function getGitProgressToastAction(params: { isRunning: boolean; hasError: boolean; result: GitRunStackedActionResult | null; gitStatus: GitStatusResult | null; openPrUrl: string | null;}): GitProgressToastAction | null { if (params.isRunning || params.hasError || !params.result) { return null; } if (params.openPrUrl) { return { kind: "open_pr", label: "Open PR", }; } if (params.result.action === "commit_push" && canCreatePrFromStatus(params.gitStatus)) { return { kind: "create_pr", label: "Create PR", }; } return null;} function buildGitActionMenuItems( gitStatus: GitStatusResult | null, isDisabled: boolean,): GitActionMenuItem[] { if (!gitStatus) return []; const hasBranch = gitStatus.branch !== null; const hasOpenPr = gitStatus.openPr !== null; const hasChanges = gitStatus.hasWorkingTreeChanges; const hasAhead = gitStatus.aheadCount > 0; const canCommit = !isDisabled && hasChanges; const canPush = !isDisabled && hasBranch && !hasChanges && hasAhead; const canOpenPr = !isDisabled && hasOpenPr; const canCreatePr = !isDisabled && canCreatePrFromStatus(gitStatus); return [ { id: "commit", label: "Commit", disabled: !canCommit, icon: "commit", kind: "open_modal", }, { id: "push", label: "Push", disabled: !canPush, icon: "push", kind: "run_action", action: "commit_push", }, hasOpenPr ? { id: "pr", label: "View PR", disabled: !canOpenPr, icon: "pr", kind: "open_pr", } : { id: "pr", label: "Create PR", disabled: !canCreatePr, icon: "pr", kind: "run_action", action: "commit_push_pr", }, ];} function buildGitModalActionOptions( gitStatus: GitStatusResult | null, isDisabled: boolean,): GitModalActionOption[] { const hasBranch = gitStatus?.branch !== null; const hasOpenPr = gitStatus?.openPr !== null; return [ { action: "commit", label: "Commit", disabled: isDisabled, icon: "commit", }, { action: "commit_push", label: "Commit and push", disabled: isDisabled || !hasBranch, icon: "push", ...(!hasBranch ? { detail: "Requires an attached branch." } : {}), }, { action: "commit_push_pr", label: "Commit and create PR", disabled: isDisabled || !hasBranch || hasOpenPr, icon: "pr", ...(!hasBranch ? { detail: "Requires an attached branch." } : hasOpenPr ? { detail: "A PR is already open for this branch." } : {}), }, ];} function getGitStatusQueryKey(gitCwd: string | null): readonly ["git", "status", string | null] { return ["git", "status", gitCwd];} function gitStatusQueryOptions(api: NativeApi | undefined, gitCwd: string | null) { return queryOptions({ queryKey: getGitStatusQueryKey(gitCwd), queryFn: async () => { if (!api || !gitCwd) { throw new Error("Git status is unavailable."); } return api.git.status({ cwd: gitCwd }); }, enabled: !!api && !!gitCwd, });} export default function GitActionsControl({ api, gitCwd }: GitActionsControlProps) { const queryClient = useQueryClient(); const [isGitMenuOpen, setIsGitMenuOpen] = useState(false); const [isGitModalActionRunning, setIsGitModalActionRunning] = useState(false); const [gitActionError, setGitActionError] = useState<string | null>(null); const [isGitModalOpen, setIsGitModalOpen] = useState(false); const [gitModalSelectedAction, setGitModalSelectedAction] = useState<GitStackedAction>("commit"); const [gitModalCommitMessage, setGitModalCommitMessage] = useState(""); const [gitModalProgress, setGitModalProgress] = useState<GitProgressStep[]>([]); const [gitModalError, setGitModalError] = useState<string | null>(null); const [gitModalResult, setGitModalResult] = useState<GitRunStackedActionResult | null>(null); const [isGitProgressToastVisible, setIsGitProgressToastVisible] = useState(false); const gitMenuRef = useRef<HTMLDivElement>(null); const { data: gitStatus = null, error: gitStatusError } = useQuery( gitStatusQueryOptions(api, gitCwd), ); const isGitActionRunning = isGitModalActionRunning; const gitBaseDisabled = !api || !gitCwd || !gitStatus || isGitActionRunning; const gitActionMenuItems = useMemo( () => buildGitActionMenuItems(gitStatus, gitBaseDisabled), [gitBaseDisabled, gitStatus], ); const gitModalActionOptions = useMemo( () => buildGitModalActionOptions(gitStatus, !gitStatus || isGitActionRunning), [gitStatus, isGitActionRunning], ); const selectedGitModalActionOption = useMemo( () => gitModalActionOptions.find((option) => option.action === gitModalSelectedAction) ?? null, [gitModalActionOptions, gitModalSelectedAction], ); const visibleGitActionError = gitActionError ?? gitStatusError?.message; const gitProgressToastOpenPrUrl = gitModalResult?.pr.url ?? gitStatus?.openPr?.url ?? null; const showGitProgressToast = isGitProgressToastVisible && (isGitModalActionRunning || gitModalProgress.length > 0 || gitModalError !== null || gitModalResult !== null); const activeGitProgressStep = useMemo( () => gitModalProgress.find((step) => step.status === "running") ?? null, [gitModalProgress], ); const gitProgressToastStatus = isGitModalActionRunning ? "running" : gitModalError ? "failed" : "done"; const gitProgressToastMessage = isGitModalActionRunning ? runningStepMessage(activeGitProgressStep?.id ?? "commit") : gitModalError ? "Git action failed." : "Done."; const gitProgressToastAction = useMemo( () => getGitProgressToastAction({ isRunning: isGitModalActionRunning, hasError: gitModalError !== null, result: gitModalResult, gitStatus, openPrUrl: gitProgressToastOpenPrUrl, }), [ gitModalError, gitModalResult, gitProgressToastOpenPrUrl, gitStatus, isGitModalActionRunning, ], ); const refreshGitStatus = useCallback(async () => { if (!api || !gitCwd) return; await queryClient.invalidateQueries({ queryKey: ["git"] }); setGitActionError(null); }, [api, gitCwd, queryClient]); const openGitActionModal = useCallback(() => { setIsGitMenuOpen(false); setIsGitModalOpen(true); setGitModalSelectedAction("commit"); setGitModalCommitMessage(""); setIsGitProgressToastVisible(false); setGitModalProgress([]); setGitModalError(null); setGitModalResult(null); setGitActionError(null); }, []); const closeGitActionModal = useCallback(() => { if (isGitActionRunning) return; setIsGitModalOpen(false); setGitModalSelectedAction("commit"); setGitModalCommitMessage(""); }, [isGitActionRunning]); const openExistingPr = useCallback(() => { setIsGitMenuOpen(false); setGitActionError(null); if (!api) { setGitActionError("Link opening is unavailable."); return; } const prUrl = gitStatus?.openPr?.url ?? null; if (!prUrl) { setGitActionError("No open PR found for the current branch."); return; } void api.shell.openExternal(prUrl).catch((error) => { setGitActionError(error instanceof Error ? error.message : "Unable to open PR link."); }); }, [api, gitStatus?.openPr?.url]); const openPrFromToast = useCallback(() => { if (!api) { setGitModalError("Link opening is unavailable."); return; } const prUrl = gitProgressToastOpenPrUrl; if (!prUrl) return; void api.shell.openExternal(prUrl).catch((error) => { setGitModalError(error instanceof Error ? error.message : "Unable to open PR link."); }); }, [api, gitProgressToastOpenPrUrl]); const dismissGitProgressToast = useCallback(() => { if (isGitModalActionRunning) return; setIsGitProgressToastVisible(false); setGitModalProgress([]); setGitModalError(null); setGitModalResult(null); }, [isGitModalActionRunning]); const executeGitAction = useCallback( async (params: { action: GitStackedAction; commitMessage: string; includeGeneratedCommitMessage: boolean; closeModal: boolean; }) => { if (!api || !gitCwd) return; const actionCwd = gitCwd; const { action, closeModal, commitMessage, includeGeneratedCommitMessage } = params; setIsGitMenuOpen(false); if (closeModal) { setIsGitModalOpen(false); } setIsGitProgressToastVisible(true); setIsGitModalActionRunning(true); setGitModalError(null); setGitActionError(null); setGitModalResult(null); setGitModalProgress(initialGitProgressSteps(action, includeGeneratedCommitMessage)); let commit: GitRunStackedActionResult["commit"] = { status: "skipped_no_changes", }; let push: GitRunStackedActionResult["push"] = { status: "skipped_not_requested", }; let pr: GitRunStackedActionResult["pr"] = { status: "skipped_not_requested", }; const updateStep = ( id: GitProgressStep["id"], status: GitProgressStepStatus, detail?: string, ) => { setGitModalProgress((steps) => updateProgressStep(steps, id, status, detail)); }; try { if (includeGeneratedCommitMessage) { updateStep("generate", "running"); } else { updateStep("commit", "running"); } const commitRun = await api.git.runStackedAction({ cwd: actionCwd, action: "commit", ...(commitMessage.length > 0 ? { commitMessage } : {}), }); commit = commitRun.commit; if (includeGeneratedCommitMessage) { if (commitRun.commit.status === "created") { if (commitRun.commit.subject) { setGitModalCommitMessage(commitRun.commit.subject); } updateStep("generate", "completed"); } else { updateStep("generate", "skipped", "No local changes to commit."); } } if (commitRun.commit.status === "created") { updateStep("commit", "completed", commitRun.commit.subject ?? "Committed local changes."); } else { updateStep("commit", "skipped", "No local changes to commit."); } if (action !== "commit") { updateStep("push", "running"); const pushRun = await api.git.runStackedAction({ cwd: actionCwd, action: "commit_push", }); push = pushRun.push; if (pushRun.push.status === "pushed") {Lines 1–500 of 905. Load more
import { type GitRunStackedActionResult, type GitStackedAction, type GitStatusResult, type NativeApi, } from "@t3tools/contracts"; import { queryOptions, useQuery, useQueryClient } from "@tanstack/react-query"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { CheckIcon, CloudUploadIcon, GitCommitIcon, Loader2Icon, XIcon, } from "lucide-react"; import { GitHubIcon } from "./Icons"; interface GitActionsControlProps { api: NativeApi | undefined; gitCwd: string | null; } type GitActionIconName = "commit" | "push" | "pr"; interface GitActionMenuItem { id: "commit" | "push" | "pr"; label: string; disabled: boolean; icon: GitActionIconName; kind: "open_modal" | "run_action" | "open_pr"; action?: GitStackedAction; } interface GitModalActionOption { action: GitStackedAction; label: string; disabled: boolean; icon: GitActionIconName; detail?: string; } type GitProgressStepStatus = "pending" | "running" | "completed" | "skipped" | "failed"; interface GitProgressStep { id: "generate" | "commit" | "push" | "pr"; label: string; status: GitProgressStepStatus; detail?: string; } interface GitProgressToastAction { kind: "open_pr" | "create_pr"; label: "Open PR" | "Create PR"; } function GitActionIcon(props: { icon: GitActionIconName; disabled: boolean }) { const toneClass = props.disabled ? "text-muted-foreground/45" : "text-foreground/85"; if (props.icon === "commit") { return <GitCommitIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; } if (props.icon === "push") { return <CloudUploadIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; } return <GitHubIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; } function runningStepMessage(stepId: GitProgressStep["id"]): string { if (stepId === "generate") return "Generating commit message..."; if (stepId === "commit") return "Committing changes..."; if (stepId === "push") return "Pushing branch..."; return "Preparing pull request..."; } function gitActionModalTitle(): string { return "Commit your changes"; } function initialGitProgressSteps(action: GitStackedAction, includeGenerateStep: boolean): GitProgressStep[] { const steps: GitProgressStep[] = []; if (includeGenerateStep) { steps.push({ id: "generate", label: "Generate commit message", status: "pending", }); } steps.push({ id: "commit", label: "Commit changes", status: "pending", }); if (action !== "commit") { steps.push({ id: "push", label: "Push branch", status: "pending", }); } if (action === "commit_push_pr") { steps.push({ id: "pr", label: "Create or open PR", status: "pending", }); } return steps; } function updateProgressStep( steps: GitProgressStep[], id: GitProgressStep["id"], status: GitProgressStepStatus, detail?: string, ): GitProgressStep[] { return steps.map((step) => { if (step.id !== id) return step; return { ...step, status, ...(detail ? { detail } : {}), }; }); } function runActionLabel(action: GitStackedAction): string { if (action === "commit") return "Commit changes"; if (action === "commit_push") return "Commit and push"; return "Commit and create PR"; } function canCreatePrFromStatus(gitStatus: GitStatusResult | null): boolean { if (!gitStatus) return false; const hasBranch = gitStatus.branch !== null; const hasOpenPr = gitStatus.openPr !== null; return ( hasBranch && !hasOpenPr && !gitStatus.hasWorkingTreeChanges && gitStatus.hasUpstream && gitStatus.behindCount === 0 ); } export function getGitProgressToastAction(params: { isRunning: boolean; hasError: boolean; result: GitRunStackedActionResult | null; gitStatus: GitStatusResult | null; openPrUrl: string | null; }): GitProgressToastAction | null { if (params.isRunning || params.hasError || !params.result) { return null; } if (params.openPrUrl) { return { kind: "open_pr", label: "Open PR", }; } if (params.result.action === "commit_push" && canCreatePrFromStatus(params.gitStatus)) { return { kind: "create_pr", label: "Create PR", }; } return null; } function buildGitActionMenuItems( gitStatus: GitStatusResult | null, isDisabled: boolean, ): GitActionMenuItem[] { if (!gitStatus) return []; const hasBranch = gitStatus.branch !== null; const hasOpenPr = gitStatus.openPr !== null; const hasChanges = gitStatus.hasWorkingTreeChanges; const hasAhead = gitStatus.aheadCount > 0; const canCommit = !isDisabled && hasChanges; const canPush = !isDisabled && hasBranch && !hasChanges && hasAhead; const canOpenPr = !isDisabled && hasOpenPr; const canCreatePr = !isDisabled && canCreatePrFromStatus(gitStatus); return [ { id: "commit", label: "Commit", disabled: !canCommit, icon: "commit", kind: "open_modal", }, { id: "push", label: "Push", disabled: !canPush, icon: "push", kind: "run_action", action: "commit_push", }, hasOpenPr ? { id: "pr", label: "View PR", disabled: !canOpenPr, icon: "pr", kind: "open_pr", } : { id: "pr", label: "Create PR", disabled: !canCreatePr, icon: "pr", kind: "run_action", action: "commit_push_pr", }, ]; } function buildGitModalActionOptions( gitStatus: GitStatusResult | null, isDisabled: boolean, ): GitModalActionOption[] { const hasBranch = gitStatus?.branch !== null; const hasOpenPr = gitStatus?.openPr !== null; return [ { action: "commit", label: "Commit", disabled: isDisabled, icon: "commit", }, { action: "commit_push", label: "Commit and push", disabled: isDisabled || !hasBranch, icon: "push", ...(!hasBranch ? { detail: "Requires an attached branch." } : {}), }, { action: "commit_push_pr", label: "Commit and create PR", disabled: isDisabled || !hasBranch || hasOpenPr, icon: "pr", ...(!hasBranch ? { detail: "Requires an attached branch." } : hasOpenPr ? { detail: "A PR is already open for this branch." } : {}), }, ]; } function getGitStatusQueryKey(gitCwd: string | null): readonly ["git", "status", string | null] { return ["git", "status", gitCwd]; } function gitStatusQueryOptions(api: NativeApi | undefined, gitCwd: string | null) { return queryOptions({ queryKey: getGitStatusQueryKey(gitCwd), queryFn: async () => { if (!api || !gitCwd) { throw new Error("Git status is unavailable."); } return api.git.status({ cwd: gitCwd }); }, enabled: !!api && !!gitCwd, }); } export default function GitActionsControl({ api, gitCwd }: GitActionsControlProps) { const queryClient = useQueryClient(); const [isGitMenuOpen, setIsGitMenuOpen] = useState(false); const [isGitModalActionRunning, setIsGitModalActionRunning] = useState(false); const [gitActionError, setGitActionError] = useState<string | null>(null); const [isGitModalOpen, setIsGitModalOpen] = useState(false); const [gitModalSelectedAction, setGitModalSelectedAction] = useState<GitStackedAction>("commit"); const [gitModalCommitMessage, setGitModalCommitMessage] = useState(""); const [gitModalProgress, setGitModalProgress] = useState<GitProgressStep[]>([]); const [gitModalError, setGitModalError] = useState<string | null>(null); const [gitModalResult, setGitModalResult] = useState<GitRunStackedActionResult | null>(null); const [isGitProgressToastVisible, setIsGitProgressToastVisible] = useState(false); const gitMenuRef = useRef<HTMLDivElement>(null); const { data: gitStatus = null, error: gitStatusError } = useQuery( gitStatusQueryOptions(api, gitCwd), ); const isGitActionRunning = isGitModalActionRunning; const gitBaseDisabled = !api || !gitCwd || !gitStatus || isGitActionRunning; const gitActionMenuItems = useMemo( () => buildGitActionMenuItems(gitStatus, gitBaseDisabled), [gitBaseDisabled, gitStatus], ); const gitModalActionOptions = useMemo( () => buildGitModalActionOptions(gitStatus, !gitStatus || isGitActionRunning), [gitStatus, isGitActionRunning], ); const selectedGitModalActionOption = useMemo( () => gitModalActionOptions.find((option) => option.action === gitModalSelectedAction) ?? null, [gitModalActionOptions, gitModalSelectedAction], ); const visibleGitActionError = gitActionError ?? gitStatusError?.message; const gitProgressToastOpenPrUrl = gitModalResult?.pr.url ?? gitStatus?.openPr?.url ?? null; const showGitProgressToast = isGitProgressToastVisible && (isGitModalActionRunning || gitModalProgress.length > 0 || gitModalError !== null || gitModalResult !== null); const activeGitProgressStep = useMemo( () => gitModalProgress.find((step) => step.status === "running") ?? null, [gitModalProgress], ); const gitProgressToastStatus = isGitModalActionRunning ? "running" : gitModalError ? "failed" : "done"; const gitProgressToastMessage = isGitModalActionRunning ? runningStepMessage(activeGitProgressStep?.id ?? "commit") : gitModalError ? "Git action failed." : "Done."; const gitProgressToastAction = useMemo( () => getGitProgressToastAction({ isRunning: isGitModalActionRunning, hasError: gitModalError !== null, result: gitModalResult, gitStatus, openPrUrl: gitProgressToastOpenPrUrl, }), [ gitModalError, gitModalResult, gitProgressToastOpenPrUrl, gitStatus, isGitModalActionRunning, ], ); const refreshGitStatus = useCallback(async () => { if (!api || !gitCwd) return; await queryClient.invalidateQueries({ queryKey: ["git"] }); setGitActionError(null); }, [api, gitCwd, queryClient]); const openGitActionModal = useCallback(() => { setIsGitMenuOpen(false); setIsGitModalOpen(true); setGitModalSelectedAction("commit"); setGitModalCommitMessage(""); setIsGitProgressToastVisible(false); setGitModalProgress([]); setGitModalError(null); setGitModalResult(null); setGitActionError(null); }, []); const closeGitActionModal = useCallback(() => { if (isGitActionRunning) return; setIsGitModalOpen(false); setGitModalSelectedAction("commit"); setGitModalCommitMessage(""); }, [isGitActionRunning]); const openExistingPr = useCallback(() => { setIsGitMenuOpen(false); setGitActionError(null); if (!api) { setGitActionError("Link opening is unavailable."); return; } const prUrl = gitStatus?.openPr?.url ?? null; if (!prUrl) { setGitActionError("No open PR found for the current branch."); return; } void api.shell.openExternal(prUrl).catch((error) => { setGitActionError(error instanceof Error ? error.message : "Unable to open PR link."); }); }, [api, gitStatus?.openPr?.url]); const openPrFromToast = useCallback(() => { if (!api) { setGitModalError("Link opening is unavailable."); return; } const prUrl = gitProgressToastOpenPrUrl; if (!prUrl) return; void api.shell.openExternal(prUrl).catch((error) => { setGitModalError(error instanceof Error ? error.message : "Unable to open PR link."); }); }, [api, gitProgressToastOpenPrUrl]); const dismissGitProgressToast = useCallback(() => { if (isGitModalActionRunning) return; setIsGitProgressToastVisible(false); setGitModalProgress([]); setGitModalError(null); setGitModalResult(null); }, [isGitModalActionRunning]); const executeGitAction = useCallback( async (params: { action: GitStackedAction; commitMessage: string; includeGeneratedCommitMessage: boolean; closeModal: boolean; }) => { if (!api || !gitCwd) return; const actionCwd = gitCwd; const { action, closeModal, commitMessage, includeGeneratedCommitMessage } = params; setIsGitMenuOpen(false); if (closeModal) { setIsGitModalOpen(false); } setIsGitProgressToastVisible(true); setIsGitModalActionRunning(true); setGitModalError(null); setGitActionError(null); setGitModalResult(null); setGitModalProgress(initialGitProgressSteps(action, includeGeneratedCommitMessage)); let commit: GitRunStackedActionResult["commit"] = { status: "skipped_no_changes", }; let push: GitRunStackedActionResult["push"] = { status: "skipped_not_requested", }; let pr: GitRunStackedActionResult["pr"] = { status: "skipped_not_requested", }; const updateStep = ( id: GitProgressStep["id"], status: GitProgressStepStatus, detail?: string, ) => { setGitModalProgress((steps) => updateProgressStep(steps, id, status, detail)); }; try { if (includeGeneratedCommitMessage) { updateStep("generate", "running"); } else { updateStep("commit", "running"); } const commitRun = await api.git.runStackedAction({ cwd: actionCwd, action: "commit", ...(commitMessage.length > 0 ? { commitMessage } : {}), }); commit = commitRun.commit; if (includeGeneratedCommitMessage) { if (commitRun.commit.status === "created") { if (commitRun.commit.subject) { setGitModalCommitMessage(commitRun.commit.subject); } updateStep("generate", "completed"); } else { updateStep("generate", "skipped", "No local changes to commit."); } } if (commitRun.commit.status === "created") { updateStep("commit", "completed", commitRun.commit.subject ?? "Committed local changes."); } else { updateStep("commit", "skipped", "No local changes to commit."); } if (action !== "commit") { updateStep("push", "running"); const pushRun = await api.git.runStackedAction({ cwd: actionCwd, action: "commit_push", }); push = pushRun.push; if (pushRun.push.status === "pushed") {