Git95 min total · 17 parts
Git Internals and Workflows: Branching, Merging, and Rebasing
Part 5 of 17 · ~2 min
Fast-Forward vs. True Merges
Before you choose, one detail about merges that explains why they sometimes leave a trace and sometimes do not.
Theo also branched off a4f1c0e on Monday, but only to fix a typo in the onboarding copy — one commit, and nobody touched main while he did it. When he merges:
git switch main
git merge fix/onboarding-typo
Updating a4f1c0e..8e21b90
Fast-forward
src/onboarding/copy.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
No merge commit. Git noticed that main had not moved since Theo branched, which means his branch's history already contains everything main has — his commit's parent is main's tip. There is nothing to reconcile. So Git slides main's label forward onto his commit and stops. That is a fast-forward merge, and the word is accurate: the branch pointer fast-forwards along a line that already exists.
Your branch cannot do that, because main moved out from under you when Nadia's timezone fix landed. There is no straight line from main's tip to yours, so Git has to build a commit that reaches back into both:
Merge made by the 'ort' strategy.
That is a true merge, and the merge commit is the thing that makes it possible — it is the only kind of commit that can have two parents, and therefore the only way to say "these two lines of development are now one."
You can override the decision in both directions, and both overrides are useful:
git merge --no-ff feature/shift-swap # force a merge commit even when a fast-forward would work
git merge --ff-only feature/shift-swap # refuse to merge at all unless a fast-forward is possible
--no-ff is what shiftplan uses for feature branches, and the reason is practical rather than aesthetic. With a fast-forward, a five-commit feature dissolves into main as five ordinary commits, indistinguishable from anything else. With --no-ff, there is one merge commit saying "shift swapping landed here," and git revert can take the whole feature back out in one move — which becomes extremely relevant in a few chapters.
--ff-only is the opposite instinct: fail loudly rather than quietly create a merge commit. On a diverged branch it refuses outright.
hint: Diverging branches can't be fast-forwarded, you need to either:
hint:
hint: git merge --no-ff
hint:
hint: or:
hint:
hint: git rebase
fatal: Not possible to fast-forward, aborting.
That is a good failure. It is worth setting in CI and in deploy scripts, where a surprise merge commit means something has gone wrong upstream and you would much rather find out now.