backend / git / 03_interactive_rebase.md

Interactive Rebase

6 interview angles 5 min read source

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.
  • edit commits.
  • exec commands 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:

  1. Commit 4 conflicts → resolve, git add, git rebase --continue.
  2. Commit 7 conflicts (same file, different change) → resolve, continue.
  3. 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

  • squash and fixup are 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 --abort returns you to the pre-rebase state. And the original commits are in reflog for ~90 days regardless.
  • git commit --amend is the same as squash via interactive rebase.”--amend only 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.
  • squash vs fixup?” — 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?”edit it in interactive rebase; when stopped, git reset HEAD~ to unstage, then make two commits, then git rebase --continue.
  • “What’s --autosquash and the workflow it enables?”git commit --fixup <sha> creates a marked commit; later rebase -i --autosquash auto-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 in branch past old-base and replays on new-base.
  • “What if the interactive rebase goes wrong?”git rebase --abort to return to start. Failing that, git reflog to find the pre-rebase HEAD and git reset --hard <sha>.