Skip to content

Three ways to squash, and when each is right

Interactive rebase, soft reset, and merge --squash all collapse commits into fewer commits. They differ in granularity, on which branch they leave the result, and how much history they preserve.

Updated May 26, 2026

“Squash this branch before merging” is a common review request — and there are three ways to do it that look superficially identical but are different operations with different surface area. Pick the one that matches what you actually want preserved.

The three methods at a glance

Axis Interactive rebase (`rebase -i`) Soft reset + recommit
Granularity Per-commit — keep, squash, fixup, drop one at a time. All-or-nothing — everything becomes one commit.
Edits the branch Yes — rewrites the feature branch. Yes — rewrites the feature branch.
Preserves intermediate commit messages Yes (squash); no (fixup); editable. No — you write a single new message.
Best for Curating a story before merging — keep meaningful steps, drop WIP noise. Collapsing a messy spike or experiment into one clean commit.

merge --squash is the third option and operates on a different branch entirely — see below.

Method 1: interactive rebase

The right call when you want some commits to survive (the meaningful ones) and others to disappear (the WIP, the typo fixes, the “actually that was wrong” follow-ups).

git switch feature
git rebase -i origin/main

In the editor, change pick to squash or fixup on the lines you want collapsed:

  • squash (or s) — combine into the previous commit, open the editor to merge messages.
  • fixup (or f) — combine into the previous commit, discard this message.

Autosquash for the disciplined workflow

If you commit fixes with --fixup, Git can sort them automatically:

git commit --fixup=<sha-of-commit-to-fix>
# ... more work ...
git rebase -i --autosquash origin/main

The editor opens with fixup commits already moved adjacent to their targets and marked fixup. You skim, save, done.

Method 2: soft reset and recommit

The right call when you don’t care about the intermediate commits at all — the branch’s net change is the only thing you want to preserve.

git switch feature
git reset --soft origin/main
git commit -m "feat: one clean commit"

Why it works: reset --soft moves the HEAD pointer back to origin/main without touching the index or working tree. Every line your branch added is now staged for one fresh commit.

Method 3: merge --squash on the target branch

The right call when you don’t want to rewrite the feature branch at all — you want the target branch to receive a single integration commit with no merge-commit metadata.

git switch main
git merge --squash feature
git commit -m "feat: squash merge feature"

After this, main has one new commit containing every change from feature. The feature branch is unchanged, and there’s no merge commit pointing back to it — from main’s perspective, the feature branch never existed.

Decision shortcut

Decision
Chose: Interactive rebase
2026-05-26

When the branch has any commits worth keeping (a real refactor step before the feature, a test commit, a migration), interactive rebase preserves the story. Soft reset is for spike branches; merge --squash is for teams that have standardised on one-commit-per-PR at the integration layer.

✓ Interactive rebase
Pros
  • Keep meaningful intermediate commits
  • Drop WIP noise selectively
  • Fine control over the resulting story
Cons
  • More work to drive
  • Rewrites the feature branch
Soft reset + recommit
Pros
  • Fast — three commands
  • One commit, one message, done
Cons
  • Loses all intermediate history
  • Wrong base = giant accidental commit
merge --squash
Pros
  • Linear history on the target branch
  • Feature branch untouched
  • Clean default for one-PR-one-commit policies
Cons
  • No merge commit — loses the link back to the feature branch
  • Reverts must rewrite the whole squash

Pushing the result

After methods 1 and 2, the feature branch has been rewritten. The remote will reject a plain push:

git push --force-with-lease origin feature

After method 3, only the target branch has a new commit — push that normally; the feature branch wasn’t touched. See 0025 Git rebase without losing work · Git for why --force-with-lease is the right form.

Recovery

A squash that produced the wrong result is reachable via the reflog:

git reflog
git reset --hard HEAD@{n}

Every reset, rebase, and commit leaves an entry. The pre-squash tip stays reachable for the reflog expiry window (default 90 days), giving you time to recover from any normal mistake.

Anti-patterns

Anti-pattern
Squashing other people's commits during a rebase
Smell

Interactive rebase on a long-lived branch with multiple contributors — picking fixup on a commit you didn’t author.

Why it happens

The author saw “WIP” in the message and assumed it was noise to clean up.

Fix instead

Only squash commits you authored. Other people’s WIP commits are signals about their workflow you don’t have visibility into — and a force-push that flattens their work is the first step of an apology email.

Related