EduFlow
Git & GitHub
0%

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.

  1. 1The rule of direction
  2. 2Case 1: fast-forward
  3. 3Case 2: a true merge
  4. 4Choosing on purpose
  5. 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.

Terminal
git switch main
git merge footer

Case 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:

Text
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.

Terminal
git switch main
git merge dark-mode

Git opens your editor with the message Merge branch 'dark-mode'. Save and close, and you’ll see:

Text
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.

Top: main points at b2e0 and footer is two commits ahead on the same line, so merging slides main forward to f1a3 with no new commit. Bottom: main and dark-mode each have a new commit after b2e0, so merging creates merge commit m7c1 with two parents, and main moves to it. Fast-forward: main hasn't moved b2e0 e9d2 f1a3 footer main (after) main (before) was here, on b2e0 Merge commit: both branches moved b2e0 c39b d4a7 m7c1 main dark-mode m7c1 has two parents: c39b (main) and d4a7
Fast-forward versus merge commit

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:

Terminal
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 commit

My 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:

Terminal
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 reverse

If 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:

Terminal
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-ff forces a merge commit to keep a feature grouped; --ff-only refuses anything but a fast-forward.

Further reading

Check your understanding

4 questions

Question 1 of 4

You’re on main. Since you created footer from it, main has gained no commits and footer has three. What does git merge footer do?

Question 2 of 4

You meant to bring dark-mode into main, but you were on dark-mode and ran git merge main. What happened?

Question 3 of 4

Which commit does Git use as the starting point when it does a three-way merge of dark-mode into main?

Question 4 of 4

Your team wants every feature to appear in main’s history as one merge commit, even when a fast-forward would be possible. Which option gives that?

Practice

Choose wisely

Predict the merge

This is the portfolio right now. Arrows point from each commit to its parent.

:::figure Before the merge

Commit a1 then b2. From b2, main continues to c3, which main and HEAD point to. Also from b2, the projects-grid branch has d4 then e5, and projects-grid points to e5. a1 b2 c3 d4 e5 main HEAD projects-grid :::

The two branches touched different files. You run git merge projects-grid. What does the graph look like afterwards?

Predict the merge