Squash the Last N Commits with Interactive Rebase
Use git rebase -i to squash, reorder, edit, or drop the last N commits before pushing or opening a pull request.
git rebase -i HEAD~N is how you clean up the last N commits before pushing or opening a pull request. Interactive rebase lets you squash work-in-progress commits, reword bad messages, drop debug commits, and reorder steps without losing your changes. This is the daily workflow for keeping a tidy history.
Tested on git 2.43+.
When to Use This
- Squashing 5 messy WIP commits into one clean commit before opening a PR
- Rewording a recent commit message
- Dropping a "fixme" or debug commit you don't want to push
- Reordering commits so logically related changes appear together
Don't use this when the commits have already been pushed and others may have pulled them. Rewriting public history breaks every collaborator on that branch.
Code
# Open the last 5 commits in your editor
git rebase -i HEAD~5The editor opens with something like:
pick a1b2c3d feat: scaffold login form
pick e4f5g6h fix: typo in label
pick i7j8k9l fix: another typo
pick m0n1o2p WIP: trying validation
pick q3r4s5t feat: zod schema for login
Change pick to one of:
| Action | Effect |
|---|---|
| pick | Keep the commit as-is |
| reword (r) | Keep the commit, edit the message |
| squash (s) | Combine into the previous commit, both messages preserved |
| fixup (f) | Combine into the previous commit, discard this message |
| drop (d) | Remove the commit entirely |
| edit (e) | Pause so you can amend the commit |
Save and close. Git replays the commits according to your edits.
Usage
The cleanup workflow before opening a PR:
# 1) See what you're working with
git log --oneline -10
# 2) Rebase the commits since main
git rebase -i $(git merge-base HEAD main)
# 3) In the editor: squash WIP commits, reword the final message
# pick a1b2c3d feat: scaffold login form
# fixup e4f5g6h fix: typo in label
# fixup i7j8k9l fix: another typo
# fixup m0n1o2p WIP: trying validation
# squash q3r4s5t feat: zod schema for login
# 4) After save, you get one combined commit. Force-push your branch.
git push --force-with-leaseThe result: 5 messy commits become 1 clean feat: add login form with zod validation commit on your PR.
Pitfalls
- Always use
--force-with-lease, never--force.--force-with-leaserefuses to push if someone else has pushed to your branch in the meantime, which prevents you from clobbering their work.--forceis the foot-gun. - Never rebase shared branches. If
mainordevelopis your branch, do not rebase. Usegit revertor merge instead. fixupis usually what you want, notsquash.squashkeeps both commit messages and asks you to edit the result, which is annoying.fixupsilently merges into the previous commit and keeps only that commit's message.- You can rebase onto a different base with
git rebase -i --onto <newbase> <oldbase>. Useful when you accidentally branched from the wrong commit. - If you mess up,
git rebase --abortis always safe. It restores your branch to exactly where it was before you started the rebase.
Related Snippets & Reading
- Undo the Last Git Commit — when you only need to fix one commit
- Recover a Deleted Branch with Reflog(coming soon) — for rescuing a botched rebase
- git rebase official docs — interactive rebase reference
Frequently Asked Questions
What does git rebase -i actually do?
Interactive rebase opens an editor with a list of the last N commits and lets you reword, squash, fixup, drop, or reorder them. When you save and close, git replays the commits according to your edits, producing a new linear history with the same end state.
When should you squash commits versus keeping them separate?
Squash before opening a pull request to clean up the noise of work-in-progress commits. Keep them separate when each commit represents a meaningful, reviewable step that someone might want to bisect or revert independently.