C.W.K.
Stream
Lesson 03 of 05 · published

Merge Shapes: Fast-Forward and Three-Way

~22 min · merge, graph

Level 0Untracked Rookie
0 XP0/47 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

Two merge shapes, one mental model

Every git merge ends in one of two shapes. Fast-forward: the target branch has not advanced since the source branched off, so Git just slides the target pointer forward to the source's tip. No new commit. Three-way (true) merge: both branches advanced, so Git creates a new commit with two parents — yours and theirs — joining the histories at a Y. The graph itself tells you which happened: a straight line versus a diamond.

The fast-forward case is what you usually get when you finish a small feature on a branch that nobody else touched. git merge feature with main unchanged just moves main's pointer. The downside is that the branch's existence is invisible afterward — there is no signal in history that this was a feature branch, just a longer linear chain. Some teams force a merge commit even when fast-forward would work, using --no-ff, to preserve the "this was a unit of work" record.

The three-way merge is what you get when both sides moved. Git uses the most recent common ancestor as a base, computes diffs from base to each side, and combines them. If the diffs touch different lines, the merge is automatic. If they touch the same lines, you get a conflict — that is the next lesson. The merge commit's message defaults to "Merge branch 'feature' into main" — replace that with something meaningful when the merge is significant.

Two flags worth knowing. git merge --ff-only refuses to merge if a fast-forward is impossible, useful in scripts that should fail loudly rather than create a merge commit. git merge --squash takes all the changes from the source branch and stages them as a single commit on top of the target — useful when the branch's individual commits are noisy and you want one clean commit on main.

Code

Fast-forward: pointer slides·bash
# main: A - B - C
# feature:        \- D - E   (branched at C)
# main has not moved since branching.

git switch main
git merge feature
# Result (fast-forward):
# main: A - B - C - D - E

# Inspect the merge result:
git log --oneline --graph -n 5
Three-way and squash variants·bash
# Both branches advanced:
# main:    A - B - C - F
# feature:        \- D - E

git switch main
git merge feature                  # creates merge commit G with parents F + E
git merge --no-ff feature          # force a merge commit even when FF possible
git merge --ff-only feature        # refuse if FF impossible (script-friendly)
git merge --squash feature         # stage all of feature's changes as one commit
git commit -m "Squashed feature"   # then commit the staged changes

# Inspect the result:
git log --oneline --graph --all -n 10

External links

Exercise

Set up a scratch repo with the fast-forward scenario: branch feature from main, add two commits, do not commit anything on main, then git merge feature from main. Run git log --oneline --graph and confirm a straight line. Reset (git reset --hard to before the merge), this time add a commit on main first, then merge. Confirm the diamond shape. Run git merge --no-ff feature on the FF case and observe the difference.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.