Fix: Undo git reset --hard and Recover Lost Commits
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 3Or you reset to the wrong target:
git reset --hard origin/main
# Wiped out 5 local commits that were not pushed yetOr after a bad merge:
git reset --hard HEAD
# Discarded uncommitted changes you neededThe 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 -- fileorgit restore filewithout 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 reflogOutput 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 setupThe 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 def5678Fix 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 def5678This 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 commitsPro 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^..def5678Cherry-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 def5678abcFix 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 featureAfter 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 abc1234If the branch was pushed to remote and deleted locally:
# The remote still has it
git checkout -b branch-name origin/branch-nameIf 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~1For 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-resetEnable 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.jsStill 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 daysCheck 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-branchFor other accidental git operations, see Fix: git stash pop conflict and Fix: git merge conflict.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: .gitignore Not Working (Files Still Being Tracked)
How to fix .gitignore not working — files still showing in git status after being added to .gitignore, caused by already-tracked files, wrong syntax, nested gitignore rules, and cache issues.
Fix: git fatal: A branch named 'x' already exists
How to fix 'git fatal: A branch named already exists' when creating or renaming branches — including local conflicts, remote tracking branches, and worktree issues.
Fix: Git Keeps Asking for Username and Password
How to fix Git repeatedly prompting for credentials — credential helper not configured, HTTPS vs SSH, expired tokens, macOS keychain issues, and setting up a Personal Access Token.
Fix: Git submodule update failed / fatal: not a git repository
Resolve Git submodule update and init failures including 'fatal: not a git repository', path conflicts, URL mismatches, shallow clone issues, and CI/CD checkout problems.