All posts
·12 min read· rebase· undo· git· reflog· recovery

How to undo a Git rebase (with ORIG_HEAD and reflog)

Undo a Git rebase and recover the pre-rebase state. Use git reset --hard ORIG_HEAD for the fast undo, git reflog when it is gone, and git rebase --abort mid-conflict.

You ran git rebase main on your feature branch. The terminal scrolled, said it finished, and now git log looks wrong. Commits are missing. A change you made yesterday is not there. Or the branch points at something you do not recognise.

Your first thought is the scary one: did the rebase delete my work?

It did not. A rebase almost never destroys commits. It moves your branch to point at a new chain of commits, and the old chain is still sitting in the repository. Git even wrote down where your branch was before the rebase started, in two different places, just so you can go back.

This post shows you how to go back. There are three situations, and they need different commands:

  1. The rebase finished and you want to undo it — use ORIG_HEAD.
  2. ORIG_HEAD is gone because you ran another command after the rebase — use git reflog.
  3. The rebase is still running and paused on a conflict — use git rebase --abort.

We will cover all three, in that order. By the end you will know which one applies to you and the exact command to type.

What ORIG_HEAD is

Before we touch the commands, you need to know one thing about Git. It keeps a pointer called ORIG_HEAD.

When you run a command that moves your HEAD in a big way, Git first saves where HEAD was into ORIG_HEAD. The official Git docs call these "drastic" moves and list exactly which commands do it: git am, git merge, git rebase, and git reset. The point of saving the old position is, in the words of the docs, "so that you can easily change the tip of the branch back to the state before you ran them."

So ORIG_HEAD is a bookmark. It says "here is where your branch was right before the last drastic thing you did." For a rebase, that means ORIG_HEAD points at your branch tip from before the rebase. The git rebase documentation confirms it: "When starting the rebase, ORIG_HEAD is set to point to the commit at the tip of the to-be-rebased branch."

ORIG_HEAD is not the same as HEAD. HEAD is where you are now (after the rebase). ORIG_HEAD is where you were (before the rebase). To undo the rebase, you move HEAD back to where ORIG_HEAD points.

That is the whole idea. The rest is one command.

The fast path: git reset --hard ORIG_HEAD

If you just finished a rebase and nothing else has happened since, this is your undo:

git reset --hard ORIG_HEAD

This tells Git: move my current branch back to the commit ORIG_HEAD points at, and make my working tree match it exactly. Since ORIG_HEAD is your pre-rebase tip, your branch is now back to exactly where it was before you started the rebase. The commits the rebase created are abandoned. Your original commits are the branch tip again.

Let me show the shape of it. Say your feature branch had these commits before the rebase:

9f4e2c8 feat: add retry queue
6c1a5e3 feat: webhook handler
3d8f2b1 feat: payment scaffold

You run git rebase main, it finishes, and now the SHAs are different and something looks off. Check what ORIG_HEAD holds:

git rev-parse ORIG_HEAD

That prints the SHA of 9f4e2c8 — your tip from before the rebase. Now undo:

git reset --hard ORIG_HEAD
git log --oneline -3

You are back to the three original commits. The rebase is undone.

One safety note before you run reset --hard. The --hard flag throws away uncommitted changes in your working tree. If you have edits you have not committed, stash them first with git stash, run the reset, then git stash pop. If everything you care about is committed, --hard is exactly what you want, because it restores both the branch pointer and the files in one step.

A reminder on scope: git reset --hard ORIG_HEAD here is undoing a whole rebase. Using git reset to undo a single commit, and the difference between reset and revert, is a separate topic. We cover that in how to undo a Git commit.

The robust path: git reflog when ORIG_HEAD is gone

ORIG_HEAD is only one pointer. It gets overwritten every time you run another drastic command. So this can happen:

  1. You finish a rebase. ORIG_HEAD = pre-rebase tip. Good.
  2. You then run a second git rebase, or a git merge, or a git reset.
  3. Now ORIG_HEAD = the tip from before that second command, not before your first rebase.

The Git rebase docs warn about this directly: "ORIG_HEAD is not guaranteed to still point to that commit at the end of the rebase if other commands that change ORIG_HEAD (like git reset) are used during the rebase." The same is true after the rebase. The moment another drastic command runs, the old ORIG_HEAD is replaced.

When that happens, you need a fuller record. That is the reflog. The reflog is a log of every move your HEAD made — every commit, checkout, merge, reset, and every step of every rebase. It keeps the old positions around for at least 30 days. The git reflog docs describe it as a record of "when the tips of branches and other references were updated in the local repository."

To find your pre-rebase state, look at the reflog:

git reflog

You will see something like this:

de38a49 HEAD@{0}: rebase (finish): returning to refs/heads/feature
de38a49 HEAD@{1}: rebase (pick): feat: webhook handler
b1198c3 HEAD@{2}: rebase (start): checkout main
4b48515 HEAD@{3}: checkout: moving from main to feature
b1198c3 HEAD@{4}: commit: main change

Read it top-down as "what HEAD did most recently, going backwards." The line you care about is rebase (start). That marks the moment the rebase began. The entry just below it — the one with the older position — is where your branch was before the rebase touched it. In the example above, that is HEAD@{3}, which is 4b48515 (the checkout: moving from main to feature line, recorded right after you landed on the branch and before the rebase moved it).

A note on the entry text. On current Git, the start of an interactive rebase is also written as rebase (start), with the target after it, like rebase (start): checkout HEAD~2. Older Git versions and some setups wrote rebase -i (start) for interactive rebases. Either way, look for the (start) marker — that is your landmark. I verified this on Git 2.49.

Once you have spotted the entry, reset to it. You can use either the reflog reference or the raw SHA:

git reset --hard HEAD@{3}
# or, the same thing by SHA:
git reset --hard 4b48515

HEAD@{3} means "where HEAD was three moves ago," using the reflog. The gitrevisions docs define it: "HEAD@{2} means 'where HEAD used to be two moves ago'." After this reset, your branch is back to its pre-rebase tip, the same as the ORIG_HEAD path would have given you — except this works even when ORIG_HEAD has moved on.

If you want the full tour of reflog, including recovering deleted branches and reset mistakes, read git reflog: the undo button you didn't know you had. It is the deeper version of this section.

There is a live version of this exact problem too. The rebase ate my commits scenario drops you into a seeded repo where a rebase has lost teammate commits, with a real terminal underneath, so you can practice the reflog recovery with nothing real at risk.

Aborting a rebase that is still running

The two paths above are for a rebase that finished. There is a different situation: the rebase is still in progress and paused on a conflict.

When a rebase hits a conflict, it stops and waits. You will see something like:

CONFLICT (content): Merge conflict in schema.sql
error: could not apply b4c8e2f... feat: add field_b
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".

At this point the rebase is half-done. Some commits have been replayed, the rest are waiting, and you are in the middle. If you decide you do not want to continue, do not reach for reset --hard. Use the dedicated command:

git rebase --abort

The git rebase docs describe it: "Abort the rebase operation and reset HEAD to the original branch... HEAD will be reset to where it was when the rebase operation was started." It puts your branch back exactly where it was before the rebase, cleans up the half-finished state, and leaves you on solid ground. It is the cleanest way to back out of a rebase that has not finished.

The key difference: git rebase --abort only works while a rebase is in progress. If you try it when no rebase is running, Git refuses:

git rebase --abort
# fatal: no rebase in progress

(I confirmed that exact message on Git 2.49.) So the rule is simple. If you are paused on a conflict and want out, git rebase --abort. If the rebase already said it finished, --abort will not help — use git reset --hard ORIG_HEAD instead.

Here is the same split as a table:

Your situationCommand
Rebase paused on a conflict, you want outgit rebase --abort
Rebase finished, you want to undo itgit reset --hard ORIG_HEAD
Rebase finished, ORIG_HEAD overwrittengit reset --hard HEAD@{n} (from reflog)

"Did I lose my commits?"

Almost certainly not. This is the most common worry after a rebase, and it is usually wrong.

Here is why. A rebase does not edit your old commits in place. It creates new commits with the same changes and moves your branch to point at them. The old commits are still in the repository as objects. They are just not on your branch anymore. Two pointers know where they are:

So your pre-rebase state has two safety nets. To actually lose a commit for good, you would have to wait past the reflog window (a month or more) and run garbage collection, or never have committed the work in the first place. A normal "my rebase went wrong" is fully recoverable.

The mental model worth keeping: a rebase moves a pointer, it does not delete history. The reflog post goes deeper on this, including how long the recovery window lasts and how to extend it.

If you already force-pushed the rebased branch

There is one case that needs an extra step. After a rebase, the SHAs change, so to update the remote you have to force-push:

git push --force-with-lease

If you already did that, then the remote now has the rebased version too. Undoing locally with reset --hard ORIG_HEAD fixes your machine, but the remote still holds the bad rebase. You have to push the undo as well, and because you are again moving the branch backwards, that push also needs force:

git reset --hard ORIG_HEAD     # fix locally
git push --force-with-lease    # publish the fix

Use --force-with-lease, not plain --force. The git push docs explain the difference: plain --force overwrites the remote ref no matter what, which can erase a teammate's commit if they pushed while you were working. --force-with-lease first checks that the remote ref still points where you last saw it. If someone else pushed in the meantime, the push is refused, and you get a chance to pull their work before overwriting. It is the same safety check, applied to your undo.

If a teammate had already pulled the rebased branch before you fixed it, give them a heads-up. They will need to reset their local copy to match the corrected remote, the same way you fixed yours.

Common myths

Myth 1: "A bad rebase loses your commits permanently." Wrong in the normal case. A rebase builds new commits and moves your branch to them; the originals stay in the object database. ORIG_HEAD points at your pre-rebase tip until the next drastic command, and the reflog keeps it for at least 30 days after that. You would have to wait out the reflog window and run garbage collection to truly lose committed work. For the ordinary "I rebased and it went wrong" case, recovery is one git reset --hard away.

Myth 2: "ORIG_HEAD and HEAD are the same thing." No. HEAD is where you are now — after the rebase. ORIG_HEAD is where you were — before the rebase. Git sets ORIG_HEAD to the old tip precisely so the two differ after a drastic move. That gap is what makes git reset --hard ORIG_HEAD an undo. If they were the same, the command would do nothing. Run git rev-parse HEAD and git rev-parse ORIG_HEAD after a rebase and you will see two different SHAs.

Myth 3: "You can git rebase --abort after the rebase finished." No. --abort only works while a rebase is in progress — paused on a conflict, mid-replay. Once the rebase has finished, there is no rebase to abort, and Git tells you so: fatal: no rebase in progress. To undo a finished rebase you use git reset --hard ORIG_HEAD, or the reflog if ORIG_HEAD has moved on. Abort is for the live operation; reset is for the completed one.

What to read next

If you remember one line from this post: a finished rebase is undone with git reset --hard ORIG_HEAD, a paused one is backed out with git rebase --abort, and the reflog has your back when ORIG_HEAD is gone. None of them lose your commits.