Interactive Rebase
git rebase -i (interactive) is the swiss-army knife for cleaning up commits before they get merged. Reorder, squash, edit messages, drop, split — all in one editor session.
The basic command
git rebase -i HEAD~5 # rewrite the last 5 commits
git rebase -i main # rewrite every commit on this branch since branching from main
git rebase -i abc1234 # rewrite every commit since abc1234
Git opens an editor with one line per commit:
pick a1b2c3d Add user model
pick d4e5f6g Fix typo in user model
pick h7i8j9k Add user serializer
pick k0l1m2n WIP debug logging
pick n3o4p5q Fix bug in serializer
# Rebase abc1234..n3o4p5q onto abc1234 (5 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like squash, but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
Edit, save, close. Git replays the commits in the new order with the new actions.
The commands you’ll actually use
| Command | Effect |
|---|---|
pick |
keep as-is |
reword (r) |
keep, but edit the commit message |
edit (e) |
stop after applying so you can amend (split, add files, change content) |
squash (s) |
combine into the previous commit, edit the combined message |
fixup (f) |
combine into the previous commit, discard this message |
drop (d) |
remove this commit entirely |
exec (x) |
run shell command between commits (e.g. x npm test to verify each step) |
Reorder by moving lines. The order in the file is the order they’ll be applied.
Common patterns
Squash WIP commits before pushing
pick a1b2 Implement feature
pick d4e5 WIP
pick h7i8 WIP fix
pick k0l1 Final
Change to:
pick a1b2 Implement feature
fixup d4e5 WIP
fixup h7i8 WIP fix
fixup k0l1 Final
Result: one commit, message from a1b2. Clean PR.
Reword the most recent commit
git commit --amend -m "Better message" # for the very last commit
git rebase -i HEAD~3 # change `pick` to `reword` for older commits
Split one commit into two
Mark edit on the commit:
edit a1b2 Big mixed commit
When rebase stops:
git reset HEAD~ # un-stage the commit's changes (keeps them in working dir)
git add file1.py
git commit -m "First half"
git add file2.py
git commit -m "Second half"
git rebase --continue
Drop a commit
Change pick to drop (or just delete the line). The commit’s changes are removed from history.
Reorder commits
Move lines around. Useful to group related commits before squashing.
--autosquash — the workflow trick
When you spot a typo in commit a1b2, instead of an immediate fixup rebase:
git commit --fixup a1b2 # creates "fixup! original message"
# ... continue working, more commits ...
git rebase -i --autosquash main
--autosquash automatically reorders fixup! commits next to their target and marks them as fixup. Combined with git config rebase.autoSquash true, it’s automatic.
Pair with git config rebase.autoStash true so uncommitted work is auto-stashed and re-applied.
When the rebase pauses
Rebase stops on:
- Conflicts.
editcommits.execcommands that fail.
Options:
git rebase --continue # after fixing/staging the conflict or completing the edit
git rebase --skip # skip this commit entirely (rare)
git rebase --abort # bail out, return to pre-rebase state
--abort is your friend. If a rebase gets confusing, abort and try again — your reflog has the original branch state.
Conflicts during rebase
You resolve conflicts per commit being replayed, not per branch (unlike merge). For a branch with 10 commits where 3 touch the same file:
- Commit 4 conflicts → resolve,
git add,git rebase --continue. - Commit 7 conflicts (same file, different change) → resolve, continue.
- Commit 9 conflicts → resolve, continue.
This is annoying. Two mitigations:
git rerere(reuse recorded resolution): records how you resolved each conflict and replays automatically. See 12_conflict_resolution.md.- Squash first, then rebase: if you collapse the 10 commits into 1, you only resolve once.
Rebasing a branch onto a different base
git rebase --onto main old-base feature
“Take commits in feature that come after old-base and replay them on main.” Used to move a branch from one base to another.
Common example: you branched from dev for featureA, then branched from featureA for featureB. After featureA merges to dev, you want featureB to be based on dev directly:
git rebase --onto dev featureA featureB
Editor caveats
git rebase -i opens your $GIT_EDITOR (or $EDITOR). If it’s vi/vim and you don’t know how, set:
git config --global core.editor "code --wait" # VS Code
git config --global core.editor "nano" # nano
--wait matters for VS Code — without it, git proceeds before you save.
Don’t rebase shared history
Same rule as plain rebase (02_merge_vs_rebase.md). Interactive rebase rewrites SHAs; pushed commits get duplicated for collaborators on git pull. Only rewrite local-only or branches-you-own.
If you must, push with --force-with-lease (safer than --force) and let collaborators reset:
git fetch
git reset --hard origin/feature
Common interview confusions
- “
squashandfixupare the same.” — both combine, but squash opens an editor to merge messages; fixup silently discards the second message. Use fixup when the second commit is just “fix typo from previous.” - “Rebase loses my work if conflicts get bad.” —
git rebase --abortreturns you to the pre-rebase state. And the original commits are in reflog for ~90 days regardless. - “
git commit --amendis the same as squash via interactive rebase.” —--amendonly modifies the most recent commit. For older commits, use interactive rebase.
Interview angle
- “What’s interactive rebase used for?” — cleaning up history before merging: squash WIPs, reword messages, reorder commits, drop accidental commits, split mixed commits.
- “
squashvsfixup?” — both meld into the previous commit; squash lets you edit the combined message, fixup silently keeps only the first commit’s message. - “How do you split one commit into two?” —
editit in interactive rebase; when stopped,git reset HEAD~to unstage, then make two commits, thengit rebase --continue. - “What’s
--autosquashand the workflow it enables?” —git commit --fixup <sha>creates a marked commit; laterrebase -i --autosquashauto-positions and fixups it. Lets you fix old commits without stopping current work. - “How do you safely move commits from one branch to another base?” —
git rebase --onto new-base old-base branch— takes commits inbranchpastold-baseand replays onnew-base. - “What if the interactive rebase goes wrong?” —
git rebase --abortto return to start. Failing that,git reflogto find the pre-rebase HEAD andgit reset --hard <sha>.