Skip to content

Fix: Undo git reset --hard and Recover Lost Commits

FixDevs ·

Quick Answer

How to undo git reset --hard and recover lost commits using git reflog — step-by-step recovery for accidentally reset branches, lost work, and dropped stashes.

The Error

You run git reset --hard to undo changes and realize you reset too far — commits you needed are gone:

git reset --hard HEAD~3
# You meant to go back 1 commit, not 3

Or you reset to the wrong target:

git reset --hard origin/main
# Wiped out 5 local commits that were not pushed yet

Or after a bad merge:

git reset --hard HEAD
# Discarded uncommitted changes you needed

The commits appear lost — git log no longer shows them.

Why Recovery Is Usually Possible

git reset --hard moves the branch pointer — it does not immediately delete commits. The commit objects remain in Git’s object database until garbage collection runs (typically after 30 days or on explicit git gc). Git’s reflog records every movement of HEAD and branch pointers, making it possible to find and restore those commits.

What can be recovered:

  • Commits that existed before the reset (they are in the reflog).
  • Commits from deleted branches (also in the reflog).

What cannot be recovered:

  • Uncommitted changes that were never staged or committed — these are gone permanently after git reset --hard.
  • Changes discarded with git checkout -- file or git restore file without prior staging.

Fix 1: Use git reflog to Find the Lost Commits

The reflog is a local log of where HEAD has pointed. It is your primary recovery tool:

git reflog

Output looks like:

abc1234 HEAD@{0}: reset: moving to HEAD~3
def5678 HEAD@{1}: commit: Add user authentication
ghi9012 HEAD@{2}: commit: Fix login form validation
jkl3456 HEAD@{3}: commit: Update dashboard layout
mno7890 HEAD@{4}: commit: Initial dashboard setup

The commit hashes on the left are the actual commit objects — still in the repository. HEAD@{1} is where HEAD was before your reset, HEAD@{2} is the commit before that, and so on.

Find the commit you want to restore:

# See the commit message and diff for a reflog entry
git show HEAD@{1}
git show def5678

Fix 2: Restore the Branch to a Reflog Entry

Once you find the commit hash or reflog reference you want to restore to:

# Reset the branch back to the commit before the mistake
git reset --hard HEAD@{1}

# Or use the commit hash directly
git reset --hard def5678

This moves the branch pointer back to that commit — all commits between that point and your current position are restored in git log.

Verify the recovery:

git log --oneline -10
# Should show the recovered commits

Pro Tip: After recovering commits, push them to the remote immediately if they were already pushed before — or push for the first time if they are new. This protects them from future accidental resets and garbage collection.

Fix 3: Recover Commits Without Changing the Branch

If you only need one or a few commits back (not the entire branch), cherry-pick specific commits:

# Find the commit hash in reflog
git reflog

# Cherry-pick specific commits onto the current branch
git cherry-pick def5678  # Restore one commit
git cherry-pick ghi9012  # Restore another

# Cherry-pick a range
git cherry-pick ghi9012^..def5678

Cherry-pick applies the changes from those commits as new commits — useful when you want to bring back specific changes without fully resetting the branch.

Create a new branch from a reflog entry:

# Keep current branch as-is, create a recovery branch
git branch recovery-branch HEAD@{1}

# Or create and switch to it
git checkout -b recovery-branch HEAD@{1}

This is the safest recovery approach — you get the old commits on a new branch and can selectively merge what you need.

Fix 4: Find Lost Commits with git fsck

If the reflog does not show what you need (e.g., after a long time), use git fsck to find dangling commits:

# Find commits not reachable from any branch or tag
git fsck --lost-found

# Output:
# dangling commit def5678abc...
# dangling commit ghi9012def...

Inspect dangling commits:

git show def5678abc
git log --oneline def5678abc

# Or browse all lost-found objects
ls .git/lost-found/commit/

Git writes dangling objects to .git/lost-found/commit/. Inspect each one to find your lost work, then create a branch from it:

git branch recovered-work def5678abc

Fix 5: Recover a Dropped git stash

git stash drop or git stash clear removes stash entries. These are also recoverable via reflog:

# List recent stash reflog entries
git reflog stash

# Or find dangling blobs (stash contents)
git fsck --lost-found 2>/dev/null | grep "dangling commit"

# Inspect each one — stash commits have a specific format
git show <hash>

# Restore a lost stash as a branch
git branch recovered-stash <hash>

Stash entries in reflog look like:

abc1234 refs/stash@{0}: WIP on main: def5678 Add feature

After git stash drop, the entry disappears from git stash list but remains findable with git fsck until garbage collection.

Fix 6: Recover a Deleted Branch

When a branch is deleted with git branch -D, its commits are not immediately lost:

# Find the deleted branch's last commit in reflog
git reflog | grep "branch-name"

# Output:
# abc1234 HEAD@{5}: checkout: moving from branch-name to main

# Recreate the branch at that commit
git checkout -b branch-name abc1234

If the branch was pushed to remote and deleted locally:

# The remote still has it
git checkout -b branch-name origin/branch-name

If the branch was deleted on the remote too:

Use the reflog approach above — remote deletions do not affect your local reflog until you fetch.

Fix 7: Prevent Future Accidents

Use git reset --soft or git reset --mixed instead of --hard:

# --soft: move branch pointer, keep changes staged
git reset --soft HEAD~1

# --mixed (default): move branch pointer, unstage changes, keep files
git reset HEAD~1

# --hard: move branch pointer, DISCARD all changes — use with caution
git reset --hard HEAD~1

For most “undo last commit” use cases, --soft or --mixed is safer — they preserve your work.

Create a backup tag before risky operations:

# Tag the current state before a dangerous operation
git tag backup-before-reset

# Do the risky thing
git reset --hard HEAD~5

# Oops — restore from tag
git reset --hard backup-before-reset

# Clean up the tag when done
git tag -d backup-before-reset

Enable Git’s safety features:

# Warn before force-pushing
git config --global push.default simple

# Require --force-with-lease instead of --force (safer)
git config --global alias.pushf "push --force-with-lease"

Common Mistake: Running git reset --hard when you meant git checkout -- file (to undo changes to a single file). git reset --hard discards ALL uncommitted changes. For single-file undo, use:

# Undo changes to a specific file only
git checkout -- path/to/file.js
# or (modern syntax):
git restore path/to/file.js

Still Not Working?

Check if garbage collection already ran. If significant time has passed or you ran git gc --prune=now, dangling commits may be permanently deleted. Run git fsck — if it shows no dangling commits for your time range, they are gone.

Check the reflog expiry settings. By default, reflog entries expire after 90 days (30 days for unreachable entries). If the reset happened months ago, the reflog entry may be expired:

git config gc.reflogExpire         # Default: 90 days
git config gc.reflogExpireUnreachable  # Default: 30 days

Check for a remote copy. If you had pushed the commits before resetting, the remote branch still has them:

git fetch origin
git log origin/your-branch --oneline
# If they appear here, you can restore them
git reset --hard origin/your-branch

For other accidental git operations, see Fix: git stash pop conflict and Fix: git merge conflict.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles