Why a Merge Only Stops to Ask About Some of Your Changes
Last timeThe Three Places a File Lives
A merge reads three versions of every file, not two, and the third one is the commit both lines came from. That reading decides what it can apply for you.
What a join is actually comparing
The repository worked here holds three files, readme.md, settings.ini and
render.py. Its main line has seven commits. A side line, named proofs, has
three, and it starts from the fifth commit on the main line, so the two lines
have been apart for a while and both of them have edited settings.ini.
Every name quoted below is the first seven characters of a real one, and anyone
who builds the same three files with the same commit messages will read the
same names out, because a name here is computed from the contents it names.
That claim is only worth making if you can check it, so here is the script
that produced the part of the history this lesson and the next one argue over.
cd plotter
export GIT_AUTHOR_NAME='A Writer' GIT_AUTHOR_EMAIL='[email protected]'
export GIT_COMMITTER_NAME='A Writer' GIT_COMMITTER_EMAIL='[email protected]'
git checkout -q main
export GIT_AUTHOR_DATE='2026-02-02T09:00:00 +0000'
export GIT_COMMITTER_DATE='2026-02-02T09:00:00 +0000'
printf '[page]\nwidth = 800\nheight = 480\n\n[render]\nquality = draft\n' > settings.ini
git add -A && git commit -q -m 'Add a render section'
export GIT_AUTHOR_DATE='2026-02-03T09:00:00 +0000'
export GIT_COMMITTER_DATE='2026-02-03T09:00:00 +0000'
printf '[page]\nwidth = 800\nheight = 600\n\n[render]\nquality = draft\n' > settings.ini
git add -A && git commit -q -m 'Raise the page height to 600'
export GIT_AUTHOR_DATE='2026-02-04T09:00:00 +0000'
export GIT_COMMITTER_DATE='2026-02-04T09:00:00 +0000'
printf 'def render(page):\n if page.blank:\n return page\n frame(page)\n return page\n' > render.py
git add -A && git commit -q -m 'Leave a blank page unframed'
git checkout -q -b proofs main~2
export GIT_AUTHOR_DATE='2026-02-06T09:00:00 +0000'
export GIT_COMMITTER_DATE='2026-02-06T09:00:00 +0000'
printf '[page]\nwidth = 800\nheight = 900\n\n[render]\nquality = draft\n' > settings.ini
git add -A && git commit -q -m 'Raise the page height to 900'
export GIT_AUTHOR_DATE='2026-02-07T09:00:00 +0000'
export GIT_COMMITTER_DATE='2026-02-07T09:00:00 +0000'
printf '[page]\nwidth = 800\nheight = 900\n\n[render]\nquality = proof\n' > settings.ini
git add -A && git commit -q -m 'Set the render quality to proof'
export GIT_AUTHOR_DATE='2026-02-08T09:00:00 +0000'
export GIT_COMMITTER_DATE='2026-02-08T09:00:00 +0000'
printf '# Plotter\n\nA small renderer.\n\nSettings live in settings.ini.\n\nProofs use a taller page.\n' > readme.md
git add -A && git commit -q -m 'Point the readme at the proof settings'
git checkout -q mainMain's tip, fbdccf5, holds one version of settings.ini. The side's tip,
3f7bf69, holds another. Compare just those two and all you learn is that they
differ. You cannot tell whether main changed the file, or the side changed it,
or both did, and those three situations want three different answers: take the
side's, take main's, and ask a person.
So the missing reading is the version both lines started from. The commit both
tips descend from is the merge base, and asking for it in this repository
reports ea21ea3, the fifth commit on the main line. A join compares three
versions of everything, which is why it is called a three way merge, and the
third version is the entire reason it can decide anything for itself.
git merge-base main proofs # ea21ea3, the fifth commit on the main line
git merge proofs # CONFLICT content: Merge conflict in settings.ini
git diff --name-only --diff-filter=U # settings.ini, and nothing else
git ls-files -u settings.ini # stage 1 ca72d6e, stage 2 721142e, stage 3 b80b111
git status --short # M readme.md, UU settings.ini, render.py not listedWhat equality with the ancestor decides
Write for the version held in the merge base, for the version at
main's tip, and for the version at the side's tip. For any region of any
file the result follows from comparisons alone.
If the side never touched this region, so the only change is main's and
. If the reverse, so . If both sides made the
same change, there is nothing to choose between them, and . Otherwise
both sides moved away from the ancestor and moved somewhere different, and
every possible answer silently discards somebody's work, so the merge stops and
reports a conflict.
The lesson stops here
5 more paragraphs to go
You have read the opening. The rest of the argument, the problems that check whether it landed, and the lines worth keeping at the end all come with a plan.
The first lesson of every course in the library reads the whole way through, free, so you can see exactly what the rest of them are.
See the planThe contentsThis is the reading half
Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.
The contents