GitSquash Merge

Squash Merge

A squash merge combines all the commits on a branch into a single new commit on the destination — no merge commit, no individual branch commits in history. The destination branch sees the feature as one unit of work. This is the default style on many modern teams because it keeps history clean and easy to read.

The setup

Before squash

Text
        A───B───C        ◀── main (HEAD)
                 \
                  D───E───F   ◀── feature-x (3 commits)
The squash merge

Bash
git switch main
git merge --squash feature-x
# Squash commit -- not updating HEAD
# Automatic merge went well; stopped before committing as requested

# All of feature-x's changes are now STAGED on main. Make the commit:
git commit -m "Add login feature"
After squash

One new commit on main

Text
        A───B───C───S        ◀── main
                 \
                  D───E───F        ◀── feature-x (orphaned from main's history)

S contains the combined diff of D, E, and F, but is a brand-new commit with no parent relationship to feature-x. Notice main has no merge commit and no second parent.

Squash on GitHub

On a Pull Request, click the dropdown next to the merge button and pick “Squash and merge”. GitHub will offer to combine the PR’s commits, prompt you for a final commit message (defaulting to the PR title and body), and produce a single new commit on the target branch.

Pros and cons
  • Clean history. Each merge becomes one commit on main — easy to scan.

  • One revert, one rollback. To undo a feature, revert one commit.

  • Encourages messy WIP commits on branches — “fix typo”, “lint”, “address review”. They are squashed away on merge.

  • Loses fine-grained history. The individual steps of the feature are gone.

  • Loses author info for each step — the squash commit has one author.

  • The feature branch becomes orphaned in the graph; its commits remain in the repo but are not on main.

When to squash
  • Small to medium features where the intermediate commits do not add long-term value.

  • Teams that value a linear, readable main history above all else.

  • When PR review happens commit-by-commit but the merge should be one logical change.

When NOT to squash
  • Long-running release branches where you want a full audit trail.

  • When the individual commits each tell a useful story — e.g., a refactor where each step is independently meaningful.

  • When you want to preserve precise authorship for credit or blame.

  • Mono-repos where multiple unrelated changes might be in one PR (squashing hides that).

The commit message for a squash

A typical squash commit message

Text
Add login feature (#123)

* Add login form component
* Wire form to /auth/login endpoint
* Handle invalid credentials gracefully
* Add unit tests for the new flow

Co-authored-by: Alice Smith <alice@example.com>

GitHub usually pre-fills this with the PR title and a bulleted list of every squashed commit. You can edit it before confirming the merge.

Cleaning up after a squash

Delete the orphan branch

Bash
git switch main
git pull                       # bring in the squashed commit
git branch -D feature-x        # local feature-x is now "unmerged"
# (You need -D because Git can't tell the squash includes feature-x's work)
git push origin --delete feature-x   # remove from the remote
Why -D, not -d?
Git considers `feature-x` unmerged because the squash created a *new* commit on main rather than a merge commit. Use `-D` to delete anyway — you know the work is already on main.
Squash vs rebase
  • Squash — collapses N commits into 1. Most information is lost.

  • Rebase + merge (no squash) — keeps N commits but linearises them onto main. History is straight, individual commits preserved.

  • Squash + rebase (PR style) — common GitHub/GitLab setting; the squash happens server-side. Same effect as git merge --squash.

Tip
A practical hybrid: squash on merge, but write a good final commit message. You lose the intermediate steps but gain a clean log; the message itself becomes the record of the feature.