How do you use interactive rebase to rewrite, squash, and reorder commits before pushing to a shared branch?
Quick Answer
git rebase -i HEAD~N opens an editor listing the last N commits with actions like pick, squash, fixup, reword, edit, and drop. You reorder lines to reorder commits, change 'pick' to 'squash' to combine commits, or 'reword' to edit messages, crafting a clean history before pushing.
Detailed Answer
Think of interactive rebase like editing a draft manuscript before sending it to your publisher. Your first draft has rough chapters, duplicated paragraphs, and notes-to-self scattered throughout. Before publishing, you reorder chapters, merge related sections, rewrite headings, and remove scratch notes. The final version tells a clean story even though the creative process was messy. Interactive rebase lets you do exactly this with your commit history.
Interactive rebase is invoked with git rebase -i <base>, where <base> is typically HEAD~N (the last N commits) or a branch name. Git opens your configured editor with a list of commits, each prefixed with an action keyword. The available actions are: pick (use commit as-is), reword (use commit but edit the message), edit (pause after applying to let you amend), squash (combine with the previous commit, merging messages), fixup (combine with the previous but discard this commit's message), drop (remove the commit entirely), and exec (run a shell command between commits). You can also reorder lines to reorder the commits in history.
Internally, interactive rebase works by detaching HEAD, moving it to the base commit, and replaying the listed commits one by one according to your instructions. Each replayed commit gets a new SHA-1 hash because its parent has changed. The state is tracked in .git/rebase-merge/ which contains the todo list, done list, and current patch being applied. If a conflict arises during replay, Git pauses and lets you resolve it, then continue with git rebase --continue. The autosquash feature (git rebase -i --autosquash) automatically reorders commits whose messages start with 'squash!' or 'fixup!' to appear after the commit they reference, which pairs beautifully with git commit --fixup=<sha> during development.
In production workflows, interactive rebase is the primary tool for crafting meaningful commit histories before opening a pull request. At companies like Shopify and Stripe, developers work with messy WIP commits locally, then use interactive rebase to produce a logical sequence: one commit per logical change, clear messages following conventional commit format, and no debugging artifacts. A common pattern is: develop freely with frequent small commits, then before pushing, run git rebase -i main to squash related changes, reword unclear messages, and drop any 'WIP' or 'debug' commits. Many teams combine this with git commit --fixup and --autosquash for efficient cleanup. The exec action is powerful for CI-like validation: git rebase -i --exec 'npm test' HEAD~5 runs the test suite after each commit, ensuring no intermediate commit breaks the build.
A critical gotcha: never interactive-rebase commits that have already been pushed to a shared branch, as other developers' histories will diverge from yours, causing confusion and merge conflicts. Another trap: squashing too aggressively can make git bisect less effective because large commits are harder to diagnose than small, atomic ones. Find the balance between a clean history and debuggability. Also, if an interactive rebase goes wrong partway through, git rebase --abort returns you to the exact state before you started. Keep your terminal open during the rebase session so you can monitor progress and catch conflicts early.