Learning Objectives
By the end of this lesson you will be able to:
- Predict which of the two merge types Git will perform
- Explain what a merge commit is and why it has two parents
- Force either behaviour with
--no-ffand--ff-only - Recognise what each choice does to the readability of the history
The Two Outcomes
git merge produces one of two very different results, and which one you get depends entirely on whether the destination branch moved after the split.
Fast-Forward
The destination has not moved. Everything on it is already in the source branch's history.
BEFORE AFTER (git merge feature/auth-test)
A───B ← main A───B───E───F ← main, feature/auth-test
\
E───F ← feature/auth-test
main was sitting at B, and B is already in the feature branch's ancestry. There is nothing to combine — Git just slides the main pointer forward to F.
Updating 8f3c2a1..d4e5f6a
Fast-forward
No new commit is created. The history stays a single straight line, and afterwards there is no record that a branch ever existed.
Merge Commit
The destination did move. Both branches have commits the other does not.
BEFORE AFTER (git merge feature/auth-test)
A───B───C───D ← main A───B───C───D───M ← main
\ \ /
E───F ← feature/auth-test E───F ──╯ ← feature/auth-test
Git cannot slide anything forward, because C and D are not in the feature branch. So it creates a brand new commit M whose job is to record the combination.
Merge made by the 'ort' strategy.
A merge commit has two parents — D and F. That is what makes it a merge commit, and it is the only kind of commit in Git with more than one parent. It is why the graph shows a loop, and why HEAD^1 and HEAD^2 mean different things on it.
Git will also open an editor for the merge message, pre-filled with something like Merge branch 'feature/auth-test'. Accepting the default is fine.
Forcing Either One
git merge --no-ff feature/auth-test
Always create a merge commit, even when a fast-forward was possible. Many teams require this, and the reason is good: the merge commit is a permanent record that this branch existed, what it contained, and when it landed. Fast-forwarding erases that.
git merge --ff-only feature/auth-test
The opposite: merge only if it can fast-forward, otherwise fail and change nothing. This keeps history perfectly linear and is a useful safety check — if it refuses, the destination has moved and you should look at why before proceeding.
Which One Should a Tester Care About?
You are unlikely to be the one setting your team's policy. What matters is being able to read the result.
In a fast-forwarded history, git log --oneline is a flat list. Simple to read, but you cannot tell which commits came from which branch or which ones landed together.
In a history with merge commits, you can. git log --graph shows the shape, each merge commit names the branch it absorbed, and "what changed in this release?" becomes a question with a precise answer.
That second property is the one that earns its keep for QA. When a regression appears, being able to say "these eleven commits arrived together in merge M, from branch feature/auth-test" is a much stronger starting point than a flat list of eleven unrelated-looking commits.
Pro Tip:
git log --merges --onelinelists only merge commits. On a team that uses--no-ff, that is a compact list of every feature that has landed — a genuinely useful release-scope summary.
The One Confusing Case
A fast-forward can happen even when you did work, as long as the destination didn't:
git switch main
git merge feature/auth-test # Fast-forward
People sometimes read "Fast-forward" as "Git skipped something". It didn't. Every commit from the branch is now in main, in full. The only thing that didn't happen is the creation of an extra commit to describe it.
Key Takeaways
- Fast-forward: the destination hasn't moved, so Git slides the pointer forward and creates no commit
- Merge commit: both branches moved, so Git creates a commit with two parents to record the combination
--no-ffalways creates a merge commit;--ff-onlyrefuses anything that isn't a fast-forward- Merge commits preserve which commits arrived together and from which branch — valuable when tracing a regression
git log --merges --onelineis a compact list of everything that has landed- "Fast-forward" never means work was skipped; it means no extra commit was needed