What you will achieve
You will take a feature branch with messy work-in-progress commits ("wip", "more wip", "fix lint", "address feedback") and rewrite it into a clean, atomic commit series ready for review.
Set up a sandbox
mkdir cleanup-tutorial && cd cleanup-tutorial
git init
echo "main" > app.txt && git add . && git commit -m "Initial"
git checkout -b feature
echo "1" > one.txt && git add . && git commit -m "wip"
echo "2" > two.txt && git add . && git commit -m "more wip"
echo "3" > three.txt && git add . && git commit -m "fix"
echo "fixed" >> one.txt && git commit -am "actually fix one"
echo "lint" >> three.txt && git commit -am "lint"
Step 1: review
git log --oneline
# 5 commits with terrible messages
Step 2: interactive rebase
git rebase -i main
Editor opens:
pick aaa1111 wip
pick bbb2222 more wip
pick ccc3333 fix
pick ddd4444 actually fix one
pick eee5555 lint
Step 3: rewrite the script
Reorder, squash, and reword:
pick aaa1111 wip # will become "Add one"
fixup ddd4444 actually fix one # folds into above
pick bbb2222 more wip # will become "Add two"
pick ccc3333 fix # will become "Add three"
fixup eee5555 lint # folds into above
Save and exit. Git replays commits according to your script.
Step 4: reword each commit
Wait - we wanted to reword too. Mark the picks as reword instead, or do a second pass:
git rebase -i main
reword AAA111 wip
reword BBB222 more wip
reword CCC333 fix
Git pauses on each reword commit; type a proper message:
Add file one with initial content
The first piece of the feature, providing the base data structure.
Step 5: verify
git log --oneline
# Clean commits with proper messages
git log
# Verify each commit's message and content
Step 6: push
If the branch was previously pushed:
git push --force-with-lease
The fixup workflow during development
The cleanup we just did manually can be partially automated by using --fixup as you go:
# During development, when you find a fix to an earlier commit:
git commit --fixup <target-sha>
# When ready to clean up:
git rebase -i --autosquash main
Autosquash automatically reorders fixup commits next to their targets and marks them as fixups - no manual editing of the rebase script.
Splitting a too-large commit
The opposite problem: one commit contains two unrelated changes. Mark it as edit:
git rebase -i main
# mark the commit as edit
# rebase pauses
git reset HEAD^ # un-stage the changes
git add -p # cherry-pick hunks
git commit -m "First atomic piece"
git add -p
git commit -m "Second atomic piece"
git rebase --continue
Pitfalls
- Never rebase a branch others have based work on.
- Always
--force-with-lease, never plain--forceon shared branches. - Test after rebase - the squashed result might behave differently from the sum of parts.
Recovery
Botched rebase? The reflog has the original tip:
git reflog
git reset --hard ORIG_HEAD
The result
Your reviewer sees a series of atomic, well-titled commits telling a story: setup, feature, polish. The diff is the same as your messy version, but the narrative invites careful review. Spend ten minutes cleaning up; save your reviewer thirty minutes of confusion.