Reviewing conflict resolutions with remerge-diff Jump to heading
A merge commit can contain code that no reviewer ever saw. When a merge conflicts, whoever resolves it edits the conflicted files and commits the result. Those edits might be a careful combination of both sides, a wrong pick that drops a fix, or an unrelated change slipped in while the files were open. Standard views of a merge hide all of it: the default combined diff omits hunks that resolved to one side, and a diff against either parent shows the entire other branch. --remerge-diff (Git 2.36+) shows something better: Git re-runs the merge automatically, keeping the conflict markers, and diffs that against what was actually committed. The result is exactly the human contribution to the merge. This page shows how to read it and build it into review, within semantic conflicts and merge verification.
When to use this approach Jump to heading
- Merges on your long-lived branches regularly involve conflict resolution.
- You want to review what was changed during a resolution, not the whole incoming branch.
- An audit or incident needs to know whether a merge introduced code beyond its two sides.
- Resolutions are replayed by rerere, as in rerere conflict automation, and you want to check them in context.
Step 1 — Show a merge’s resolution Jump to heading
Run git show --remerge-diff on a merge commit. Files that merged cleanly show nothing. Files that conflicted show a diff from the auto-merged version — with conflict markers — to the committed version.
git show --remerge-diff 5a9e3c1 diff --git a/src/billing/totals.py b/src/billing/totals.py
remerge CONFLICT (content): Merge conflict in src/billing/totals.py
--- a/src/billing/totals.py
+++ b/src/billing/totals.py
@@ -40,9 +40,5 @@ def invoice_total(lines, customer, invoice):
-<<<<<<< 2d4b6f8 (Round credit notes half-even)
- total = round_half_even(sum(l.amount for l in lines))
-=======
- total = sum(l.amount for l in lines) - discount(customer)
->>>>>>> 9e01b55 (Apply customer discount in totals)
+ total = round_half_even(sum(l.amount for l in lines) - discount(customer))
Read it as: here is the conflict Git produced, and here is what the person replaced it with. In the example, both sides’ intents survived — rounding and the discount — which is a good resolution.
Step 2 — Spot the patterns that need attention Jump to heading
Most resolutions look like the example: markers replaced by a combination. Three other patterns deserve a closer look.
# Merges whose resolutions touched files that did not conflict ("evil merges")
for m in $(git rev-list --merges --since=1.month main); do
extra=$(git show --remerge-diff --format= "$m" | grep -c '^diff --git' || true)
conflicted=$(git show --remerge-diff --format= "$m" | grep -c '^remerge CONFLICT' || true)
[ "$extra" -gt "$conflicted" ] && echo "$m edits $extra file(s), $conflicted conflicted"
done Step 3 — Audit resolutions over a range Jump to heading
git log --remerge-diff applies the same view to every merge in a range, which makes it possible to audit a branch’s integration history — for a release, an incident, or a long-lived branch’s lifetime.
# Every resolution on main touching payments, in the last quarter
git log --merges --remerge-diff --since=3.months --format='%n=== %h %an %ad %s' --date=short \
main -- src/payments/ | less Combine it with the pickaxe to find which merge introduced a line during resolution, which ordinary -S searches miss because they skip merge diffs.
git log --merges --remerge-diff -S'round_half_even(sum' --oneline main The pickaxe itself is covered in finding when code appeared with the pickaxe.
Step 4 — Put resolutions in front of reviewers Jump to heading
When a pull request’s branch merges main into itself and resolves conflicts, the forge’s diff shows the combined result, not the resolution. Post the remerge-diff of each merge on the branch as a review comment so reviewers see it.
jobs:
resolutions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0, ref: "${{ github.event.pull_request.head.sha }}" }
- run: |
out=$(for m in $(git rev-list --merges "origin/${{ github.base_ref }}..HEAD"); do
git show --remerge-diff --format='### %h %s' "$m"; done)
[ -z "$out" ] && exit 0
printf 'Conflict resolutions in this branch:\n\n```diff\n%s\n```\n' "$out" > body.md
gh pr comment "${{ github.event.number }}" --body-file body.md
env: { GH_TOKEN: "${{ github.token }}" } Step 5 — Encourage resolutions that review well Jump to heading
Some resolution habits make remerge-diffs easier to trust. Resolve conflicts only — keep unrelated fixes for a separate commit. Note non-obvious choices in the merge commit message. Prefer rebasing a private branch over merging main into it repeatedly, so the pull request has fewer merge commits to review.
git commit -e # in the merge message, explain any resolution that is not an obvious combination Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Does remerge-diff work for octopus merges? Jump to heading
It is defined for two-parent merges. For octopus merges, Git falls back to another diff format. Octopus merges cannot contain conflict resolutions anyway, because the strategy refuses conflicts.
Why does a remerge-diff show changes in a file that did not conflict? Jump to heading
Either someone edited it while resolving (an “evil merge”), or rename detection or a merge driver behaved differently when the merge was originally made than when it is re-run now. The former needs review; the latter is rare and visible as an otherwise identical file.
Can rerere-applied resolutions be reviewed this way? Jump to heading
Yes, and they should be: a replayed resolution appears in the remerge-diff exactly like a hand-typed one. It is the most practical way to check that rerere applied an old resolution sensibly in a new context.
Related Jump to heading
- Semantic Conflicts & Merge Verification — the parent topic.
- Previewing Merges with git merge-tree — seeing conflicts before a merge happens.
- Reading diff3 and zdiff3 Conflict Markers — the marker format remerge-diff shows.
- Reviewing a Force-Push with git range-diff — the equivalent review aid for rebases.