Finding the merge base and why it matters Jump to heading
Every three-way merge compares three snapshots: ours, theirs, and the merge base — the most recent commit both branches share. Git does not diff ours against theirs. It diffs each against the base, and combines the two sets of changes. That makes the base the single most important input to a merge, and the one people look at least. A base further back than you expect makes Git think both sides changed things they did not, producing conflicts nobody can explain. A base that includes a reverted change makes Git believe one side deliberately removed code. This page shows how to find the base, how to read a merge through it, and how to recognise when it is the source of a strange result. It belongs to 3-way merge fundamentals.
When to use this approach Jump to heading
- A merge produced conflicts in code you are sure only one side touched.
- A merge succeeded cleanly but silently undid or re-applied a change.
- You are about to merge a long-lived branch and want to predict the result.
- You are scripting merges or CI previews and need the base explicitly — as in previewing merges with git merge-tree.
Step 1 — Find the base Jump to heading
git merge-base prints the best common ancestor of two commits. With --all it prints every candidate, which matters when there is more than one.
git merge-base main feature/search
git merge-base --all main feature/search # more than one line = criss-cross history
git log -1 --format='%h %ad %s' --date=short "$(git merge-base main feature/search)" Step 2 — See the merge as Git sees it: two diffs from the base Jump to heading
Before merging, look at both diffs from the base. The three-dot notation does exactly that: main...feature means “changes on feature since the merge base”.
git diff main...feature/search --stat # what the feature changed since the base
git diff feature/search...main --stat # what main changed since the base
# Paths changed on both sides are the only possible conflict sites
git diff --name-only main...feature/search | sort > /tmp/theirs.txt
git diff --name-only feature/search...main | sort > /tmp/ours.txt
comm -12 /tmp/ours.txt /tmp/theirs.txt The last command lists the only files that can conflict. If a file is not in that list and you still see a conflict, the base is not what you think.
# Verification: the predicted conflict set matches the real one
git merge --no-commit --no-ff feature/search; git diff --name-only --diff-filter=U; git merge --abort Step 3 — Recognise a base that is older than you expected Jump to heading
A base far in the past means both sides have drifted a long way, and many files changed on both. This happens when a branch was created from an old commit, or when a previous merge between the branches was undone by a rebase, removing the newer shared commit.
# How far back is the base, in commits and in days?
base=$(git merge-base main feature/search)
git rev-list --count "$base..main"
git log -1 --format='%cr' "$base" The fix is usually to bring the branch up to date with a merge or rebase from main, which moves the base forward. For long-lived branches that is routine maintenance, described in keeping a long-lived branch in sync with main.
Step 4 — Understand reverts through the base Jump to heading
The base explains the most confusing merge behaviour in Git: merging a branch again after reverting its earlier merge. Suppose feature was merged into main, the merge was reverted, and later feature gets more commits and is merged again. The base is now the commit feature was at when first merged, so Git considers those original changes “already in both” — and the revert on main is a change only main made. The result keeps the revert, and the original changes stay missing.
# To bring the original changes back, revert the revert before merging again
git revert <sha-of-the-revert-commit>
git merge feature The full procedure is in undoing a bad merge on a shared branch.
Step 5 — Merge against a different base when you must Jump to heading
Occasionally you know a better base than the one Git picks — for instance, when cherry-picked commits make Git’s chosen base misleading. git merge-tree and git merge-file accept an explicit base, so you can compute a merge result against it and compare.
# Three-way merge of a single file against an explicit base
git show "$CHOSEN_BASE:src/search.py" > /tmp/base.py
git show main:src/search.py > /tmp/ours.py
git show feature/search:src/search.py > /tmp/theirs.py
git merge-file -p --zdiff3 /tmp/ours.py /tmp/base.py /tmp/theirs.py > /tmp/result.py
echo "conflicts: $?" Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Is the merge base the same as where I created the branch? Jump to heading
Only until the branches are merged with each other. After you merge main into your branch, the base moves to that point of main. That is why merging main in regularly makes later merges smaller.
What does git diff A...B mean exactly? Jump to heading
It means the diff from the merge base of A and B to B. It shows what B changed since the branches diverged, ignoring anything A did, which is what reviewers and merge tools want.
Why does Git sometimes choose a base I did not expect? Jump to heading
Because “best common ancestor” is defined by the graph, not by dates or branch names. Merges between branches, cherry-picks and rebases all change the graph, and Git picks the ancestor nearest to both tips.
Related Jump to heading
- 3-Way Merge Fundamentals — the parent topic.
- Resolving Criss-Cross Merges — what happens when there is more than one base.
- Reading diff3 and zdiff3 Conflict Markers — seeing the base inside every conflict.
- Choosing Between the ORT and Recursive Strategies — how the strategy uses the base.