Feature work is usually messy: a spike, a fix, a formatting commit, a test correction. That is normal. Before review, interactive rebase lets you present the history as a sequence a reviewer can understand and bisect.
git rebase -i <base>opens a todo list wherepick,reword,squash,fixup, and reordering let you edit commits that exist only on your branch. The one rule: never rewrite a shared branch without coordinating, because rewritten commits are new commits with new identities.
pick 1a2b add parser
fixup 3c4d typo in parser
pick 5e6f add tests
reword 7a8b explain error handling
| Command | Use it when |
|---|---|
reword | Code is right; commit message is not |
fixup | Fold a correction into the previous commit silently |
squash | Combine commits and edit the message |
edit | Pause to amend code or split a commit |
drop | Remove obsolete work |
A safe branch cleanup
git fetch origin
git rebase -i origin/main
git push --force-with-lease
--force-with-lease protects you from overwriting a remote update you did not have locally. Plain --force does not. If conflicts appear, resolve them, run the relevant tests, git add, then git rebase --continue.
Rebase does not erase the old remote history from other clones. It changes the branch tip. Tell collaborators before you force-push a branch they may have checked out.
Good history is not cosmetic: it makes review narrower, reverts safer, and git bisect more useful. Clean it while the intent is still in your head.
Split a mixed commit instead of hiding it
Choose edit in the todo list, reset the commit while retaining its changes, then stage logical pieces:
git reset HEAD^
git add -p
git commit -m "Add parser"
git add -p
git commit -m "Add parser tests"
git rebase --continue
This is most valuable before review, when a formatting sweep, refactor, and behavioural change accidentally landed together. Keep commits buildable where practical; it lets a reviewer and git bisect move through history without reconstructing your working state.
Cover photo by luis gomes on Pexels.
