Interactive rebase is the feature I most wanted to internalise when I was learning git, and the one I had the most trouble teaching other people. So this is me doing the thing on a real-looking branch.

The branch has six commits. Three of them are mine being sloppy: initial cache impl, two typo commits, add cache metrics, fix tests (which should really have been folded into the metrics commit), and a WIP refactor cache layer that I want to reword into a proper message.

The todo list I write is the whole trick. pick the first real commit. squash the two typo commits into it (squash combines the message; fixup discards it). pick the metrics commit. fixup the fix-tests commit because its message is garbage. reword the WIP commit so I can write a real message.

Notice that rebase pauses twice: once to combine the squash messages, once to open the editor for the reword. The final git log --oneline -3 shows three clean commits where there used to be six.

Callouts:

  • squash vs fixup: squash opens the editor so you can merge messages. fixup keeps the earlier message and throws the later one away. I use fixup about 80% of the time.
  • This only rewrites local history. If you’ve pushed the branch, you’ll need --force-with-lease to replace the remote version.
  • I never rebase main. Ever. Everything else is fair game.