Previewing merges with git merge-tree Jump to heading
Most ways of finding out whether two branches merge cleanly involve doing the merge: check out one branch, merge the other, look, then abort. That needs a working tree, moves HEAD, and cannot run in parallel. Since Git 2.38, git merge-tree --write-tree performs a real merge β the same ort strategy, the same rename detection β entirely in the object database. It prints the resulting tree, lists conflicted paths, and exits with a status that says whether the merge was clean. Nothing is checked out and no ref moves, so it is safe on a bare repository, in CI, and across hundreds of branch pairs at once. This page shows how to use it for conflict previews and for building testable merge commits, within semantic conflicts and merge verification.
When to use this approach Jump to heading
- You want to know whether a branch will merge cleanly without touching your working tree.
- You want a dashboard of which open branches currently conflict with main.
- You want CI to test the result of a merge before it happens, including on servers with bare repositories.
- You are measuring conflict rates across history, as in measuring branch age against conflict risk.
Step 1 β Run a preview and read the output Jump to heading
Give merge-tree two commits. It finds their merge base, merges, writes the result tree and prints its ID. On conflict it also prints the conflicted paths with their stages, and informational messages.
git merge-tree --write-tree main feature/renewals
# 3f1c9a2e7d... (exit 0: clean)
git merge-tree --write-tree main feature/payments-v2
# 7a0b2c4d1e... (exit 1: conflicts)
# 100644 1d3f... 1 src/payments/client.py
# 100644 9e2a... 2 src/payments/client.py
# 100644 4b77... 3 src/payments/client.py
#
# Auto-merging src/payments/client.py
# CONFLICT (content): Merge conflict in src/payments/client.py Even on conflict, the printed tree exists: it contains the files with conflict markers, exactly as a real merge would leave them in the working tree.
# Quiet form for scripts: only paths, and the exit code
git merge-tree --write-tree --name-only --no-messages main feature/payments-v2 | tail -n +2 Step 2 β Inspect a conflicted preview Jump to heading
The tree from a conflicted preview contains marker-laden files, so you can look at exactly what someone would see when merging.
tree=$(git merge-tree --write-tree main feature/payments-v2 | head -1)
git show "$tree:src/payments/client.py" | sed -n '/<<<<<<<</,/>>>>>>>/p' For a quick conflict count without reading files, count the distinct paths in the stage lines.
git merge-tree --write-tree --name-only main feature/payments-v2 | tail -n +2 | sed '/^$/,$d' | wc -l Step 3 β Preview every open branch against main Jump to heading
Because previews have no side effects, a loop over all remote branches is safe and fast. Publish the result where branch owners see it.
#!/bin/sh
# conflict-report.sh β which branches currently conflict with main?
git fetch -q origin
for b in $(git for-each-ref --format='%(refname:short)' refs/remotes/origin/ | grep -vE 'origin/(main|HEAD)$'); do
if paths=$(git merge-tree --write-tree --name-only --no-messages origin/main "$b" 2>/dev/null); then
printf 'clean %s\n' "$b"
else
n=$(printf '%s\n' "$paths" | tail -n +2 | sed '/^$/,$d' | wc -l)
printf 'CONFLICT %-40s %d file(s)\n' "$b" "$n"
fi
done | sort Run it on a schedule or on every push to main. Branch owners learn about conflicts when main changes, not when they next try to merge.
Step 4 β Build the merged result into a testable commit Jump to heading
A clean preview gives a tree. Wrap it in a commit with both branch tips as parents, and you have a commit identical to the merge that would be created β which CI can check out and test.
tree=$(git merge-tree --write-tree origin/main origin/feature/renewals) || { echo "conflicts"; exit 1; }
commit=$(git commit-tree "$tree" -p origin/main -p origin/feature/renewals -m "preview merge")
git push -q origin "$commit:refs/previews/feature-renewals" # a ref CI can build
git checkout -q --detach "$commit" && make test Pushing previews under a dedicated namespace such as refs/previews/ keeps them out of branch lists. Delete old previews periodically, or let a scheduled job overwrite them.
Step 5 β Use an explicit merge base when you need to Jump to heading
merge-tree computes the merge base itself. For special cases β simulating a merge from a different base, or scripting a cherry-pick-like merge β give it explicitly with --merge-base (Git 2.40+).
# Simulate applying feature's changes since <base> onto release/2.4
git merge-tree --write-tree --merge-base="$base" origin/release/2.4 origin/feature/fix This is how some tools implement server-side rebases and cherry-picks without a working tree.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Is the old git merge-tree without --write-tree the same thing? Jump to heading
No. The old mode used a trivial merge algorithm and printed a hard-to-parse diff. It is deprecated; always pass --write-tree.
Does merge-tree respect .gitattributes merge drivers? Jump to heading
Yes. It uses the same merge machinery as git merge, including custom merge drivers configured in .gitattributes, so lockfile drivers and merge=union behave the same in previews.
Can merge-tree replace rerere in previews? Jump to heading
It does not consult the rerere cache, so previews show raw conflicts even where rerere would resolve them. That is usually what you want from a preview β an honest view of the conflict β but it means previews can report conflicts your team resolves automatically.
Related Jump to heading
- Semantic Conflicts & Merge Verification β the parent topic.
- Catching Semantic Conflicts in CI β where preview commits are tested.
- Finding the Merge Base and Why It Matters β the base merge-tree computes for you.
- Detecting Conflict-Prone Files from History β using previews over history to find hotspots.