Great work!

XP to next level

BugEater

Fast-Forward vs. Merge Commit

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-ff and --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 parentsD 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 --oneline lists 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-ff always creates a merge commit; --ff-only refuses 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 --oneline is a compact list of everything that has landed
  • "Fast-forward" never means work was skipped; it means no extra commit was needed

Quiz

When does Git perform a fast-forward merge?

What makes a merge commit different from every other commit?

Your team requires git merge --no-ff for every feature. What does that buy them?

git merge --ff-only feature/x fails. What has it told you?