Section 2 · Branching and Merging
Merging: Fast-Forward and Merge Commits
15 min3 min readChoose wisely
Lesson at a glance
Merge a finished branch into main, predict whether Git will fast-forward or create a merge commit, and choose between the two on purpose with --no-ff and --ff-only.
- 1The rule of direction
- 2Case 1: fast-forward
- 3Case 2: a true merge
- 4Choosing on purpose
- 5After the merge
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
You’ve got a finished footer branch and a main branch that doesn’t have it yet. Merging is how work on one line of history joins another. Git has two ways to do it, and which one happens isn’t random: you can predict it by looking at the graph before you type anything.
The rule of direction
First, the rule that saves the most confusion: you merge another branch into the branch you’re on. git merge footer means “bring footer’s work into my current branch.” Only the current branch moves. The branch you named is left exactly where it was.
So the routine is always: switch to the branch that should receive the work, then merge.
git switch main
git merge footerCase 1: fast-forward
Since you made footer, nobody committed on main. Every commit on main is already in footer’s history. Git doesn’t need to combine anything; it slides main forward to footer’s latest commit:
Updating c39be11..f1a3b07
Fast-forward
index.html | 8 ++++++++
styles.css | 12 ++++++++++++
2 files changed, 20 insertions(+)That’s a fast-forward: no new commit, just a pointer moving along existing commits. History stays a straight line, as if you’d worked on main all along.
Case 2: a true merge
Now the more interesting case. While you built dark-mode, you also fixed a typo directly on main. Both branches have commits the other lacks; they’ve diverged. There’s no straight line to slide along.
Git does a three-way merge. It finds the merge base, the most recent commit both branches share, and compares each branch tip with it. A change made on only one side is taken. Changes on both sides in different places are both taken. If both sides changed the same lines, Git stops and asks you; that’s a conflict, and it gets the whole next lesson.
git switch main
git merge dark-modeGit opens your editor with the message Merge branch 'dark-mode'. Save and close, and you’ll see:
Merge made by the 'ort' strategy.
styles.css | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)ort is the name of Git’s default merge algorithm; you don’t need to choose it. The result is a merge commit: a commit with two parents, the previous main tip and the dark-mode tip. Its snapshot contains both lines of work.
Choosing on purpose
Both results contain the same final files. What differs is how history reads, and teams have opinions.
- Fast-forward keeps history linear and quiet. Good for small changes, but after the fact you can’t see which commits belonged to which feature.
- Merge commits keep each feature visibly grouped and record when it landed. The graph gets busier.
Two flags let you pick instead of letting the graph decide:
git merge --no-ff footer # always create a merge commit, even if fast-forward is possible
git merge --ff-only footer # fast-forward or refuse; never create a merge commitMy default for a solo portfolio: let Git fast-forward small branches, and don’t agonise over it. On a team, follow the team’s convention, which is usually set on GitHub rather than typed by hand. What matters far more than the policy is that each branch carries one coherent piece of work, so whichever shape history takes, every step in it makes sense.
--ff-only is a good safety habit when you only expect to catch up, for example updating your local main. If Git refuses, that’s news: something diverged that you didn’t know about. On GitHub you’ll meet the same choice as merge methods on a pull request in section 3.
You can check the merge base yourself before merging, which tells you how far the branches have drifted:
git merge-base main dark-mode
git log --oneline main..dark-mode # commits on dark-mode that main lacks
git log --oneline dark-mode..main # and the reverseIf the second log is empty, main hasn’t moved and the merge will fast-forward.
After the merge
Once footer is merged, the branch label has done its job. Delete it:
git branch -d footer-d succeeds because the commits are reachable from main. The commits themselves stay in history forever; only the sticky note goes. Clean up merged branches as you go, and git branch stays a short list of work that’s actually in progress.
So far every merge went smoothly because the two branches touched different lines. Next you’ll make them collide on purpose and resolve the conflict step by step.
Key takeaways
- You always merge another branch into the branch you’re on; only the current branch moves.
- If the current branch hasn’t moved since the other branch started, Git fast-forwards the pointer and creates no new commit.
- If both branches have new commits, Git does a three-way merge from their common ancestor and records a merge commit with two parents.
--no-ffforces a merge commit to keep a feature grouped;--ff-onlyrefuses anything but a fast-forward.