A rebase feels irreversible because it rewrites your commits — new SHAs, new parents, new order. But here's the thing: the old commits are untouched. Rebase creates copies and moves your branch pointer; the originals sit in the object database exactly as they were. Undoing a rebase means finding that old pointer and putting your branch back on it.
TL;DR:
# Still mid-rebase, conflicts open?
git rebase --abort
# Rebase already finished?
git reflog # find the tip from before "rebase (start)"
git reset --hard <sha> # often simply: git reset --hard ORIG_HEAD
Case 1: you're still inside the rebase
Conflicts on screen, (branch|REBASE 2/5) in your prompt, regret setting in. One command exits the whole operation:
git rebase --abort
Your branch returns to exactly the state it was in before you typed git rebase. Not "mostly" — exactly. This is the cleanest exit in git, and the one people forget exists while they're three conflicts deep.
Case 2: the rebase finished, but you haven't pushed
The shortcut: rebase records where your branch pointed before it started in ORIG_HEAD, so:
git reset --hard ORIG_HEAD
The catch: ORIG_HEAD gets overwritten by the next merge, rebase, or reset you run. If you've done anything since the rebase, don't gamble — use the reflog instead:
git reflog
You're looking for the line where the rebase began. The output reads newest-first and looks like:
a1b2c3d HEAD@{0}: rebase (finish): returning to refs/heads/feature
e4f5g6h HEAD@{1}: rebase (pick): add retry logic
...
7i8j9k0 HEAD@{8}: rebase (start): checkout main
2b3c4d5 HEAD@{9}: commit: last commit before the rebase
The entry directly below rebase (start) — 2b3c4d5 — is your pre-rebase tip. Put the branch back on it:
git reset --hard 2b3c4d5
git log --oneline -5
Verify the log looks like your old history, SHAs and all. It will — because those are your original commits, not reconstructions. Undoing an unpushed rebase is exact.
Undo only part of a rebase
Full undo is for full regret. If the rebase was ninety percent right and one pick was wrong, don't nuke the whole thing:
- For a branch you haven't pushed: run
git rebase -iagain and drop the offending pick, orgit rebase --ontoto relocate just the range you want. - For anything shared:
git revert <sha>the one bad commit and move on.
The reflog full-reset is the big hammer. Reach for it when the rebase itself was the mistake — wrong base, wrong branch, "why is my history suddenly unrecognizable" — not when a single commit is.
Case 3: you already pushed the rebase
Now the remote carries the rebased history. Restore locally as above, then overwrite the remote — carefully:
git push --force-with-lease
Always --force-with-lease over plain --force: it refuses to push if the remote moved since your last fetch, so you can't silently clobber a teammate's commit you haven't seen.
But understand what the flag does not protect: anyone who already pulled the rebased branch. Their clones now contain history you're about to delete from the remote, and their next pull becomes a diverged mess they have to reset --hard out of — or they accidentally merge the rebased commits back in, resurrecting the very thing you undid. So before force-pushing the undo:
- Tell everyone who pulls this branch. "I'm restoring
feature-xto its pre-rebase state; after I push, rungit fetch && git reset --hard origin/feature-x." - If a teammate has built work on top of the rebased commits, the undo may cost more than it fixes. Sometimes the adult move is leaving a bad rebase in place and fixing forward.
Rebased onto the wrong base entirely?
If the branch itself is fine but sitting on the wrong foundation — say you rebased onto develop when you meant main — you don't need to undo first. Transplant directly with --onto:
git rebase --onto main develop feature-x
That reads as "take the commits on feature-x that came after develop, and replay them onto main." One command, no detour through the old state. The reflog still covers you if even this goes sideways.
Dropped a commit during an interactive rebase?
Same recovery, smaller blast radius. That drop line in your rebase -i didn't delete anything — the commit still exists in the object store. Find it in the reflog (it's the commit right before the one you rebased onto it, or search your shell history for its SHA) and either cherry-pick it back onto your branch, or reset the whole branch to the pre-rebase tip as above.
Make the next rebase boring
Rebases will keep happening; panics don't have to. Drop a thirty-second backup branch before any rebase more complicated than a fast-forward, and recovery becomes git reset --hard backup/before-rebase instead of reflog archaeology. If you work with a dirty tree, git config --global rebase.autoStash true stops "cannot rebase: you have unstaged changes" from tempting you into a hasty git stash you'll forget about. And when the branch is shared, default to --force-with-lease for every force push so the worst case is a rejected push, not a clobbered teammate.
Restore the pre-rebase state with Time Travel
Point at the commit your branch sat on before the rebase. git.tools opens a pull request that puts it back in exactly that state — reviewed by your team, no force push, no reflog spelunking.
Open Time Travel →Common questions
Does undoing a rebase restore the original SHAs?
Yes, exactly. The pre-rebase commits were never modified — rebase only made copies. Resetting to the pre-rebase tip brings back the identical commits, hashes included.
I've committed new work on top of the rebased branch. Can I still undo?
Yes, but a plain reset --hard to the pre-rebase tip would orphan your new commits too. Instead: note the SHAs of the new commits, reset to the pre-rebase tip, then git cherry-pick the new commits back on. The reflog keeps the full trail if you lose track.
A teammate already pulled my rebased branch. Now what?
Coordinate before you force-push the undo. They'll need to git fetch and git reset --hard origin/<branch> to realign, and any local work they based on the rebased commits needs to be rebased or cherry-picked onto the restored branch. If that sounds expensive, weigh fixing forward instead.
What's the difference between rebase --abort and the reflog method?
--abort only works while a rebase is still in progress. The moment it finishes (or you --continue past the last conflict), --abort is off the table and the reflog/ORIG_HEAD reset is your path. Same destination, different door.
Does git pull --rebase count as a rebase I can undo?
Yes — it's a fetch plus a rebase, and the rebase half sets ORIG_HEAD and writes reflog entries like any other. If a pull --rebase just rewound and replayed your local commits in a way you hate, the same git reset --hard ORIG_HEAD (or the reflog) puts you back.
Is there any case where a rebase truly can't be undone?
Locally, practically never — not until garbage collection prunes the old commits weeks later, and not if you act now. The genuinely hard case is when the rebase was pushed long ago, others built on it, and "undo" would mean rewriting history a whole team depends on. That's not a git problem anymore; that's a negotiation.