How merges really work

Git · Expert · 15 min

When merges behave surprisingly, these internals explain why and give you the tools to control them.

A merge combines three trees: the merge base and the two tips. git merge-base A B shows the base; for each path Git takes whichever side changed it, and where both changed it, merges at line level with a diff3 algorithm. Conflicts are hunks both sides changed differently.

Git's default strategy is ort ("Ostensibly Recursive's Twin"), the default since Git 2.34 and much faster than the older recursive, which recent versions treat as a synonym for ort. Its main jobs beyond the basic three-way merge:

  • Rename detection: matching a deleted path to an added one with similar content, so an edit on one side follows a rename on the other.
  • Criss-cross merges: when two branches have merged each other, there can be several "best" common ancestors. Ort merges those bases into a virtual base first, then merges against it.

Other strategies (-s): octopus merges several branches at once if there are no conflicts; ours records a merge but keeps your tree entirely, discarding the other side; subtree handles a project embedded in a subdirectory.

Don't confuse the `-s ours` strategy with the `-X ours` option. -X ours runs a normal ort merge but resolves conflicting hunks only in your favour; non-conflicting changes from the other side still come in. -X theirs is the mirror, and -X ignore-space-change and -X patience are other options.

rerere ("reuse recorded resolution", rerere.enabled true) remembers how you resolved a conflict and reapplies it the next time the same conflict appears, which is very useful when repeatedly rebasing a long branch.

git merge-tree --write-tree A B performs a merge without touching the working tree or index and prints the result tree and any conflicts. Servers use it to compute "mergeable?" for PRs.

Example

git merge-base main feature                 # 3f9a2c1...
git merge-base --all main feature           # several = criss-cross history

git merge -X ours feature       # conflicts resolved to our side, rest merged
git merge -s ours legacy        # record merge, keep our tree completely

git config --global rerere.enabled true

# Test-merge without touching the worktree (exit status 1 = conflicts)
git merge-tree --write-tree main feature
# 4b0e3c...                     <- resulting tree ID
# CONFLICT (content): Merge conflict in billing.py

Remember

  • ort is the default strategy; it detects renames and handles multiple bases
  • -s ours discards the other side entirely; -X ours only settles conflicts
  • rerere replays conflict resolutions you made before
  • merge-tree --write-tree merges in memory without a checkout

Watch out for

  • Using -s ours when you meant -X ours and silently dropping all of the other branch's changes.

Check yourself

  1. What does git merge -X theirs feature do to non-conflicting changes made on your current branch?
  2. When does a merge have more than one merge base?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: git-scm.com/docs/merge-strategies