INTERMEDIATEGITREBASE & HISTORY

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.

Published Apr 22, 2026
gitrebasesquashhistoryshell

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~5

The 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-lease

The 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-lease refuses to push if someone else has pushed to your branch in the meantime, which prevents you from clobbering their work. --force is the foot-gun.
  • Never rebase shared branches. If main or develop is your branch, do not rebase. Use git revert or merge instead.
  • fixup is usually what you want, not squash. squash keeps both commit messages and asks you to edit the result, which is annoying. fixup silently 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 --abort is always safe. It restores your branch to exactly where it was before you started the rebase.

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.

X (Twitter)LinkedIn