When git merge feat runs on branch main, Git first checks: can it
simply fast-forward main to the tip of feat?
When it is possible
When main has received no new commits since the branch was created:
before merge:
main: A → B
\
feat: C → D
git switch main && git merge feat:
after:
main: A → B → C → D ← main caught up with feat
feat: A → B → C → D
No merge commit is created. Git simply rewrites
.git/refs/heads/main from the old SHA to the SHA of commit D.
When it is not possible
When main has at least one commit of its own since the branch was created:
main: A → B → E
\
feat: C → D
Here fast-forward is not possible. A three-way merge with a merge commit is required (see merge).
Controlling the behavior
git merge feat # fast-forward if possible, otherwise three-way
git merge --no-ff feat # always create a merge commit,
# even when fast-forward is possible
git merge --ff-only feat # fast-forward only; refuse if not possible
--no-ffis the strategy teams use when they want to preserve topology: "there was a feature branch here." Useful for GitFlow.--ff-onlyguards against accidental merge commits. It works well as the default forpull:git config --global pull.ff only.
Pitfalls
- fast-forward creates no extra commit, so
git log --oneline --graphdoes not show that a branch existed. If the fact of branching matters for audit purposes, use--no-ff. - With
--ff-only, if the branches have diverged Git refuses to proceed. You need to explicitly merge or rebase rather than relying on automatic behavior. - The default for
pullhas historically been different. To avoid getting unexpected merge commits, setpull.ff = onlyorpull.rebase = true.