/** * @file The confirmation a cherry-pick or revert opens with, showing exactly * what will happen before anything does (the reference tools' preview): the * commits in the order git will apply them (numbered — read from the server, * which orders them the same way the action will), the branch they land on, * whether the working tree is clean, and what happens on a conflict. Confirm * is disabled, with the reason, when the server would refuse (a dirty tree, a * subfolder workspace, another operation open, a merge commit). * @author Nguyễn Ngọc Trí Vĩ */ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { CheckCircle2, AlertTriangle } from "lucide-react"; import { api, type GitTarget } from "../../../lib/api"; import type { PickPlan, PickResult } from "../../../lib/gitHistory"; import { GitModal, ModalButton } from "./GitModal"; export function CherryPickDialog({ target, op, shas, onClose, onDone, }: { target: GitTarget; op: "cherry-pick" | "revert"; shas: string[]; onClose: () => void; onDone: (r: PickResult) => void; }) { const { t } = useTranslation(["gitHistory"]); const [plan, setPlan] = useState(null); const [error, setError] = useState(null); const [running, setRunning] = useState(false); useEffect(() => { let live = true; void api.lanes.gitHistory .pickPlan(target, op, shas) .then((r) => live && (r.available ? setPlan(r) : setError(t("planUnavailable")))) .catch((err) => live && setError(err instanceof Error ? err.message : String(err))); return () => { live = false; }; // eslint-disable-next-line react-hooks/exhaustive-deps -- one plan per dialog }, []); const branch = plan?.branch ?? "HEAD"; const blocker = !plan ? null : plan.busy ? t("blockBusy") : !plan.repoRoot ? t("blockSubdir") : !plan.clean ? t("blockDirty") : null; const n = plan?.commits.length ?? shas.length; const title = op === "revert" ? t("revertTitle", { count: n }) : t("pickTitle", { count: n, branch }); const run = async () => { setRunning(true); setError(null); try { onDone( await api.lanes.gitHistory.pick( target, op, plan!.commits.map((c) => c.sha) ) ); } catch (err) { setError(err instanceof Error ? err.message : String(err)); setRunning(false); } }; return ( {t("cancel")} void run()} > {running ? t("running") : op === "revert" ? t("revertN", { count: n }) : t("pickN", { count: n, branch })} } >
{!plan && !error &&

{t("planning")}

} {plan && ( <>

{op === "revert" ? t("revertIntro", { count: n, branch }) : t("pickIntro", { count: n, branch })}

    {plan.commits.map((c, i) => (
  1. {i + 1}. {c.short} {c.subject}
  2. ))}

{plan.clean && plan.repoRoot && !plan.busy ? ( ) : ( )} {blocker ?? t("treeClean")}

{t(op === "revert" ? "revertResult" : "pickResult")}

{t("onConflict", { op: t(`op.${op}`) })}

)} {error && (

{error}

)}
); }