Git Interactive Rebase: Rewrite History Without Losing Your Job
Master git interactive rebase with production patterns.
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
- ✓Basic git commands (commit, push, pull, log)
- ✓Understanding of branches and merge conflicts
- ✓Comfortable with the command line
Use git rebase -i HEAD~N to rewrite the last N commits. In the editor, change 'pick' to 'squash', 'reword', 'edit', or 'drop'. Save and close. Git replays each commit according to your instructions.
Imagine you're building a model airplane. You glued some parts in the wrong order, used too much glue, and left fingerprints. Interactive rebase is like being able to unglue those parts, clean them up, and reattach them in the right order — before you show the finished model to anyone. It's not time travel; it's careful disassembly and reassembly.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Every developer has that moment: you look at your git log and see a disaster. 'Fix typo', 'WIP', 'actually fix it', 'merge conflict resolution attempt #4'. You'd never ship that mess, but somehow it's in your branch. Interactive rebase is your cleanup crew. It lets you rewrite local history into a coherent story before anyone else sees it. But here's the catch: rebase rewrites history. Do it wrong, and you'll force-push chaos onto your team. Do it right, and you'll be the dev who always has clean, reviewable PRs. This article will teach you the exact patterns I've used across dozens of production services — and the traps that have burned me at 3 AM.
Why Interactive Rebase Exists: The Problem of Ugly History
Your commit history is a story. When you're working alone, every 'oops' commit is a footnote only you need to read. But the moment you open a PR, that story becomes public. Reviewers don't want to wade through 'fix typo' and 'actually fix it' to understand your logic. They want a clean narrative: 'Add payment validation', 'Handle edge case for refunds', 'Update tests'. Interactive rebase is the editor that lets you rewrite that narrative before publishing. Without it, your PRs are harder to review, your git blame is noisier, and your team's history becomes a swamp.
git branch backup/my-feature. If the rebase goes sideways, you can git reset --hard backup/my-feature and try again.The Anatomy of an Interactive Rebase: pick, squash, reword, edit, drop
When you run git rebase -i HEAD~N, git opens an editor with a list of the last N commits. Each line starts with a command: pick (use the commit as-is), squash (combine with the previous commit, merging messages), reword (change the commit message), edit (stop to amend the commit), or drop (delete the commit). The order of lines is the order commits will be replayed. Change the commands, save, and git replays them. This is the core mechanic. Everything else is pattern.
Squashing: Combining Multiple Commits into One
Squashing is the most common rebase operation. You have three commits: 'WIP', 'fix typo', 'actually fix it'. They should be one commit: 'Implement feature X'. Squash merges the changes and lets you write a single message. But there's a nuance: squash combines the commit into the one above it. If you want to squash the last three into the first, you need to reorder lines or use fixup (which discards the message). I use fixup when I don't care about the intermediate messages — just the code changes.
git commit --fixup=<commit> to mark a commit as a fixup for an earlier one. Then git rebase -i --autosquash automatically arranges them. Saves manual editing.Rewording: Fixing Commit Messages Without Changing Code
Sometimes the code is perfect but the commit message is garbage. 'fix bug' tells you nothing six months later. Use reword to edit the message. Git will stop at that commit, open the editor, and let you type a new message. No code changes. This is safe even for pushed commits — as long as you haven't shared them. If you've already pushed, you're rewriting history, so coordinate.
Editing: Splitting a Commit into Multiple Commits
Sometimes a commit does too much: 'Add login and refactor database layer'. You want two clean commits. Use edit to stop at that commit, then git reset HEAD^ to unstage everything, then commit in pieces. This is advanced but invaluable for cleaning up a messy PR. I've used this to untangle commits that mixed business logic with config changes — a classic review nightmare.
Dropping Commits: Removing Mistakes from History
You committed a debug log that prints secrets. Or you accidentally committed a large binary file. drop removes the commit entirely. But be careful: if later commits depend on changes in the dropped commit, you'll get merge conflicts. Git will stop and ask you to resolve. This is actually a safety net — it forces you to verify the drop is safe.
git rebase -i to drop it, then force-push to a new branch. But if the commit is already public, you must rotate any exposed secrets and consider the commit compromised.Reordering Commits: Telling a Better Story
Sometimes commits are in the wrong order. Maybe you wrote tests before the implementation, but you want the implementation first in the log. In the rebase editor, simply move lines up or down. Git will replay commits in the new order. If there are dependencies, you'll get conflicts. This is a great way to force yourself to think about commit independence — each commit should compile and pass tests on its own.
git rebase -i --exec "make test" runs tests after each commit. If any fail, you need to fix dependencies.Resolving Conflicts During Rebase: The Practical Guide
Conflicts during rebase are inevitable when you reorder, edit, or drop commits. Git stops at the conflicting commit and leaves conflict markers in the file. Fix them, git add, then git rebase --continue. The key difference from merge conflicts: each conflict is per-commit, so you resolve them one at a time. This is actually easier than resolving a giant merge conflict. Use git mergetool if you prefer a GUI. Never use git rebase --skip unless you're sure you want to drop that commit entirely.
git rebase --abort after resolving some conflicts discards all your resolution work. Only abort if you want to start over completely.When Not to Rebase: The Golden Rule
Never rebase commits that have been pushed to a shared branch. Period. If you do, you'll force-push rewritten history, and everyone else's local branches will diverge. The result: merge commits, lost work, and angry teammates. The exception is your own feature branch that no one else has pulled. If you must rebase a shared branch (e.g., to clean up before merging), coordinate with the team, have everyone stash their work, and do it in a scheduled window. Better yet, use git merge --squash on the target branch instead.
git reflog on each developer's machine to find the old commit, then git reset --hard to that commit, then cherry-pick any new work. It's messy. Don't be that dev.Interactive Rebase Workflow: A Production Pattern
Here's the workflow I use on every feature branch. Start with git checkout -b feature/payment-validation. Commit freely — WIP, fixups, whatever. Before pushing, run git rebase -i $(git merge-base HEAD master) to rebase all commits since branching from master. Squash fixups, reword messages, reorder for clarity. Then git push -u origin feature/payment-validation. Open PR. If master advances, rebase again: git pull --rebase origin master. Never merge master into your branch — that creates a merge commit that pollutes history. This keeps your PR linear and reviewable.
git pull --rebase as default: git config --global pull.rebase true. This avoids accidental merge commits when pulling.Recovering from a Botched Rebase: The reflog Safety Net
Even seniors mess up rebases. The reflog is your safety net. git reflog shows every action that moved HEAD — commits, rebases, resets. Find the commit before the rebase (look for 'rebase' entries or the last commit you recognize). Then git reset --hard <commit-hash> to go back. This works even if you've done multiple rebases. I've used this to recover branches that seemed completely destroyed. The reflog expires after 90 days by default, so act fast.
git push --force-with-lease, but warn your team first.The Force-Push That Killed a Friday Release
git rebase master on a shared branch, then force-pushed. The rebase rewrote commits that another dev had based their work on. When the second dev pushed, git saw divergent histories and created a merge commit that broke the build.git reflog on the junior's machine, then had both devs coordinate: one rebased, the other cherry-picked their commits onto the new base. Added a branch protection rule to disallow force-push on shared branches.- Never rebase a branch that more than one person has pushed to.
- If you must, coordinate with the team and expect a messy reconciliation.
git log --oneline to see if commits are duplicated. 2. Run git rebase --abort if still in progress. 3. Use git reflog to find the commit before rebase. 4. git reset --hard <pre-rebase-commit>. 5. Rebase again, but ensure you're rebasing onto the correct base (e.g., origin/master not master).--force. 2. Run git fetch origin. 3. Rebase your branch on the updated remote: git rebase origin/your-branch. 4. Resolve any conflicts. 5. Push again with --force-with-lease.git rebase --skip. 3. Be aware this removes the commit's changes permanently from this branch. 4. If unsure, abort with git rebase --abort.git rebase --abortgit log --oneline -1| File | Command / Code | Purpose |
|---|---|---|
| ugly-history.sh | git init ugly-history | Why Interactive Rebase Exists |
| interactive-rebase-basics.sh | cd ugly-history | The Anatomy of an Interactive Rebase |
| squash-vs-fixup.sh | cd ugly-history | Squashing |
| reword-commit.sh | git log --oneline -3 | Rewording |
| edit-split-commit.sh | git log --oneline -1 | Editing |
| drop-commit.sh | echo "console.log('API_KEY: ' + process.env.API_KEY)" >> debug.js | Dropping Commits |
| reorder-commits.sh | git log --oneline -3 | Reordering Commits |
| resolve-rebase-conflict.sh | git init conflict-demo | Resolving Conflicts During Rebase |
| production-rebase-workflow.sh | git checkout -b feature/payment-validation | Interactive Rebase Workflow |
| reflog-recovery.sh | git reflog | Recovering from a Botched Rebase |
Key takeaways
git branch backup/my-feature is cheap insurance.--force-with-lease instead of --force to avoid overwriting others' work.git reflog + git reset --hard can recover almost anything.Interview Questions on This Topic
How does interactive rebase handle merge conflicts differently than a merge? Why is this useful?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
That's Git. Mark it forged?
3 min read · try the examples if you haven't