Great work!

XP to next level

BugEater
EN

When an Interactive Rebase Goes Wrong

Learning Objectives

By the end of this lesson you will be able to:

  • Read where you are in a stopped rebase and decide what to do
  • Choose correctly between --continue, --skip and --abort
  • Recover a branch after a rebase you already finished and regret
  • Explain why a rebase is never actually unrecoverable

First: Find Out Where You Are

A stopped rebase is not an error state. It is a pause, and Git will tell you everything about it:

git status
interactive rebase in progress; onto e2b90d1
Last commands done (2 commands done):
   pick 1a77c60 test: extract payment fixture builder
   squash 9d3e072 fix
Next commands to do (2 remaining commands):
   pick 4c1f8ab test: add refund boundary cases
   pick 7b2e551 docs: update the test README
You are currently rebasing branch 'test/PAY-4417' on 'e2b90d1'.

Unmerged paths:
  both modified:   tests/payments/refund.spec.ts

Everything you need is there: what has been done, what remains, and what is blocking. Read this before typing anything. It is the most useful output in the whole module and most people scroll past it.

The Three Exits

--continue

You resolved the conflict, or amended the commit, and want to proceed.

git add tests/payments/refund.spec.ts
git rebase --continue

Requirement: nothing may still be unmerged. If a file still has conflict markers, Git refuses and says so.

--skip

Discards the commit currently being applied and moves to the next one.

git rebase --skip

This is right in exactly one common situation: the commit's changes are already present in the new base. Someone cherry-picked your fix into main last week, so replaying it produces a conflict against an identical change. Skipping it is correct — the change is not lost, it is already there.

It is wrong whenever you have not verified that. --skip throws the commit away, and it is the one option in this lesson that loses work if you guess.

--abort

git rebase --abort

Cancels everything and restores the branch exactly as it was before the rebase started. Always available, always complete, never a bad idea when you are unsure. It is the answer to "I don't understand what is happening" every time.

The Decision, In One Table

Situation Command
Conflict resolved and staged --continue
Amended the commit after edit --continue
This commit's change is already in the base --skip
I don't understand this conflict --abort
The plan was wrong to begin with --abort, then start over

Notice that two of the five rows are --abort. Aborting and running a simpler rebase is almost always faster than fighting a complicated one.

After the Rebase Finished — and You Regret It

--abort is only available during a rebase. Once it has completed, the branch has already been rewritten. This is where people panic, and it is completely unnecessary.

git reset --hard ORIG_HEAD

ORIG_HEAD is a reference Git sets automatically before any operation that moves the branch dramatically — rebase, merge, reset. It holds where you were. One command puts the branch back.

If you have run other commands since and ORIG_HEAD has moved on, the reflog has the full record:

git reflog
d4a91e0 HEAD@{0}: rebase (finish): returning to refs/heads/test/PAY-4417
d4a91e0 HEAD@{1}: rebase (squash): test: extract payment fixture builder
1a77c60 HEAD@{2}: rebase (start): checkout e2b90d1
7b2e551 HEAD@{3}: commit: docs: update the test README   ← the branch before the rebase
git reset --hard HEAD@{3}

The whole branch, exactly as it was. This is Module 5's subject, and it is the reason nothing in this module is genuinely dangerous — as long as you have not already force-pushed.

Pro Tip: Before a rebase you are nervous about, git branch backup/pre-rebase. It costs nothing, it makes recovery a git reset --hard backup/pre-rebase, and you delete it thirty seconds later when the rebase turns out fine.

The One Real Trap

The recovery paths above are all local. Once you have force-pushed a bad rebase and a colleague has fetched it, you are no longer recovering your own mistake — you are coordinating a fix with another person.

So: rebase, check the result, then push.

git log --oneline --graph main..HEAD    # is this the branch I meant?
git diff main...HEAD                    # is the final content still right?
git push --force-with-lease

The git diff main...HEAD step is worth the second it takes. It shows the cumulative change of your whole branch, which a correct history-rewrite leaves identical to what it was before. If that diff changed, you dropped or mis-squashed something.

Key Takeaways

  • git status during a rebase reports what is done, what remains, and what blocks it
  • --continue after resolving; --skip only when the change is already in the base; --abort whenever unsure
  • --skip discards a commit — it is the one option that loses work
  • After a finished rebase, git reset --hard ORIG_HEAD undoes it
  • The reflog recovers the branch even after ORIG_HEAD has moved on
  • A backup branch before a risky rebase costs nothing
  • Check git diff main...HEAD before force-pushing: a clean rewrite leaves it unchanged

Quiz

Which of the three exits from a stopped rebase can lose work?

When is git rebase --skip the correct choice?

The rebase completed, and you immediately want the old branch back. What is the shortest route?

Why check git diff main...HEAD after cleaning up a branch?