Fast-forward: when merge only moves a pointer
The interactive lesson needs JavaScript. The text below is the complete lesson.
After this lesson you can
- predict from the graph when a merge will be a fast-forward understand
- merge a finished branch into
mainwithgit mergewhile you are onmainapply - choose between a fast-forward and
--no-ff, and explain the difference in the graph evaluate - delete a fully merged branch with
git branch -dapply
Understand: The pointer slides forward
You have worked on feature and made two commits. main has not received anything new in the meantime. It still points to A, where feature started.
Follow the arrows back from C: first B, then A. The commit of main is an ancestor of feature. Everything on main is already in feature.
Git does not need to make anything new. git merge feature just slides main forward to C. That is called a fast-forward. No new commit, and the history is still a straight line.
Now both names point to the same commit. git branch -d feature deletes the name, but not the commits. They are part of the history of main. If the work is not merged, -d refuses and tells you.
Some teams would rather see in the history that a feature was merged. We go back to the starting point: main at A and feature at C.
git merge --no-ff feature now makes a merge commit M with two parents, A and C, even though a fast-forward was possible. main moves to M. It is a choice the team makes, not a requirement.
Watch: A fast-forward in real Git
We are on feature, which has two new commits. main is still at the first one. The graph is a straight line: main sits right behind feature.
$ git log --oneline --graph --all
A merge brings work into the branch you are on. We want main to receive the work, so we switch there first.
$ git switch main
Now we merge feature. The terminal says Fast-forward, and in the graph main slides up to the same commit as feature.
$ git merge feature
feature has done its job. We delete the name. Watch the graph: the label disappears, but the commits stay, because main reaches them.
$ git branch -d feature
Tasks
- Show the graph with all branches.
- Go to the branch that should receive the work.
- Bring the work from
featureintomainwithout making a new commit. - Delete the branch that is now fully merged.
Practise: Transfer: choose the shape of the history
You are on docs. Two branches have one commit each after main: docs is finished, spike is an unfinished experiment.
Tasks
- Your team wants the history of
mainto show where each finished feature was merged in. Bringdocsintomainfollowing that rule. - Tidy up: delete the branches that are fully merged into
main, and leave the rest.
Check yourself: Recall it
The history is a straight line A, B, C, D. main points to B and feature to D. You are on main and run git merge feature. How many new commits does Git make?
- None
- One merge commit
- As many as
featurehas, as copies
You want main to receive the work from feature. Where must you be when you run git merge?
- On
feature - On
main - It does not matter
What disappears when you run git branch -d feature after a fast-forward?
- The commits that were made on
feature - Only the name
feature - The files that were added on
feature
The team wants every feature to show as one merge commit on main. After git merge docs the history is a straight line. What is the right assessment?
- The rule is followed, because the work from
docsis now onmain - The rule is broken: it was a fast-forward.
git merge --no-ff docswould have made the merge commit - The merge failed, so
docsmust be merged again
Review: Before you merge, git status --short shows an M in the second column for README.md. What does that mean?
- README.md is modified and ready in the staging area
- README.md is modified in the working tree, but the change is not in the staging area
- README.md is already merged
Common misconceptions
- “A merge always makes a new commit.” When
mainhas not moved, the pointer just slides forward. - “It does not matter which branch I am on when I merge.” A merge only ever changes the branch HEAD is on.
- “
git branch -ddeletes the work.” It deletes only the name. The commits stay in the history.
Read more
When is a merge a fast-forward?
git merge feature only ever changes the branch you are on. Git first asks one question: is the commit of the current branch an ancestor of feature? If yes, everything on main is already in feature, there is nothing to combine, and Git just moves the branch forward. You can ask the same question yourself:
$ git merge-base --is-ancestor main feature && echo "fast-forward possible"
fast-forward possible
A fast-forward prints the old and new position and a summary of what arrived in your working tree:
$ git merge feature
Updating df6c7db..2379bbb
Fast-forward
login.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 login.txt
No commit was made, and no commit was copied. main now names the same commit as feature.
If it is the other way round and feature is already contained in main, Git says Already up to date. and does nothing.
Three ways to control the shape
| Option | Behaviour |
|---|---|
--ff (default) |
fast-forward when possible, otherwise a merge commit |
--ff-only |
fast-forward or nothing: refuses when the lines have diverged |
--no-ff |
a merge commit even when a fast-forward was possible |
--ff-only: update, never combine
Use it when you only expect to catch up, for example when updating your main from the team's main. If both lines have new commits, Git stops without changing anything and exits with status 128:
$ git merge --ff-only feature
hint: Diverging branches can't be fast-forwarded, you need to either:
...
fatal: Not possible to fast-forward, aborting.
That is not an error: a real integration choice is needed, a three-way merge (next lesson) or a rebase. To make this the default, run git config merge.ff only.
--no-ff: keep the feature visible
$ git merge --no-ff feature
Merge made by the 'ort' strategy.
login.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 login.txt
$ git log --oneline --graph
* 4f3483e Merge branch 'feature'
|\
| * 2379bbb Style login form
| * 27a6985 Add login form
|/
* df6c7db Initial snapshot
Benefits: the commits of one feature stay grouped, git log --first-parent shows each merged feature as a single merge commit, and the whole feature can later be undone with a single git revert -m 1 of the merge commit. Costs: more commits and a busier graph. It is a team decision, not a right or wrong.
Squash merges, briefly
git merge --squash feature puts all changes from feature into the staging area, and you make one ordinary commit with git commit. It has only one parent, so Git does not know feature was merged, and git branch -d feature later refuses. Check with git diff main feature that nothing is missing, then delete with -D.
Before you merge
- Be on the receiving branch.
git branch --show-currentorgit statustells you. A merge on the wrong branch moves the wrong name. - Start clean. If uncommitted changes would be overwritten, Git refuses and changes nothing:
error: Your local changes to the following files would be overwritten by merge:
README.md
Please commit your changes or stash them before you merge.
Aborting
- Know what is coming.
git log --oneline main..featurelists the commits the merge will bring in.
If you merged into the wrong branch, Git has saved the previous position in ORIG_HEAD. The reset lesson shows how to move back to it safely.
Cleaning up merged branches
$ git branch --merged
feature
* main
$ git branch -d feature
Deleted branch feature (was 2379bbb).
git branch --merged lists branches whose commits are all contained in the current branch, so -d can delete them safely. git branch --no-merged lists the ones that still hold unique work, and on those -d refuses:
error: the branch 'spike' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D spike'
git log main..spike lists the work only spike has. Use -D only when you know it is not needed. Deleting a name never deletes commits that another branch still reaches.
Official documentation
- git merge, especially the section on fast-forward merges
- Pro Git: Basic Branching and Merging
- git merge-base, git branch
Per-Arne's Git Good!