Concept · Merging & history

Fast-forward vs merge commits

If the target hasn’t moved, Git just slides the pointer; otherwise it records a commit with two parents.

3 min--ff-only--no-ffmerge commit

Merging feature into main can happen in two ways, depending on whether main has moved since feature branched off.

Fast-forward

If main hasn't changed, feature is simply main plus some commits. No real merge is needed. Git moves the main pointer forward to feature's tip:

text
before:  A ─ B            ← main
               └ C ─ D    ← feature

after:   A ─ B ─ C ─ D    ← main, feature

No new commit is created, and the history stays a straight line. Afterwards there's no record that C and D were ever on a separate branch.

Merge commit (true merge)

If both branches have new commits, Git does a three-way merge and records a merge commit with two parents:

text
before:  A ─ B ─ E        ← main
               └ C ─ D    ← feature

after:   A ─ B ─ E ───── M   ← main
               └ C ─ D ─┘

M captures exactly when and what was integrated. git log --first-parent main shows one entry per merged branch, and git revert -m 1 M undoes the whole feature at once.

Choosing with flags

CommandBehaviour
git merge featureFast-forward if possible, otherwise a merge commit
git merge --ff-only featureFast-forward or refuse; never creates a merge commit
git merge --no-ff featureAlways a merge commit, even when a fast-forward was possible
git pull --ff-onlyUpdate your branch only if it hasn't diverged; a good default

Set git config --global pull.ff only and git pull will never create a surprise merge commit on your local main. If it has diverged, Git stops and you decide whether to rebase or merge.

The team decision

  • --no-ff for feature branches keeps a visible "this PR landed here" commit, which is useful for reverting a feature or auditing.
  • Fast-forward only (after rebasing) or squash merges keep main linear and easy to read and bisect, at the cost of losing the branch structure.

Hosting platforms' "merge", "squash and merge" and "rebase and merge" buttons map onto these choices. See merge vs rebase for the trade-off.

Learn it in depth