GitFast-Forward Merge

Fast-Forward Merge

A fast-forward merge is the simplest kind. Git just slides the branch pointer forward — no merge commit is created and history stays linear. Fast-forwards happen when the destination branch has not moved since you branched off, so Git has nothing to actually merge — it can just advance the pointer.

The setup

Before merge

Text
        A───B───C        ◀── main (HEAD)
                 \
                  D───E    ◀── feature-x

main is at C. feature-x is at E. Crucially, feature-x contains every commit on main plus two more (D, E).

The merge

Bash
git switch main
git merge feature-x
# Updating c204c1d..1f9ab2c
# Fast-forward
#  src/login.js | 5 +++--
#  1 file changed, 3 insertions(+), 2 deletions(-)
The result

After merge

Text
        A───B───C───D───E    ◀── main, feature-x

Both branches now point at E. The history is straight. There is no merge commit because Git did not have to create one — feature-x already contained main’s history in its entirety.

Why it’s called fast-forward

Think of main as a movie playing on a tape. To merge in feature-x Git just hits fast-forward on the tape until main is in the same place as feature-x. There is no editing, no combining, no new content — just advancing a pointer.

When fast-forward is possible
  • You branched off, made commits, and the destination branch did not move while you worked.

  • No one else committed to main since you branched off.

  • You never merged anything into main from another source.

Solo work and small teams often see lots of fast-forwards. Larger teams see fewer, because main moves more often.

When fast-forward is NOT possible

The destination moved while you worked

Text
        A───B───C───F   ◀── main (HEAD)
                 \
                  D───E       ◀── feature-x

main has commit F that feature-x does not. To combine them Git must create a merge commit (or you must rebase first). FF is impossible.

Refusing fast-forward (--no-ff)

Force a merge commit

Bash
git merge --no-ff feature-x

Result with --no-ff

Text
        A───B───C───────M   ◀── main
                 \         /
                  D───E───┘    ◀── feature-x

Even though FF was possible, Git created a merge commit. This is what Gitflow uses: history clearly shows “a feature branch was integrated here”.

Requiring fast-forward (--ff-only)

Refuse anything else

Bash
git merge --ff-only feature-x
# If FF is not possible:
# fatal: Not possible to fast-forward, aborting.

Useful for keeping history strictly linear. If FF fails, you rebase feature-x onto main first, then try again.

Setting a global default

Make 'git pull' refuse merges

Bash
git config --global pull.ff only
# 'git pull' will only fast-forward; if it can't, it errors out
# Forcing you to explicitly merge or rebase
FF vs FF-with-merge in pull requests

GitHub, GitLab, and Bitbucket all let you pick the merge method for PRs:

  • Create a merge commit — equivalent to --no-ff. Linear-feature visible in history.

  • Squash and merge — combines all PR commits into one, then FFs.

  • Rebase and merge — rebases PR commits onto target, then FFs. No merge commit, linear history.

  • Fast-forward — only allowed if no divergence. Some platforms expose this directly.

Pros and cons
  • ✅ Linear history is easy to read with git log.

  • ✅ No “noise” merge commits cluttering the log.

  • ❌ You lose the record of which work was on a branch. If your team values that information, use --no-ff.

  • ❌ Reverting a whole feature is slightly harder than reverting a single merge commit.

Most modern teams pick one rule
Either “always FF for clean linear history” or “always --no-ff for explicit feature integration”. Mixing styles produces confusing logs.
Tip
Right before merging, run git log --oneline --graph --all to see the topology. If your branch already includes main, an FF will happen automatically.