git.tools / guides

How to undo git reset --hard

Just watched git reset --hard eat your work? Committed work is almost always recoverable through the reflog. Uncommitted work is grimmer — here's the honest version of both.

Last updated September 18, 2026 · 7 min read

First, understand what git reset --hard actually did: it moved your branch pointer backwards and overwrote your working tree and staging area to match. It did not delete any commits — it orphaned them. Whether you're saved depends on one question: was the lost work ever committed, or at least staged?

TL;DR (committed work):

git reflog                  # find the commit from before the reset
git reset --hard <sha>      # or safer: git branch recovery <sha>

Case 1: the work was committed — you're fine

Git's reflog records every move HEAD made in your clone, including the reset itself. Run it:

git reflog

You'll see something like:

d4e5f6a HEAD@{0}: reset: moving to HEAD~2
9b8c7d2 HEAD@{1}: commit: add payment retries
1a2b3c4 HEAD@{2}: commit: wire up webhook handler

The reset: moving to... line is the mistake. The line directly below it — 9b8c7d2 — is where your branch pointed one second before. Two ways to go back.

The safe way: park the recovered state on a new branch and look at it before committing to anything.

git branch recovery 9b8c7d2
git log recovery --oneline -5

If it looks right, reset your branch to it (or just switch to recovery and carry on).

The direct way, when you're sure:

git reset --hard 9b8c7d2

Yes, another reset --hard — it's fine. Resets are recorded in the reflog too, so you can undo the undo. The work was never gone; the pointer just stopped looking at it.

Reading a reflog line

Each entry is <sha> HEAD@{n}: <action>: <detail>, newest first. The action label tells you what moved HEAD: commit: for commits, reset: for resets, checkout: for branch switches, rebase (start): for rebases. When hunting, scan the actions, not the messages — the entry before the reset: line is your pre-reset state, whatever its commit message says.

Two refinements that help under pressure:

Case 2: it was staged but never committed — maybe recoverable

If you ran git add at any point, git stored those file contents as objects in its database. They're now "dangling blobs," and git fsck can find them:

git fsck --lost-found

Look for dangling blob <sha> lines — blobs are your staged file contents. The dangling commit lines are orphaned commits, which is Case 1 material: git show <sha> to inspect, git branch recovery <sha> to resurrect outright.

fsck also copies what it finds into .git/lost-found/other/. The catch with blobs: they have no filenames. Git stored the content of your staged api/routes.ts, but the name lived in a tree object that may not have survived. You recover by inspecting blobs one by one:

git show <sha>

With many blobs, loop and grep for a distinctive string from your lost file:

for sha in $(git fsck --lost-found 2>/dev/null | awk '/dangling blob/ {print $3}'); do
  git show "$sha" | grep -q "distinctive-string" && echo "FOUND: $sha"
done

Tedious, but it has saved real work. Note this only covers content that was staged — git add is the moment git takes its snapshot.

Case 3: never staged, never committed — the honest answer

If the changes only ever existed in your working tree, git never saw them, and no git command can bring them back. Anyone who tells you otherwise is guessing. Your real options live outside git:

Take the lesson cheap: commit early, or at least git stash / git add before any reset. A thirty-second backup branch turns this whole article into a non-event.

How long do you have?

Not forever, but longer than the panic suggests:

Two practical rules: don't run git gc --prune=now or any aggressive cleanup while you're recovering, and don't wait. Recovery today is a certainty; recovery next month is a coin flip.

Edge cases worth knowing

Untracked files are not touched by reset --hard. If your brand-new never-added files vanished, the culprit was git clean — a different command with a much grimmer recovery story (usually: OS-level backups or nothing).

Mid-merge or mid-rebase resets often have a shortcut: ORIG_HEAD points at where the branch was before the operation, so git reset --hard ORIG_HEAD can be a one-liner — but only until another operation overwrites it, which is why the reflog is the reliable path.

A reset that only moved the pointer — e.g. you reset to the wrong commit but your working tree was clean — is the same Case 1. Reflog, find the SHA, go back.

The git.tools way

Put the branch back with Time Travel

If the branch was pushed before the reset, git.tools can restore it to any earlier commit through a reviewed pull request — no second force push, no guessing SHAs under pressure.

Open Time Travel →

Common questions

Does the reflog work in a fresh clone?

No. The reflog is local to your clone and records only what happened there. If the reset happened on another machine, or you re-cloned since, you need the SHA from somewhere else — GitHub's activity feed, a pull request, CI logs. Our force-push recovery guide covers that hunt.

Can I undo the undo?

Yes. Every reset appears in the reflog as a new entry, so git reset --hard is itself recoverable with another trip through git reflog. You can walk the pointer back and forth as many times as you need.

My commits were never pushed. Does reflog still have them?

Yes — reflog doesn't care about remotes at all. Committed locally is committed, pushed or not.

I dropped a stash, not a reset. Same trick?

Same family of trick. A stash is secretly two commits, and a dropped stash becomes a dangling commit that git fsck --lost-found can find. Once you have its SHA, git stash apply <sha> restores it. The reflog doesn't track stash drops, so fsck is the tool here rather than git reflog.

Will git gc delete my orphaned commits while I'm thinking?

Not in the next few minutes, and automatic gc is conservative about young objects. But every day you wait is a day closer to pruning, and a manual git gc --prune=now (yours or a well-meaning teammate's) ends the reprieve instantly. Recover now, deliberate later.

What if I reset --hard with uncommitted changes AND committed work?

Split the problem: the committed work comes back via the reflog (Case 1), the staged-but-uncommitted content via git fsck --lost-found (Case 2), and anything never staged is an IDE/OS recovery job (Case 3). Do Case 1 first — it's the near-certain win.