Skip to main content

git rebase โ€” Replaying Commits

What It Doesโ€‹

git rebase moves or replays a sequence of commits onto a new base. It rewrites commit history by creating new commits with the same changes but different parent commits (and therefore new SHA hashes).

The fundamental difference from merge:

BEFORE (both start from C):
main: A -- B -- C -- D -- E
\
feature: F -- G -- H

AFTER git merge main (creates a merge commit):
feature: A -- B -- C -- F -- G -- H -- M
\ /
D -- E --------

AFTER git rebase main (replays F, G, H on top of E):
feature: A -- B -- C -- D -- E -- F' -- G' -- H'
(new SHA hashes)

Rebasing produces linear history โ€” as if the feature branch was started from the latest point on main.


Standard Rebase (Update a Branch)โ€‹

The most common use: bring your feature branch up-to-date with main before opening a PR.

git switch feature/JIRA-123
git fetch origin
git rebase origin/main

If there are conflicts during replay, Git pauses on the conflicting commit:

# 1. Resolve conflicts in your editor
# 2. Stage the resolved files
git add src/main/java/com/example/TransactionService.java

# 3. Continue replaying the remaining commits
git rebase --continue

# Or abort and return to the state before rebase started
git rebase --abort

# Or skip the problematic commit (use only if you're certain it's safe)
git rebase --skip

Interactive Rebase (-i)โ€‹

Interactive rebase is one of Git's most powerful features. It lets you edit, reorder, squash, split, drop, or rename commits before they are pushed or merged.

# Rebase the last 5 commits interactively
git rebase -i HEAD~5

# Rebase all commits since branching from main
git rebase -i origin/main

Git opens your editor with a list of commits, oldest at the top:

pick a3f9bc2 feat(transactions): add repository method
pick 7d1e4f0 wip: controller scaffolding
pick 2c8a1b3 fix typo
pick 9f3e2d1 feat(transactions): add service logic
pick 1a4b5c6 feat(transactions): add controller endpoint

# Commands:
# p, pick = use commit as-is
# r, reword = use commit but edit the message
# e, edit = use commit but pause to amend
# s, squash = meld into previous commit (combine messages)
# f, fixup = meld into previous commit (discard this message)
# d, drop = remove the commit entirely
# b, break = pause here (useful for splitting commits)

Common patternsโ€‹

Squash WIP commits into a clean feature commit:

pick a3f9bc2 feat(transactions): add repository method
squash 7d1e4f0 wip: controller scaffolding
squash 2c8a1b3 fix typo
squash 9f3e2d1 feat(transactions): add service logic
squash 1a4b5c6 feat(transactions): add controller endpoint

Reorder commits (change lines order):

pick a3f9bc2 feat: add domain model
pick 9f3e2d1 feat: add service logic
pick 7d1e4f0 wip: controller scaffolding โ† move this down
pick 1a4b5c6 feat: add controller

Drop a commit entirely:

pick a3f9bc2 feat: add domain model
drop 7d1e4f0 wip: debug logging I don't want
pick 9f3e2d1 feat: add service logic

Reword a commit message:

reword a3f9bc2 feat: add transaction filter โ† Git will pause to edit this message
pick 9f3e2d1 feat: add service logic

Rebase onto Another Branchโ€‹

Move a branch from one base onto a completely different branch:

# Scenario: feature-b was accidentally branched from feature-a,
# but should have been branched from main

git rebase --onto main feature-a feature-b
Before:
main: A -- B -- C
\
feature-a: D -- E
\
feature-b: F -- G

After: git rebase --onto main feature-a feature-b
main: A -- B -- C -- F' -- G'

Autosquashโ€‹

When combined with --fixup commits, --autosquash automatically rearranges the rebase todo list:

# Create a fixup commit targeting an earlier commit
git commit --fixup a3f9bc2

# During interactive rebase, autosquash arranges it automatically
git rebase -i --autosquash origin/main

See Fixup for the full workflow.


When NOT to Rebaseโ€‹

Never rebase shared branches

Do not rebase any branch that other team members have pulled and based work on. Rebase rewrites SHA hashes โ€” anyone who has the old SHAs will have a diverged history that is painful to reconcile.

Safe to rebase: your own local feature branch before pushing
Safe to rebase: a pushed feature branch only you are working on (use --force-with-lease)
NEVER rebase: main, develop, release/**, or any branch shared with others


Useful Flags Summaryโ€‹

FlagMeaning
-i / --interactiveOpen interactive editor
--onto <newbase>Rebase onto a different base
--continueContinue after resolving conflicts
--abortCancel rebase and restore original state
--skipSkip the current conflicting commit
--autosquashAuto-arrange fixup/squash commits
--autostashStash dirty working tree before rebase, pop after
--no-ffCreate a merge commit instead of fast-forward at the end
-x "cmd"Run a shell command after each replayed commit

Interview Questionsโ€‹

Q: Why is rebasing shared branches dangerous?โ€‹

A: It rewrites commit SHAs and invalidates other developers' branch ancestry, creating painful divergence.

Q: When do you prefer rebase over merge in day-to-day work?โ€‹

A: Use rebase to keep feature branch history linear before review; use merge to integrate completed work into shared branches.

Q: How does interactive rebase improve code review quality?โ€‹

A: It groups commits into logical units, removes noise, and clarifies narrative intent for reviewers.

Q: What is a safe force-push policy after rebase?โ€‹

A: Use --force-with-lease only on branches you own, after confirming no one else based work on old SHAs.

Q: How do you recover if a rebase went wrong?โ€‹

A: Use git reflog to locate pre-rebase HEAD and reset back to it.

Q: What indicates a team is overusing rebase?โ€‹

A: Frequent history rewrites on collaborative branches and recurring confusion about missing/duplicated commits.