# Fast-forward vs merge commits

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

Concept · Merging & history · https://levelcode.org/concepts/fast-forward-merge

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](https://levelcode.org/concepts/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

| Command | Behaviour |
|---|---|
| `git merge feature` | Fast-forward if possible, otherwise a merge commit |
| `git merge --ff-only feature` | Fast-forward or **refuse**; never creates a merge commit |
| `git merge --no-ff feature` | **Always** a merge commit, even when a fast-forward was possible |
| `git pull --ff-only` | Update 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](https://levelcode.org/concepts/git-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](https://levelcode.org/concepts/merge-vs-rebase) for the trade-off.
