DEV Community

Amaresh Pelleti
Amaresh Pelleti

Posted on Originally published at devtoolhub.com

Git Recover Lost Commits: 12 Fixes for Common Mistakes

Originally published on DevToolHub.

Reflog flow: git reset --hard hides a commit, git reflog shows its hash, a new branch brings it back

To git recover lost commits, run git reflog, find the hash from before the mistake, and point a branch at it with git branch rescue <hash>. Almost every local Git mistake can be undone this way, but only if you act before the reflog expires.

I tested 12 common mistakes on Git 2.56.0 in throwaway repositories and ran every command below. The outputs are real. Find your symptom in the table and jump to the fix.

Symptom Fix
Committed on the wrong branch git switch -c feature, then reset the old branch
Last commit was too early git reset --soft HEAD~1
Typo in the last commit message git commit --amend
Staged the wrong file git restore --staged <file>
Ran git reset --hard git reflog, then git reset --hard HEAD@{1}
Deleted a branch git branch <name> <hash>
Committed on a detached HEAD git switch -c rescue <hash>
A merge went wrong git merge --abort or git reset --hard ORIG_HEAD
A rebase went wrong git rebase --abort or git reset --hard ORIG_HEAD
Pushed a bad commit git revert HEAD
Dropped a stash git stash apply <hash>
Lost staged changes git fsck --lost-found

Git Recover Lost Commits: Start With the Reflog

Git keeps a private log of every place HEAD and your branch tips have been. That log is the reflog, and it is why "lost" commits usually aren't gone. They are only unreachable from any branch.

git reflog
Enter fullscreen mode Exit fullscreen mode

Entries expire, so the window matters. By default, reflog entries last 90 days, or 30 days if the commit is no longer reachable from the current tip. git gc prunes unreachable objects after 2 weeks. Those defaults come from the git-config docs, and they are why "recover it soon" is real advice.

The reflog also lives in one repository only. When I cloned my test repo, the clone's reflog held a single clone entry.

Git Recover Lost Commits After git reset --hard

I ran git reset --hard HEAD~2 and the two newest commits vanished from git log. The reflog still had them:

git reset --hard HEAD~2
# HEAD is now at df077d5 commit 1
git reflog
# df077d5 HEAD@{0}: reset: moving to HEAD~2
# 3a46592 HEAD@{1}: commit: commit 3
# e9e89f5 HEAD@{2}: commit: commit 2
git reset --hard HEAD@{1}
# HEAD is now at 3a46592 commit 3
Enter fullscreen mode Exit fullscreen mode

HEAD@{1} means "where HEAD was one move ago". Both commits came back. If you would rather not move your branch, run git branch rescue 3a46592 instead. The Pro Git chapter on data recovery shows the same approach.

Git Recover Lost Commits From a Deleted Branch

git branch -D prints the hash you need, so keep that line:

git branch -D feature
# Deleted branch feature (was c67023a).
git branch feature c67023a
Enter fullscreen mode Exit fullscreen mode

If you missed it, search git reflog for the branch's last commit message. Then use that hash.

How Do You Recover Commits Made on a Detached HEAD?

Commits made on a detached HEAD belong to no branch. When you switch away, Git warns you:

Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  f449b13 commit on detached HEAD
Enter fullscreen mode Exit fullscreen mode

Copy that hash, or find it in git reflog, then run git switch -c rescue f449b13. The commit was back on a branch called rescue.

How Do You Undo a Commit You Have Not Pushed?

Four fixes cover the usual mistakes before a push.

Committed on the wrong branch. Create a branch at the current commit, then move the old branch back:

git switch -c feature
git switch main
git reset --hard HEAD~1
Enter fullscreen mode Exit fullscreen mode

In my test, main went back to commit 3 and feature kept the new commit.

Committed too early. git reset --soft HEAD~1 removes the commit and leaves the changes staged. My git status --short showed M f.txt afterwards.

Wrong commit message. git commit --amend -m "new message" replaces the last commit and gives it a new hash. Don't do this after pushing.

Staged the wrong file. git restore --staged b.txt unstages it. In my test, the new file went back to untracked.

For when to pick reset, revert or restore, see our Git reset vs revert vs restore guide.

How Do You Undo a Merge or a Rebase?

During a conflict, abort:

git merge --abort
git rebase --abort
Enter fullscreen mode Exit fullscreen mode

Both put the repo back as it was. After the merge abort, my git status was clean. After the rebase abort, I was back on topic with my original commit. For resolving the conflict instead, see our merge conflicts guide.

If the merge or rebase already finished, ORIG_HEAD holds the tip from before it:

git reset --hard ORIG_HEAD
# HEAD is now at 093899c main z
Enter fullscreen mode Exit fullscreen mode

After a finished merge, ORIG_HEAD pointed at the pre-merge commit, and the reset restored it. After a finished rebase, it pointed at my original branch tip.

Run it first, because ORIG_HEAD is one pointer. When I ran a second reset, it replaced the value, although the reflog still listed the older tip. Also, reset --hard discards uncommitted changes, so commit or stash them first.

How Do You Undo a Commit You Already Pushed?

Don't rewrite shared history. Revert the commit instead:

git revert --no-edit HEAD
git push origin main
Enter fullscreen mode Exit fullscreen mode

The revert is a new commit, so my push went through without --force. The history keeps the bad commit and its reversal, which is what your teammates need.

Git Recover Lost Commits From Dangling Objects

When the reflog has nothing, git fsck can still find objects that no branch points to. If you want to know why they survive, our Git internals guide explains how Git stores data.

A dropped stash. git stash drop prints the stash's commit hash, so I saved it:

git stash drop
# Dropped refs/stash@{0} (82f6bc5f139abbd879ec42fc05a93581470fccb0)
git stash apply 82f6bc5f139abbd879ec42fc05a93581470fccb0
Enter fullscreen mode Exit fullscreen mode

If you didn't save the hash, git fsck --unreachable lists unreachable commits. In my test it showed the stash commit, plus a blob and a tree. The git-stash docs give a command that filters the list.

Staged changes lost to git reset --hard. git add stores a file's contents as a blob before you commit. After the reset, my file was gone from the folder, but this found it:

git fsck --lost-found
# dangling blob 3b17e58d0160fa20865c3e81bb46877c0c2da7bc
cat .git/lost-found/other/3b17e58d0160fa20865c3e81bb46877c0c2da7bc
# precious staged content
Enter fullscreen mode Exit fullscreen mode

The command writes blobs to .git/lost-found/other/, as the git-fsck docs describe. You get the contents but not the file name, so you rename the file yourself.

What Git Cannot Recover

Edits you never staged or committed are gone, because Git never saw them. Check your editor's local history instead. Anything past the expiry windows above is also at risk, and so is a repository you deleted.

Leaked secrets are a separate job. Rotate the credential first, then read our Git security best practices.

What I Did Not Test

I used one version, Git 2.56.0, on macOS, with local repositories and one bare remote. I did not test force-push recovery on a hosted remote, reverting a merge commit (it needs -m), submodules or very large histories. I also did not wait for reflog expiry, so those numbers come from the docs.

Frequently Asked Questions

Q: How long can I recover lost commits in Git?
A: By default, reflog entries last 90 days, or 30 days for commits no longer reachable from the current tip, and git gc prunes unreachable objects after 2 weeks. git config --get gc.reflogExpire printing nothing means you use the defaults.

Q: Does the reflog survive a clone?
A: No. The reflog is local to one repository. My fresh clone had a single clone entry.

Q: Can I recover uncommitted changes?
A: Only if you staged them with git add, because that stores a blob. Use git fsck --lost-found. Edits you never staged can't be recovered by Git.

Q: What is the difference between git reset --hard and git revert?
A: reset --hard moves your branch and discards changes, so use it on local work. revert adds a new commit that undoes an old one, so use it after pushing.

Q: What is ORIG_HEAD?
A: A pointer that Git sets to your previous tip when a merge, rebase or reset moves the branch. Another reset overwrites it.

Quick Summary:

  • Start with git reflog, then git reset --hard HEAD@{1} or git branch rescue <hash>
  • git branch -D prints the hash of the deleted branch
  • git merge --abort and git rebase --abort undo a conflicted operation
  • After a finished merge or rebase, git reset --hard ORIG_HEAD goes back
  • After a push, use git revert instead of rewriting history
  • git fsck --lost-found can recover work you staged but never committed

To git recover lost commits, begin with the reflog and move to git fsck only when it comes up empty.

Top comments (0)