Skip to content

← Writing

engineering

Merge vs Rebase vs Squash-Merge

· Jerwin Arnado ·

You finished a feature branch and cleaned it up with rebase. Now you have to land it on main, and your hosting provider offers a button with three options: Create a merge commit, Rebase and merge, Squash and merge. They produce three genuinely different histories. Picking blindly is how a main log becomes either a tangle of merge bubbles or a flat wall of unrelated commits. Here’s what each actually does.

The setup

Say main moved forward while you worked, and your branch has three commits:

main:     A───B───C
                   \
feature:            D───E───F

All three strategies get D, E, F onto main. They differ in the shape they leave.

1. Merge commit (git merge)

git checkout main
git merge feature

Git creates a new merge commit (M) with two parents — the tip of main and the tip of feature — stitching the branch in without rewriting anything:

main:     A───B───C───────M
                   \     /
feature:            D───E───F

What you get: the complete, true history. Every commit is preserved with its original hash; the merge commit records exactly when and what was integrated. Branch structure is visible forever.

The cost: on a busy repo, main fills with merge commits and the graph looks like a subway map. git log is noisy; following the mainline takes effort.

2. Rebase and merge (git rebase then fast-forward)

git checkout feature
git rebase main          # replay D,E,F on top of C
git checkout main
git merge feature        # fast-forward, no merge commit

Rebase replays your commits on top of the current main, then main fast-forwards to include them. No merge commit:

main:     A───B───C───D'───E'───F'

What you get: a perfectly linear history. Every commit is preserved individually (D’, E’, F’ — note the new hashes from the replay), but there’s no branch bubble. git log reads like a single story.

The cost: it rewrites the branch’s commits (new hashes), so it obeys the one rebase rule — only do it to un-shared branches. And you lose the explicit record that D, E, F were ever one unit.

3. Squash and merge (git merge --squash)

git checkout main
git merge --squash feature
git commit -m "feat(auth): add passkey login"

All of the branch’s changes are collapsed into a single new commit on main. D, E, and F cease to exist as separate commits:

main:     A───B───C───S          (S = D+E+F as one commit)

What you get: the cleanest possible main — one feature, one commit, one conventional-commit subject. The messy intermediate commits never touch the shared history at all, which means you barely even need to clean the branch first.

The cost: you throw away the branch’s internal granularity. If the feature later needs a partial revert or a git bisect lands inside it, you have one big commit, not the fine-grained steps. Attribution of individual changes is gone too.

A rule of thumb

Strategy History shape Best when…
Merge commit Full, branched You want a complete audit trail; large team; long-lived branches
Rebase and merge Linear, every commit kept You value a clean and granular history; small commits each stand alone
Squash and merge Linear, one commit per feature Branch commits are noisy; you want main to read as one-feature-per-line

My default for a feature branch: squash and merge. Most feature branches accumulate “wip” and “fix typo” commits that nobody needs in main, and squash makes the whole cleanup question moot — the noise never lands. Reach for rebase-and-merge when the individual commits are each meaningful and well-formed (a refactor done in deliberate steps). Reach for a merge commit when the audit trail of “these things were integrated together, on this date” genuinely matters.

Caveats and best practices

  • Pick one per repo and configure it. On GitHub, Settings → repo → “Allow [strategy]” and disable the rest, so nobody picks the wrong button half-asleep. Consistency beats any individual choice.
  • Squash kills the branch’s commit messages — so the one squashed message is the only record. Make it a good conventional-commit subject; it’s all main will remember.
  • Rebase-and-merge on a shared branch is a footgun. If the PR branch has been pulled by others, the rewrite bites them. Squash sidesteps this (it makes a brand-new commit).
  • Delete the branch after. All three strategies leave the source branch behind; merge it, then remove it so stale branches don’t pile up.

Conclusion

Three buttons, three histories: a merge commit keeps the whole truth, rebase keeps a linear truth, squash keeps a summary. There’s no universally right answer — but there is a right answer per repo, so decide once, enforce it in settings, and stop thinking about it. For most feature work, squash-and-merge buys the cleanest main for the least ceremony.