2b5d7268ed
Server: git-rewrite.js (rebase + continue/skip/abort, rebase-interactive with no editor, reset soft/mixed/keep/hard, undo-rewrite, force-push with lease, remote-branch-delete, remote add/rename/remove) and git-conflicts.js (conflict stages read + conflict-save, reflog), dispatched from runGitAction; operationInProgress now reports a rebase and only ALLOWED_WHILE_REBASING writes run during one. New reads: /git/conflict, /git/reflog, /git/remotes, /git/rewrite-plan. Client: History tools menu, MergeEditor, ReflogView, RebaseDialog, InteractiveRebase, ResetDialog, ForcePushDialog, RemotesDialog, rebase banner Skip. CLAUDE.md git-writes rule rewritten for the new actions.
91 lines
2.9 KiB
TypeScript
91 lines
2.9 KiB
TypeScript
/**
|
|
* @file RewriteDone.tsx
|
|
* @description What a finished rebase / interactive rebase / reset leaves on
|
|
* screen: the result, a force-push reminder when the rewrite touched pushed
|
|
* commits, and the recovery button — **Undo** = `undo-rewrite` (`reset --keep`
|
|
* back to the recorded `before`), which the server runs only while HEAD is
|
|
* still the `after` this dialog saw (409 ESTALE otherwise).
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { CheckCircle2, Undo2 } from "lucide-react";
|
|
import type { GitTarget } from "../../../lib/api";
|
|
import { errText, gitRewrite, type RewriteResult } from "../../../lib/gitRewrite";
|
|
import { toast } from "../../../lib/toast";
|
|
import { useArmed } from "../../../hooks/useArmed";
|
|
import { BTN, ErrorLine, Warn } from "./GitModal";
|
|
|
|
export function RewriteDone({
|
|
target,
|
|
result,
|
|
onChanged,
|
|
onUndone,
|
|
}: {
|
|
target: GitTarget;
|
|
result: RewriteResult;
|
|
onChanged: () => void;
|
|
onUndone: () => void;
|
|
}) {
|
|
const { t } = useTranslation(["gitTools"]);
|
|
const [busy, setBusy] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const { isArmed, arm, disarm } = useArmed();
|
|
const canUndo = !!result.before && !!result.after && result.before !== result.after;
|
|
|
|
const undo = async () => {
|
|
if (!isArmed("undo")) return arm("undo");
|
|
disarm();
|
|
setBusy(true);
|
|
setError(null);
|
|
try {
|
|
await gitRewrite.run(target, "undo-rewrite", {
|
|
to: result.before,
|
|
head: result.after,
|
|
confirm: true,
|
|
});
|
|
toast.success(t("undone"));
|
|
onChanged();
|
|
onUndone();
|
|
} catch (e) {
|
|
setError(errText(e));
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div data-testid="git-rewrite-done" className="space-y-1.5">
|
|
<p className="flex items-center gap-1.5 text-status-success">
|
|
<CheckCircle2 className="h-4 w-4 shrink-0" />
|
|
{t("doneTitle")}
|
|
</p>
|
|
{result.output && (
|
|
<pre className="max-h-32 overflow-auto whitespace-pre-wrap break-words rounded bg-surface-2 p-1.5 font-mono text-[10px] text-fg-muted">
|
|
{result.output}
|
|
</pre>
|
|
)}
|
|
{result.needsForcePush && <Warn>{t("needsForcePushAfter")}</Warn>}
|
|
{canUndo && (
|
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
<button
|
|
type="button"
|
|
data-testid="git-rewrite-undo"
|
|
disabled={busy}
|
|
onClick={() => void undo()}
|
|
className={`${BTN} flex items-center gap-1`}
|
|
>
|
|
<Undo2 className="h-3 w-3" />
|
|
{isArmed("undo") ? t("undoConfirm") : t("undo")}
|
|
</button>
|
|
<span className="text-[11px] text-fg-muted">
|
|
{t("undoHint", { sha: result.before!.slice(0, 7) })}
|
|
</span>
|
|
</div>
|
|
)}
|
|
<ErrorLine error={error} />
|
|
</div>
|
|
);
|
|
}
|