resolving-merge-conflicts
-
Orient before you touch anything. Establish from
git statuswhether you are in a merge, a rebase, a cherry-pick, a revert, agit am, or a stash pop. Unmerged paths with none of the other five in progress mean a conflicting stash pop; confirm it withgit stash listand the request, and ask the user when their origin is unclear. When none is in progress and nothing is unmerged, start a merge only from a clean worktree. Whengit status --porcelainlists anything, stop and ask the user to commit it, stash it, or move it to a separate worktree; never stash it yourself. For a PR, the base repository is the PR URL's<owner>/<repo>; read the base ref, its tip, the head branch, and the head repository withgh pr view <url> --json baseRefName,baseRefOid,headRefName,headRefOid,headRepository. The base branch is the one the request names, else the PR'sbaseRefName, else the default branch fromgh repo view --json defaultBranchRef. When the head branch,headRefNamefor a PR, is not checked out, stop and ask. Before starting a merge or rebase for a PR, fetchrefs/pull/<number>/headfrom the remote that resolves to the base repository, which works for fork PRs too, and require both the fetched ref and localHEADto equalheadRefOid. On a mismatch, stop, showheadRefOidand every SHA that differs from it, and ask, because the merge has to start from what the PR holds. Fetch from the remote that resolves to the base repository, require the fetched base ref to equalbaseRefOidwhen the base is the PR's, merge that remote's base ref into the head branch, or rebase onto it when the user asked for a rebase, and orient. Rebase only when the user asked for one, because rebasing a published branch rewrites its history; step 5'spreflight-mutationsgate covers its push. Under a rebaseoursis the upstream you are replaying onto andtheirsis your own commit, the inverse of a merge, and getting this backwards is the most common wrong resolution.Then split the conflicted files. Regenerate generated artifacts and lockfiles from source, never hand-merge them. That means
bun.lock,pnpm-lock.yaml,_generated/, and snapshots. Only hand-written files go through steps 2-3. Under a rebase, stop and ask before touching a conflicted Drizzle migration file, because deleting the head's SQL there rewrites the commit that added it. Under a merge, a conflicted Drizzlemeta/_journal.jsonormeta/<idx>_snapshot.jsontakes the base side. Delete the head's own migration SQL in the same merge, or take the base side when the base added the same path, and record the head's SQL as dropped in the ledger either way. Whenrefresh-drizzle-migrationdid not start this merge, first run its step 1 config discovery, head-migration listing, and base-owned check withMERGE_HEADas<base-ref>, before resolving any Drizzle file, and stop on any path it flags, because taking the base side there discards the head's edit to a migration the base already has. When this merge drops the head's migration, skip step 4, since checks run against a schema with no migration fail until it is regenerated andrefresh-drizzle-migrationrunsdoneafter regeneration, or on the merge when the refresh stops before that run. Finish at step 5. Whenrefresh-drizzle-migrationdid not start this merge, run its step 1 head-migration listing against the remote-tracking branch (<remote>/<branch>) this merge brought in, which must containHEAD^2. When that listing finds a head migration in any config, including one this merge or an earlier one dropped, invokerefresh-drizzle-migrationbefore any push with that branch as its base, so the branch never ships a schema change without its migration. -
Find the primary sources for each conflict. Learn why each side made its change. Read the commit messages, check the PRs, check original issues and tickets.
-
Resolve each hunk against the primary sources, not against the markers. Preserve both intents. Where incompatible, pick the one matching the merge's stated goal and note the trade-off. Every line in the resolution traces to one side's original or to a mechanical combination of both.
Before staging, diff your resolution against both parents for every conflicted file. Run
git diff --cc <file>, orgit diff :2:<file> <file>andgit diff :3:<file> <file>. Place every hunk from both sides in exactly one bucket. The buckets are kept, superseded with a note naming what replaced it, or dropped with a note naming why. A hunk you cannot place is a silent drop, an unresolved conflict wearing a resolution. State the ledger before you commit.Never abort the merge to dodge a hard hunk. When the two intents are genuinely incompatible and no stated goal decides between them, stop and put both candidate resolutions to the user rather than guessing.
-
Run the project's checks.
/doneowns this pipeline. Let it select the acceptance lanes, verify each one, assign states, and build its readiness card. Stop at that card and take none of the handoffs its final section ends in. Committing mid-merge concludes the merge with a generated conventional subject instead of the merge message, sogit-commitandfile-prboth wait for step 5. Fix anything the merge broke. -
Finish the merge or rebase. When the operation rewrites commits or refs already published or consumed, invoke
preflight-mutationsimmediately before continuing that published-history operation or updating its shared ref. Pass the exact operation, local, upstream, base, and head SHAs, affected remote refs, dependent branches and PRs, recovery ref, and explicit rewrite authorization. Apply its result contract before continuing. Unpublished local conflict resolution does not use this gate.Stage everything and commit. If rebasing, continue until all commits are replayed. The same conflict often resurfaces at each replayed commit, and your resolution must stay consistent across them.