Fast-forward: nothing to merge
If the branch you are merging already contains everything on your branch, there is nothing to combine: git moves the pointer forward. No merge commit, no new object. This is the common case for a small branch merged the same afternoon.
main: A ── B
\
feature: C ── D (main is an ancestor of feature)
$ git switch main
$ git merge feature
Updating b303b73..e184a0e
Fast-forward
The merge base
When both branches have moved, git finds the merge base — the most recent commit they share — and compares each side against it. A file only one side changed takes that side. A file both sides changed the same way is already agreed. A file both sides changed differently is a conflict, and that is the next lesson.
git merge-base main feature
The merge commit
Two parents, in order: the branch you were on, then the branch you merged. That order is what HEAD^1 and HEAD^2 mean, and what git revert -m 1 needs if the merge has to come back out.
$ git merge feature
Merge made by the 'ort' strategy.
$ git log --oneline --graph
* 5d9fd3e (HEAD -> main) Merge branch 'feature'
|\
| * e184a0e (feature) Add the fitting notes
* | 9ec51e0 Update the price list
|/
* b303b73 Start the notes
--no-ff, and why teams use it
A merge commit records that these commits were one piece of work, keeps the branch name in the message, and can be reverted in one move. The cost is a busier graph. Most teams that use pull requests get --no-ff behaviour by default, which is why main looks like a ladder of merges.
git merge --no-ff feature
Merge or rebase?
A reasonable default: rebase your own branch onto the latest main while you work, then merge it in. The next lesson but one is rebase in full.
| Merge | Rebase | |
|---|---|---|
| History | true: it records what happened | tidy: as if you had started later |
| Commit ids | unchanged | all new |
| Shared branches | always safe | never do it |
| Conflicts | once, at the end | possibly once per commit |