import { useState } from "react"; import { mergePull, recheckMergeability, updatePull } from "~/fns/pulls"; import { useAction } from "~/hooks/use-action"; import { StatusList } from "./StatusList"; import { MergeAutomation } from "./MergeAutomation"; import { StackMergeControls } from "./StackMergeControls"; import { Badge } from "~/ui/Badge"; import { Panel } from "~/ui/Panel"; import { StatusDot } from "~/ui/StatusDot"; import { Button } from "~/ui/Button"; import { CheckboxField, SelectField, TextAreaField } from "~/ui/Field"; import { ErrorBanner } from "~/ui/Callout"; import type { usePull } from "~/hooks/use-pull"; import { useViewer } from "~/hooks/use-viewer"; type PullData = ReturnType; const METHODS = [ { value: "merge", label: "Create a merge commit" }, { value: "squash", label: "Squash and merge" }, { value: "rebase", label: "Rebase and merge" }, ] as const; /** The browser's UTC offset as git's `+HHMM`, so rebased commits carry the merger's local timezone. */ function browserTimezone(): string { const offset = -new Date().getTimezoneOffset(); const absolute = Math.abs(offset); const hours = String(Math.floor(absolute / 60)).padStart(2, "0"); const minutes = String(absolute % 60).padStart(2, "0"); return `${offset < 0 ? "-" : "+"}${hours}${minutes}`; } export function MergeBox({ owner, repo, data }: { owner: string; repo: string; data: PullData }) { const viewer = useViewer(); const { pull, gate, checks, checksState } = data; const [method, setMethod] = useState<(typeof METHODS)[number]["value"]>("merge"); const merge = useAction(mergePull); const recheck = useAction(recheckMergeability); const ready = useAction(updatePull); const number = pull.number; if (pull.merged) { return (
Merged {pull.mergedBy?.name ?? "Someone"} merged commit{" "} {pull.mergeCommitSha?.slice(0, 7)} into {pull.baseRef}.
); } if (data.conversation.issue.state === "closed") return null; const mergeState = pull.mergeable === "clean" ? "success" : pull.mergeable === "conflict" ? "failure" : "pending"; return ( {checks.length > 0 && (
Checks
)}
{pull.mergeable === "clean" ? "No conflicts with the base branch" : pull.mergeable === "conflict" ? "This branch has conflicts" : "Checking mergeability…"} {pull.behindBy > 0 && ({pull.behindBy} commits behind)} {viewer && ( )}
{gate.blockers.length > 0 && (
    {gate.blockers.map((blocker) => (
  • • {blocker}
  • ))}
)} {gate.bypassed.length > 0 && (

As an administrator you can merge without: {gate.bypassed.join("; ")}

)} {pull.draft && data.canPushHead ? ( ) : data.stackedOn ? (
{data.canMerge && ( setMethod(METHODS.find((item) => item.value === value)?.value ?? "merge")} /> )}
) : ( data.canMerge && (
{ event.preventDefault(); const form = new FormData(event.currentTarget); merge.run({ data: { owner, repo, number, method, message: String(form.get("message") ?? ""), deleteBranch: form.get("deleteBranch") === "on", expectedHead: pull.headSha, timezone: browserTimezone(), }, }); }} > setMethod(METHODS.find((item) => item.value === value)?.value ?? "merge")} /> ) )}
); }