Recovering Deleted Git Commits and Branches with reflog
You run git reset --hard and only then realize you picked the wrong target. Or you remember, too late, that the branch you deleted with git branch -D still had unmerged commits on it. No matter how long you dig through git log, the lost commits are nowhere to be seen. Let’s put the most important conclusion first: if it was ever committed, it can almost always be recovered. Commits are not deleted immediately, and Git keeps a record of movements that lets you trace your way back. That record is the reflog.
reflog — the record of where HEAD has been #
If git log shows the parent chain of commits, the reflog is a record on an entirely different axis. It keeps, in chronological order, where HEAD and each branch in my repository pointed, through every single move. Commits, branch switches, merge, rebase, reset — every command that moves a reference leaves one line.
$ git reflog
2f8a1c3 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
9d4b7e5 HEAD@{1}: commit: feat: add order cancellation API
6c1f0a2 HEAD@{2}: commit: feat: add order lookup API
2f8a1c3 HEAD@{3}: commit: feat: add order modelThe HEAD@{N} notation means “the point HEAD was at, N moves ago”. HEAD@{0} is now, and larger numbers reach further into the past. Commits that have vanished from git log are still on this list, hashes and all.
You can also query by time instead of by count. If you remember roughly when the accident happened, this is faster.
git reflog --date=iso # show a timestamp on each entry
git log -g --oneline 'HEAD@{1 hour ago}' # query from where HEAD was an hour agogit log -g shows reflog entries in log format, which is handy when you want to read full commit messages while searching.
Two properties are worth keeping in mind — they speed up your judgment.
- The reflog is local to my repository. Unlike commits, it is not pushed and does not follow a clone. Recovery is only possible on the machine where the accident happened.
- It has a retention window. By default, entries reachable from the current history are kept for 90 days, and entries that have become unreachable are eligible for cleanup after 30 days. Yesterday’s accident is almost certainly recoverable; one from months ago cannot be guaranteed.
Scenario 1 — recovering commits wiped by reset –hard #
You have just wiped two commits with git reset --hard HEAD~2. Recovery takes two steps: find the hash of the lost commit in the reflog, then move the branch back to that hash.
$ git reflog
2f8a1c3 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
9d4b7e5 HEAD@{1}: commit: feat: add order cancellation API
6c1f0a2 HEAD@{2}: commit: feat: add order lookup APIWhere HEAD sat right before the reset — HEAD@{1}, hash 9d4b7e5 — is the last commit you lost. Pick one of two ways back.
# Option A — safe: put a branch on the lost commit first and inspect
git branch rescue 9d4b7e5
git log rescue --oneline # inspect, then recover via merge and so on
# Option B — direct: move main straight back to that point
git reset --hard 9d4b7e5If running yet another reset --hard without checking feels uneasy, option A is the better choice. A branch is just a pointer to a commit, so the moment the rescue branch lands on it, the commit becomes reachable again and the retention window stops being a concern.
Scenario 2 — recovering a deleted branch #
This is the case where you deleted an unmerged branch with git branch -D. The branch is gone, but the commit at its tip remains.
Right after the deletion, the fastest clue is the output of the delete command itself.
$ git branch -D feature-coupon
Deleted branch feature-coupon (was 4e7a9b1).The 4e7a9b1 in parentheses is the last commit the branch pointed to. Recreate the branch from this hash and the recovery is done.
git branch feature-coupon 4e7a9b1If you have already closed the terminal and lost that output, search the reflog. Use the commit messages you wrote on the deleted branch, or the branch-switch records, as clues.
$ git reflog | grep -n "feature-coupon\|coupon"
7: 4e7a9b1 HEAD@{6}: commit: feat: add coupon expiry validation
9: c3d5f80 HEAD@{8}: checkout: moving from main to feature-couponWhile the per-branch reflog is still around, git reflog show feature-coupon shows the movement history of just that branch.
What the reflog cannot recover #
The reflog is not a cure-all. These three cases are outside its reach.
- Changes that were never committed — working-directory modifications discarded with
git restorewere never recorded anywhere in Git, so they cannot be recovered. The danger zones of the undo commands are laid out in Git Basics #5: Undoing Changes — restore, reset, and revert. - Commits past the retention window — commits that stayed unreachable for 30 days and were swept by garbage collection cannot be brought back.
- Commits on another machine — the reflog is a local record, so movements inside a colleague’s repository or on the server are not in mine.
git stash drop does not appear in the reflog, but recovery is still possible. Running git fsck --unreachable | grep commit lists commits that belong to no branch, and a dropped stash often shows up there in commit form. Once you find the hash, bring it back with git stash apply <hash>.Wrap-up #
The whole procedure in this post fits in one line: find the hash of the lost point in the reflog, then put a branch on it or reset back to it. Most commit and branch deletion accidents end with these two steps.
And this recovery path has exactly one precondition: the commit must exist. The reflog is powerless for changes that were never committed, so the habit of committing often — even at awkward moments — is a more reliable safeguard than any recovery technique. If the units come out messy, you can tidy them up later. Only what was never recorded is truly gone.