Squashing commits is the practice of collapsing a sequence of work-in-progress commits — "WIP", "fix typo", "oops forgot file" — into a single meaningful commit before it lands on the main branch. A clean history is not about aesthetics; it makes git bisect, git log, and git revert dramatically faster to use.
What changed in 2026
- GitHub's "Squash and merge" is the default merge strategy for most teams — it enforces one-commit-per-PR automatically without any local git knowledge required.
- Conventional Commits (
feat:, fix:, chore:) are widely adopted; squashed commit messages are expected to follow the format for automated changelogs.
git rebase --autosquash is underused but powerful — prefix a commit message with fixup! or squash! and rebase picks it up automatically.
- Linear history is increasingly enforced on main via branch protection rules — merge commits are blocked, so squash-and-merge or rebase-and-merge are the required strategies.
Method 1 — GitHub (or GitLab) squash-and-merge
The simplest path: no local git knowledge needed.
- Open your pull request.
- Click the dropdown arrow next to Merge pull request.
- Select Squash and merge.
- Edit the commit message (GitHub pre-fills it with all commit messages concatenated).
- Click Confirm squash and merge.
The result is a single commit on main. Your feature branch is unchanged. After merging, delete the branch.
When to use: any PR where the branch commits are messy and you want a clean main-branch history.
Method 2 — interactive rebase
For squashing locally before pushing (or before a PR):
# Squash the last 4 commits
git rebase -i HEAD~4
Git opens your editor with a list:
pick abc1234 Add user model
pick def5678 WIP auth
pick ghi9012 fix typo in migration
pick jkl3456 actually working auth
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# s, squash = use commit, melt into previous commit
# f, fixup = like squash, but discard this commit's message
Change pick to squash (or s) for commits you want to merge into the one above them:
pick abc1234 Add user model
squash def5678 WIP auth
fixup ghi9012 fix typo in migration
squash jkl3456 actually working auth
Save and close. Git opens a second editor for the final combined commit message. Write a clean, descriptive message, save, and you are done.
# Then force-push your branch (only if it was already pushed)
git push --force-with-lease
Method 3 — git merge --squash
Merges a branch into the current branch as a single staged change, which you then commit manually:
git checkout main
git merge --squash feature/my-feature
git status # all changes from the branch are now staged
git commit -m "feat: add user authentication (#42)"
This gives you full control over the commit message. It does not create a merge commit. The feature branch still exists and still has its individual commits.
When to use: when you want to write the commit message from scratch and the feature branch is yours alone.
Squash + autosquash workflow
Pre-mark commits for squashing while you work:
# Create a fixup commit for an earlier commit
git commit --fixup abc1234
# Later, let rebase pick them all up automatically
git rebase -i --autosquash HEAD~5
--autosquash reorders commits prefixed with fixup! or squash! and sets their action automatically — you just review and save.
What the final commit message should look like
Following Conventional Commits:
feat(auth): add JWT-based user authentication
- Introduces /login and /logout endpoints
- Signs tokens with RS256; public key in env
- Adds middleware to validate Bearer tokens on protected routes
Closes #42
One subject line, an optional blank line, then a body if the change needs explanation. GitHub's squash dialog pre-fills from all commit messages — clean it up before confirming.
Squash workflow comparison
| Method |
Where |
Rewrites branch history? |
Needs local git? |
| GitHub squash-and-merge |
Remote |
No (branch unchanged) |
No |
| git rebase -i |
Local |
Yes |
Yes |
| git merge --squash |
Local |
No (branch unchanged) |
Yes |
| --autosquash |
Local |
Yes |
Yes |
How to pick
- Merging a PR with messy WIP commits? → Use GitHub/GitLab squash-and-merge.
- Want to clean up before requesting review? →
git rebase -i HEAD~N.
- Integrating a long-lived branch into another branch locally? →
git merge --squash.
- Regularly creating fixup commits as you work? → Set up
--autosquash with git config --global rebase.autoSquash true.
Common mistakes
Squashing after a colleague has checked out your branch. Their local branch diverges from yours after the force-push. Communicate before rewriting history on a shared branch.
Forgetting --force-with-lease after a rebase. A plain git push is rejected; --force is dangerous; --force-with-lease is the safe option.
Writing "squash" in the final commit message. The message that lands on main should describe the feature, not the git operation. Delete all the "WIP" and "fix typo" lines from the squash editor.
Squashing a commit that is already on main. You are rewriting shared history. This will cause divergence for everyone who has pulled main.
What to skip
- Squashing every commit including meaningful atomic steps — some teams keep one commit per logical change (refactor, feature, test) rather than one commit per PR. Know your team's convention.
- Interactive rebase on main — only rebase feature branches.
git push --force (without --lease) after rebasing — use --force-with-lease always.
FAQ
Does squashing lose commit metadata like author and date?
By default, the squashed commit uses your author info and the current timestamp. Individual authors from squashed commits appear in the squash message body on GitHub but not in the git object.
Is squash-and-merge or rebase-and-merge better for main?
Squash-and-merge gives you one commit per PR — easiest to revert, easiest to read. Rebase-and-merge preserves individual commits but requires they all be clean. Pick one and enforce it consistently via branch protection settings.
How do I squash commits on a branch that has already been merged?
You cannot cleanly — the history is shared. Use git revert on main to undo the feature, then rewrite and re-merge. Easier to squash before merging.
What is git commit --fixup and when should I use it?
It creates a commit with the message fixup! <original message>, signaling to git rebase --autosquash that it should be folded into the original. Use it while developing to mark "this fixes a mistake in commit X" without interrupting your flow.
Where to go next