Clean Up Local Commits with Interactive Rebase
Rewrite local commit history using git rebase --interactive to squash, reorder, or edit commits before sharing. Use this when preparing a clean pull request from messy WIP commits.
Task
Squash multiple WIP commits into a single logical commit with a clean message before opening a pull request.
When to use this
You've been working on a feature branch with commits like 'wip', 'fix typo', 'actually fix bug', 'address review'. You want to present a single well-structured commit.
Prerequisites
- Git 2.20+ installed and configured
- Unpushed commits on your current feature branch
- No one else is working on the same branch
Steps
1. Identify how many commits to rebase. Count back from HEAD to the commit before your changes began.
git log --oneline -n 10Expected output: A list of recent commits with 7-character SHA and subject lines. Identify the number of commits to squash.
2. Start interactive rebase for the last N commits (e.g., 4 commits).
git rebase -i HEAD~4Expected output: An editor opens listing 4 commits with 'pick' prefix, with instructions at the bottom about available commands.
3. In the editor, change all but the first commit from 'pick' to 'squash' (or 's') to merge their changes into the first commit. Alternatively, use 'fixup' (or 'f') to discard their commit messages entirely. Save and close the editor.
Expected output: Another editor opens combining all commit messages. Edit to write a single clear commit message. Save and close.
4. Verify the new commit history shows a single consolidated commit.
git log --oneline -n 5Expected output: The messy commits are gone, replaced by one commit with your new message. Note: commit SHA has changed (hash rewritten).
Troubleshooting
Problem
CONFLICT during rebase
Solution
Resolve the conflict in the affected files, stage them with `git add .`, then run `git rebase --continue`. To abort: `git rebase --abort`.
Problem
Rebase already-pushed commits
Solution
Never rebase pushed shared commits. Only use this on local-only work. If already pushed, coordinate with team before force-pushing.
Problem
Editor opens in vi and you can't exit
Solution
Type `:wq` and Enter to save and quit. To change default editor: `git config --global core.editor 'code --wait'` (VS Code).
Limitations
- Never rebase commits that have been pushed to a shared branch — it rewrites history and breaks collaborators' local copies
- Interactive rebase creates new commit SHAs, so it's not safe for commits that other branches reference
- Complex multi-branch histories may require rerere (reuse recorded resolution) to avoid resolving the same conflict repeatedly
Why Clean Up Local Commits
Messy WIP commits ('fix', 'wip', 'really fix this time') make code review harder and pollute the repository history. Interactive rebase lets you rewrite local-only commits before sharing them safely.
When to Use Interactive Rebase
- You have multiple small WIP commits that form one logical change
- You want to reorder commits to tell a better story
- You need to fix a typo in an older commit’s content
- You want to edit a commit message before pushing
Step-by-Step Example
# View recent commits
git log --oneline -n 5
# a1b2c3d Add user authentication
# d4e5f6g fix typo
# h7i8j9k actually implement auth
# l0m1n2p wip
# q3r4s5t (origin/main) Recent shared commit
# Start interactive rebase for the last 4 commits
git rebase -i HEAD~4
In the editor that opens:
pick l0m1n2p wip
squash h7i8j9k actually implement auth
squash d4e5f6g fix typo
squash a1b2c3d Add user authentication
Rebase Commands Reference
| Command | Short | Effect |
|---|---|---|
| pick | p | Keep this commit as-is |
| reword | r | Keep changes, edit message |
| edit | e | Pause to amend this commit |
| squash | s | Merge changes into previous commit, combine messages |
| fixup | f | Merge changes into previous, discard this message |
| drop | d | Remove this commit entirely |
Fixup vs Squash
Squash: Combines messages — you edit the final message from all combined commits:
# This is a combination of 4 commits.
# The first commit's message is:
wip
# This is the 2nd message:
actually implement auth
Fixup: Discards all other messages — only the first commit’s message is kept:
pick l0m1n2p wip # keep this message
fixup h7i8j9k # discard "actually implement auth"
fixup d4e5f6g # discard "fix typo"
Recovery: If Something Goes Wrong
# Abort the rebase (return to pre-rebase state)
git rebase --abort
# After resolving a conflict, continue the rebase
git add .
git rebase --continue
# If you made a mistake, use the reflog to find the original state
git reflog
git reset --before-rebase-state
Safety Rules
- Never rebase pushed commits on shared branches
- Create a backup branch before major rewrites:
git branch backup-my-feature - Use
execin interactive rebase to run tests after each pick - Configure merge.conflictStyle to
diff3for 3-way conflict view
# Add this to .gitconfig for better conflict resolution
git config --global merge.conflictStyle diff3
Related Patterns
--autosquash: Mark commits asfixup!orsquash!during creation, thengit rebase -i --autosquasharranges them automaticallygit commit --amend: Quick single-commit edit (equivalent to reword for the last commit)