Turning a messy series of WIP commits into a clean, reviewable story is what interactive rebase is for.
git rebase -i <base> opens your editor with a todo list of every commit after <base>, oldest first. Each line starts with a command; you edit the list, save, and Git replays the commits accordingly:
pick: keep the commit as is.reword: keep it but edit the message.edit: stop after applying it so you can amend it or split it into several commits.squash: meld into the previous commit and combine the messages.fixup: meld into the previous commit and discard this message (fixup -Ckeeps this one's message instead).drop(or delete the line): remove the commit.exec <cmd>: run a shell command, such as the tests, at that point; the rebase stops if it fails.
Reordering lines reorders commits. Typical use: git rebase -i origin/main to tidy everything your branch adds before opening a PR.
Autosquash saves editing by hand. When you notice a mistake in an earlier commit, fix it and run git commit --fixup=<that-commit>. Later, git rebase -i --autosquash origin/main (or rebase.autoSquash true) moves each fixup under its target, already marked.
To split a commit: mark it edit; when Git stops, run git reset HEAD~, then stage and commit pieces separately, then git rebase --continue.
If you stack branches (part1 on which part2 is built), --update-refs moves those intermediate branch pointers along too.
All the usual rebase rules apply: new commit IDs, so only rewrite commits not yet shared, and git rebase --abort gets you out at any point.
Example
git commit --fixup=3f9a2c1 # "fixup! Add search index"
git rebase -i --autosquash origin/main
# Editor shows (oldest first):
# pick 3f9a2c1 Add search index
# fixup a77b1e0 fixup! Add search index
# pick 5d2e9f1 WIP
# pick 8c4b0aa Add search UI
# Change to:
# pick 3f9a2c1 Add search index
# fixup a77b1e0 fixup! Add search index
# squash 5d2e9f1 WIP
# reword 8c4b0aa Add search UI
# exec make test
git push --force-with-leaseRemember
- The todo list is oldest-first; reorder lines to reorder commits
- squash merges messages; fixup discards the fixup's message
- commit --fixup + rebase --autosquash automates cleanup
- exec runs tests at any point in the replay
Watch out for
- Interactive-rebasing commits already merged into a shared branch, creating duplicate commits for everyone.