/** * @file ForcePushDialog.tsx * @description "Force push (with lease)" for the CURRENT branch only: shows * where it goes (remote and branch), the remote commit it will replace (the * lease — the server refuses with ESTALE if the remote moved since the last * fetch), how far ahead/behind HEAD is, and refuses a protected branch unless * the server allows it. Push is a two-click (`useArmed`). * @author Nguyễn Ngọc Trí Vĩ */ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import type { GitTarget } from "../../../lib/api"; import { errCode, errText, gitRewrite, type PushPlan } from "../../../lib/gitRewrite"; import { toast } from "../../../lib/toast"; import { useArmed } from "../../../hooks/useArmed"; import { BTN, BTN_DANGER, ErrorLine, GitModal, Warn } from "./GitModal"; export function ForcePushDialog({ target, onClose, onChanged, }: { target: GitTarget; onClose: () => void; onChanged: () => void; }) { const { t } = useTranslation(["gitTools"]); const [plan, setPlan] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const { isArmed, arm, disarm } = useArmed(); const key = JSON.stringify(target); useEffect(() => { let live = true; gitRewrite .planPush(target) .then((p) => live && setPlan(p)) .catch((e) => live && setError(errText(e))); return () => { live = false; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [key]); const run = async () => { if (!plan) return; if (!isArmed("push")) return arm("push"); disarm(); setBusy(true); setError(null); try { await gitRewrite.run(target, "force-push", { expect: plan.remoteSha ?? "", confirm: true }); toast.success(t("forcePush.done", { remote: plan.remote, branch: plan.remoteBranch })); onChanged(); onClose(); } catch (e) { setError(errCode(e) === "ESTALE" ? t("forcePush.stale") : errText(e)); } finally { setBusy(false); } }; return ( } > {!plan ? ( error ? ( ) : (

{t("loading")}

) ) : (

{t("forcePush.where", { branch: plan.branch, remote: plan.remote, remoteBranch: plan.remoteBranch, })}

{plan.remoteSha ? t("forcePush.replaces", { sha: plan.remoteSha.slice(0, 7), ahead: plan.ahead, behind: plan.behind, }) : t("forcePush.newBranch")}

{plan.behind > 0 && {t("forcePush.dropsRemote", { count: plan.behind })}} {!plan.allowed && {t("forcePush.protected")}}

{t("forcePush.lease")}

)}
); }