Interactive Rebase
git rebase -i replays a range of commits one at a time through an editable to-do list. Between "this is the messy series of commits I actually made" and "this is the clean history I want to share," interactive rebase is the tool that lets you reorder, combine, drop, reword, or pause on each commit before anyone else sees it.
The power comes with one hard boundary: every commit you touch gets a new hash, so this is strictly a tool for local history. Run it on commits others have already pulled and you force every teammate to recover from a base that no longer exists for them. Used on your own unshared work, it is the difference between a reviewable branch and an apologetic one.
The To-Do List
When you run git rebase -i <base>, Git opens an editor with one line per commit and a verb in front of each: pick, reword, edit, squash, fixup, drop, plus exec and break for scripting and pausing. The list runs top-to-bottom, oldest commit first — the reverse of what git log shows you. Internally this is just a rebase: Git resets to the base and replays each line in order, applying whatever verb you chose.
Because the order on screen is oldest-first, reordering means dragging the earlier commit up, not down. Getting this backwards is the single most common stumble for people who think in git log order.
Squash vs Fixup
Both squash and fixup fold a commit into the one above it. The difference is entirely about messages. squash opens an editor so you can combine both commit messages into one. fixup keeps the target commit's message and silently discards the squashed commit's message. Reach for fixup when the commit is an "oops, typo" with nothing worth keeping in its message, and squash when both messages carry information you want merged.
Reordering, Dropping, and Editing
Rearranging lines changes the apply order; deleting a line entirely drops that commit. Conflicts surface per replayed commit rather than all at once, which is why a long rebase can stop several times — each stop is one commit failing to apply cleanly. The edit verb stops the replay after applying a commit so you can amend its tree, change its author, or split it into several commits, then continue with git rebase --continue.
To split a fat commit, mark it edit, let the rebase stop on it, run git reset HEAD^ to un-commit it back into the working tree, then stage and commit it in pieces before continuing. Dropping a commit is equally direct, but watch for later commits that silently depended on the one you removed — the dependency surfaces as a conflict, not a warning.
The Autosquash Workflow
Rather than hand-sorting fixups, mark them as you go with git commit --fixup=<sha>, which writes a commit whose message is fixup! <original subject>. Later, git rebase -i --autosquash reads those markers, moves each fixup directly beneath its target, and pre-marks it as a fixup line. --autosquash matches a fixup! line to its target by the original commit's subject, so it pairs them up without you touching the to-do list at all.
Recovery When It Goes Wrong
If a rebase goes sideways, git rebase --abort returns the branch to exactly where it started. Even after a rebase completes, the original tip is preserved in the reflog as ORIG_HEAD, so git reset --hard ORIG_HEAD undoes a finished rebase you regret. That safety net is why local rebasing is low-risk: the pre-rebase state is always recoverable until the reflog expires.
squash — folds the commit into the one above and opens an editor so you can merge both commit messages into a single combined message. Choose it when both commits' messages carry information worth keeping.
fixup — folds the commit in the same way but keeps the target's message and silently discards the fixup commit's message. Choose it for "oops, typo" commits whose message adds nothing.
- Rebasing commits already pushed and shared, then force-pushing — every teammate who pulled the old base is forced into a painful recovery to reconcile the rewritten commits.
- Hitting a conflict and running
git rebase --continuewithout staging the resolution, which commits the file with conflict markers still in it. - Forgetting the to-do list is oldest-first and reordering commits in the wrong direction, producing an apply order you did not intend.
- Using
--autosquashwithout settingrebase.autosquash=trueor passing the flag, sofixup!commits land as ordinary commits instead of being folded. - Dropping a commit by deleting its line without noticing that a later commit depended on it, turning the removal into a mid-rebase conflict.
- Mark cleanup commits with
git commit --fixup=<sha>as you work, then collapse them in one pass withgit rebase -i --autosquash. - Set
git config --global rebase.autosquash trueso autosquash is implicit on every interactive rebase. - Abort cleanly with
git rebase --abortthe moment a replay goes wrong, rather than trying to hand-unwind a half-finished rebase. - Split a fat commit by marking it
edit, runninggit reset HEAD^, and re-staging the change in coherent pieces. - Verify every rewritten commit still builds with
git rebase -i --exec "make test", which runs the command after each commit.
hg histedit is a direct analog, plus hg rebase and the evolve extension for safe mutationSubversion no equivalent — history is immutable once committedPerforce no client-side interactive rewriteFossil deliberately forbids history rewriting by designKnowledge Check
Why is rebasing local history fine but rebasing shared history destructive?
- Rebase rewrites commits with new hashes; on shared commits, teammates who pulled the old base must recover, while local commits affect no one
- Rebase deletes and rewrites the working tree files on disk, which only starts to cause problems once other people already have their own copy of that same tree
- Shared commits are locked by the server once pushed, so Git cannot replay or rewrite them at all without admin rights
- Local rebases skip conflict resolution entirely and fast-forward the branch, which is the reason they are considered safe
How does fixup differ from squash in an interactive rebase?
squashopens an editor to combine both messages;fixupkeeps the target's message and discards the folded commit's messagefixupkeeps the two commits separate in the to-do list whilesquashis the verb that actually merges them togetherfixupmoves the commit earlier in the to-do order whilesquashleaves the order alone and only rewrites the message- They behave identically and produce the same combined commit; the two names are simply interchangeable aliases for one verb
Why do conflicts appear per-commit during a rebase but only once during a merge?
- Rebase replays each commit individually onto the new base, so any commit can conflict; a merge computes one combined result against the common ancestor
- Merge always resolves every overlapping change automatically by preferring the incoming side, so conflicts never surface to you
- Rebase conflicts are cosmetic markers that Git cleans up on its own, so you can run --continue without resolving anything
- A merge replays each commit one by one onto the target branch, while a rebase instead batches all of them into a single combined three-way application
What does ORIG_HEAD let you do after a rebase completes?
- Reset back to the pre-rebase tip with
git reset --hard ORIG_HEAD, undoing the finished rebase - Replay the whole rebase again from scratch onto a different base branch in one step
- Push the rewritten commits to the remote with a plain push, since the old tip is preserved
- Recover uncommitted working-tree edits that were lost when the rebase checked out each commit
You got correct