GitNo Fast-Forward (--no-ff)

No Fast-Forward (--no-ff)

git merge --no-ff tells Git to always create a merge commit, even when a fast-forward would have been possible. The result: history clearly records that a branch was merged here, with one commit summarising the whole branch. Many teams (especially those following Gitflow) make this the default.

What --no-ff produces

Without --no-ff (fast-forward)

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

(Linear — you cannot tell a branch ever existed)

With --no-ff (explicit merge commit)

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

(M is the merge commit — clearly marks where feature-x was integrated)
The command

Bash
git switch main
git merge --no-ff feature-x
# (your editor opens for the merge commit message)
Why use --no-ff
  • Preserves the record of a branch. git log --graph shows the diamond shape, so a year later you can still see “this was a feature branch.”

  • Easy revert. Reverting one merge commit undoes the whole feature: git revert -m 1 <merge-sha>.

  • Workflow clarity. In Gitflow especially, the explicit merge commit is part of the model — branches into develop, develop into main, etc.

  • Audit trail. Code review, compliance, or change-management workflows often require a single trackable point of integration.

Why NOT use --no-ff
  • More noise in the log. Every merge adds a commit, regardless of how trivial.

  • git log --oneline becomes harder to scan — half the lines are “Merge branch ...”.

  • Teams that prefer truly linear history (often combined with rebase or squash) skip merge commits entirely.

Configuring --no-ff as default

Always create merge commits

Bash
# For 'git merge'
git config --global merge.ff false

# For 'git pull' (so it also creates merge commits when needed)
git config --global pull.ff false
Only for some branches

Force --no-ff when merging into specific branches

Bash
# Inside the repo:
git config branch.main.mergeOptions --no-ff
git config branch.develop.mergeOptions --no-ff

# Now any merge INTO main or develop forces --no-ff
# Merges into feature branches still allow fast-forward
Writing the merge commit message

A useful merge commit

Text
Merge branch 'feature/oauth-login' into main

Add OAuth login via Google and GitHub.

* New /api/auth/oauth routes
* OAuth state stored in signed cookies
* "Sign in with X" buttons on the login page
* Updates LICENSE to credit oauth-passport

Refs: PROJ-123

Git auto-fills “Merge branch '...' into ...” — but it’s worth taking ten seconds to add a real summary. The merge commit becomes a single-shot description of the feature.

The Gitflow context

Gitflow uses --no-ff everywhere:

  • Feature branches merge into develop with --no-ff.

  • Release branches merge into main (and back into develop) with --no-ff.

  • Hotfixes merge into both main and develop with --no-ff.

The result: git log --graph --all shows a clear braided topology where every feature, release, and hotfix is visible.

Reverting a --no-ff merge

Undo a merge commit safely

Bash
# Find the merge commit
git log --oneline --merges

# Revert it (-m 1 keeps the first parent — usually main)
git revert -m 1 <merge-sha>

# Git creates a new commit that undoes the entire merge
--no-ff vs squash
  • --no-ff — keeps every commit on the branch + adds a merge commit. Full history preserved.

  • --squash — collapses every commit into one. History compressed.

  • Choose --no-ff if you value detailed history and easy reverts. Choose --squash if you value clean log lines and brevity.

The single most important thing
Pick ONE merge style per project and stick with it. Mixing FF, `--no-ff`, and squash on the same branch produces confusing logs.
Tip
If you’re unsure: --no-ff is the safer choice for serious projects. You can always squash later (with git rebase -i), but reconstructing a lost branch history is much harder.