Merging
Trace commits and branches, choose an appropriate undo operation, and resolve a small merge conflict.
THE IDEA
Two ways a merge can end
git merge feature brings the commits of feature into the branch you are on. If your branch has not moved since feature split off, nothing needs combining: Git slides your branch label forward. That is a fast-forward. If both branches have new commits, Git builds a new merge commit that has two parents.
You are on main. Choose a situation, then merge feature.
Press a button. The commands run on a real (simulated) repository.
git merge feature # fast-forward if possible
git merge --no-ff feature # always record a merge commit
git merge --ff-only feature # refuse unless it can fast-forward
git log --oneline --graph --allWhy the common ancestor matters
Git finds the merge base, the newest commit both branches share, and compares each branch with it. A line changed on one side only is taken. A line changed identically on both sides is taken once. A line changed differently on both sides is a conflict (next lesson). Without the base Git could only see two versions that differ, not who changed what.
Common ancestor (read only)
title: Menu price: 5 size: small note: fresh
Your branch (edit me)
Their branch (edit me)
Merge result: clean
title: Menu price: 5 size: small note: fresh
Try: change only the price on one side (clean). Change the price on both sides to different values (conflict). Change different lines (clean). Change the same line to the same value (clean).
Predict first. Both branches change line 1 of a file to the identical text. What does a merge do?
Chapter context
Git records snapshots in a graph of commits. Branch names are movable references to commits. Treat the working tree, staging area and recorded history as distinct states, and many apparently mysterious commands become predictable.
TRY IT YOURSELF
You are on main. Two branches, docs and theme, each hold one extra commit. Merge them in a sensible order and watch the shape change.
- Look at the shape of history across all branches before merging anything
- Merge docs into main. It is a fast-forward: main just moves, no new commit
- Merge theme into main. Now the histories have diverged, so Git must create a merge commit with two parents
- Create a branch footer from main and commit a file footer.txt on it, then return to main
- Main could fast-forward to footer, but merge it so that a merge commit still records the branch
Commit graph (live)
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Why does a three-way merge need a common ancestor?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
Model history as a directed acyclic graph. A commit is reachable from a branch when following parent edges reaches it. A merge preserves both ancestry paths; rebasing creates a different path with new commit identities.
Worked example
Let B have parent A, C have parent B, and D have parent B. A merge M with parents C and D makes both C and D reachable. Moving a branch from C to M is different from copying D's files over C: the graph also records why those histories meet.
Reference code
The following example uses the Python standard library and does not call a remote service.
parents = {'A': [], 'B': ['A'], 'C': ['B'], 'D': ['B'], 'M': ['C', 'D']}
def ancestors(commit):
seen, todo = set(), [commit]
while todo:
node = todo.pop()
if node not in seen:
seen.add(node)
todo.extend(parents[node])
return seen
assert ancestors('M') == {'A', 'B', 'C', 'D', 'M'}
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
Git commit does not automatically upload data. Rebase changes commit identities. Reset modes have different effects on uncommitted work.