Bringing Two Histories Back Together
Merging has a direction, and half of all merge confusion is standing on the wrong branch when you run it. See what a fast-forward really is, and why a three-way merge needs a third commit nobody mentions.
Worth reading first: A Branch Is a Sticky Note on One Commit
Merging has a direction, and half of all merge confusion is standing on the wrong branch when you run it. See what a fast-forward really is, and why a three-way merge needs a third commit nobody mentions.
Merging has a direction, and you stand on the target
git merge Xmeans "bring X into where I am standing". It never means the reverse, and it never touches X.
$ git switch main # stand on the branch you want to CHANGE$ git merge fix/login # bring the other branch into itAfter that, main contains the work from fix/login and fix/login is exactly where it was. It has not moved, it has not gained anything, and it can be deleted or carried on with.
Getting this backwards is the most common merge mistake there is. Running git merge mainwhile standing on fix/login is a completely legitimate and often useful command — it brings main's recent work into your branch so you can catch up. It just is not what somebody means when they say "merge my branch".
git merge fix/login — publishing your finished work. main changes.
git merge main — catching up with everyone else. Your branch changes.
git status prints the branch you are on. Read it before merging, every time.
A fast-forward is not really a merge
If main has not moved since your branch left it, there is nothing to combine. Your branch is simply main plus some commits. Git says so and slides the label forward.
A ── B ── C ── D C and D are on fix/login ↑ ↑ main fix/loginA ── B ── C ── D ↑ main, fix/login Updating 7d1f6b4..4b8e0d7 Fast-forwardNo commit was created. main was moved. That is all a fast-forward is, and it is why the history afterwards is perfectly linear with no evidence a branch ever existed.
Whether that is good depends on your taste. Some teams want a clean straight line; others want the record that these three commits were one piece of work reviewed together. Git lets you insist on the record:
$ git merge --no-ff fix/login # always create a merge commit, even when a fast-forward is possible$ git merge --ff-only fix/login # refuse to merge unless it can fast-forward--ff-only is more useful than it looks. It fails loudly when your branch has fallen behind, rather than quietly producing a merge commit you did not intend, which makes it a good default for pulling.
A three-way merge invents a new commit
When both branches have moved, there is no label to slide. Git has to build something.
C ── D fix/login ╱A ── B ── E main C ── D ╱ ╲A ── B ── E ─────── M main ↑ the merge commit — two parents, E and DM is a merge commit, and it is the only kind of commit with two parents. It contains the combined result, it records that these two lines of development joined here, and Git writes its message for you.
$ git merge fix/login# Merge made by the 'ort' strategy.# src/auth/redirect.ts | 12 +++++++++---# 1 file changed, 9 insertions(+), 3 deletions(-)The merge base decides what counts as a change
"Three-way" names the three commits Git compares, and the third one is the piece nobody explains.
- 1
The merge base — commit B
The most recent commit both branches share. Git finds it automatically; git merge-base main fix/login prints it.
- 2
Yours — commit E
The tip of the branch you are standing on.
- 3
Theirs — commit D
The tip of the branch you are merging in.
Git compares the base to each side. A line that changed on one side and not the other is taken from the side that changed it — no ambiguity, no question asked. A line that changed on both sides is a conflict, which is the next chapter.
This is why merging is not a naive line-by-line comparison of two files. Without the base, Git would have no way to tell a line somebody added from a line somebody deleted, and every merge would be a conflict.
$ git merge-base main fix/login# 91b2d4f0a7c3e5b8d1f4a6c9e2b5d8f1a4c7e0b3 # What each side did since the base$ git log --oneline main..fix/login # their commits$ git log --oneline fix/login..main # yours--no-ff keeps the shape of the work
Everything so far, as a single decision: what should main's history look like after a branch lands?
Fast-forward where possible
A straight line. Simple to read, easy to bisect, and it loses the information that three commits were one feature. Suits small teams and short branches.
--no-ff always
Every branch leaves a merge commit. The history shows what was developed together and can be reverted as a unit. Suits teams that review by pull request — which is why GitHub's "Create a merge commit" button does exactly this.
Two more things before the next chapter. If a merge starts and you decide against it:
$ git merge --abort # during a conflicted merge, put everything back$ git merge --no-commit … # merge but stop before committing, so you can inspect firstAnd once a branch is merged, delete it. The commits are safely in main; the label is forty-one bytes of noise in everybody's branch list.
$ git branch --merged # branches whose work is already in the current branch$ git branch -d fix/login # safe delete: refuses if the work is NOT mergedKey takeaways
- git merge X brings X into where you are standing. X is never modified.
- Stand on main and merge the branch to publish work; stand on the branch and merge main to catch up.
- A fast-forward happens when the target has not moved: no commit is created, the label slides.
- --no-ff forces a merge commit anyway; --ff-only refuses to merge unless it can fast-forward.
- A three-way merge builds a merge commit — the only commit with two parents.
- The merge base is the most recent shared commit, and it is what lets Git tell an addition from a deletion.
- A line changed on one side is taken silently. A line changed on both sides is a conflict.
- git merge --abort undoes a merge in progress completely.
- Delete merged branches; git branch -d refuses if the work is not actually merged.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.