Fast-Forward Merge
In short
A fast-forward merge happens when the target has no new commits since the other branch split off, so Git just moves its pointer ahead with no merge commit.
What is a fast-forward merge?
Suppose you create a feature branch from main, make three commits, and nobody adds anything to main in the meantime. History is a straight line: main is just behind feature. Merging feature into main doesn't need to combine anything, so Git moves the main pointer to the last feature commit. That is a fast-forward, and the result is linear history with no extra merge commit.
If main has moved on since the branch was created, the two lines have diverged and a fast-forward is impossible. Git then either creates a merge commit with two parents, or you rebase the branch onto the new main first, which makes it fast-forwardable again. git pull faces the same choice when your local branch and the remote branch have both changed.
Teams choose policies with flags. git merge --ff-only refuses to merge unless it can fast-forward, keeping history strictly linear. git merge --no-ff always creates a merge commit, even when a fast-forward is possible, so each feature appears as a visible group of commits. Hosting platforms offer similar choices, such as GitHub's merge, squash and rebase buttons.
A common misconception is that a fast-forward merge loses information. No commits are changed or removed; the branch pointer just moves. What disappears is the record that the commits were developed on a separate branch, which is why some teams prefer merge commits for features and fast-forwards for small updates.
Key takeaways
- A fast-forward moves the branch pointer instead of creating a merge commit.
- It is only possible when the target branch hasn't diverged.
- Rebasing a branch onto the latest main makes it fast-forwardable.
- --ff-only enforces linear history; --no-ff always records a merge commit.
- No commits are lost, only the visible grouping of a feature branch.
Example
git switch main
git merge feature
# Updating 41d0e77..9f2c1ab
# Fast-forward ← main simply moved to feature's last commit
git merge --ff-only hotfix # merge only if it can fast-forward, otherwise stop
git merge --no-ff feature # always create a merge commit for the feature
git config --global pull.ff only # make 'git pull' refuse non-fast-forward mergesReaders ask
When does Git do a fast-forward merge?
When the branch being merged into has no commits that the other branch lacks, meaning history hasn't diverged. Git then moves the pointer forward by default instead of creating a merge commit.
What does --no-ff do?
It forces Git to create a merge commit even when a fast-forward would be possible, so the feature's commits stay grouped under one merge in the history.
Fast-forward or merge commit: which is better?
It is a team preference. Fast-forwards and rebases give a clean, linear history that is easy to read and bisect. Merge commits preserve when and how features were integrated. Many teams squash or rebase small changes and use merge commits for larger features.
See also
- MergeVersion Control, p. 29A merge in Git combines the changes from one branch into another, joining separate lines of development back together into a single, shared history.
- RebaseVersion Control, p. 33A rebase in Git replays a branch's commits on top of another commit, rewriting history to produce a cleaner, linear sequence of changes without merge commits.
- BranchVersion Control, p. 2A branch in Git is an independent line of development that lets you work on a feature or fix in isolation, without affecting the main code until you merge it.
- Squash MergeVersion Control, p. 36A squash merge combines all the commits from a branch into one new commit on the target branch, keeping the main history short and easy to read.
- Git PullVersion Control, p. 17Git pull is a Git command that downloads new commits from a remote repository and immediately integrates them into your current local branch.
- Trunk-Based DevelopmentVersion Control, p. 38Trunk-based development is a branching strategy where developers merge small changes into one shared main branch at least daily, keeping it always releasable.
Spotted a mistake or something missing on this page?Suggest an edit