How-to

git reflog: how to recover lost commits and branches

Updated: · Skillaria.Top

git reflog is a local log where Git records every move of HEAD and branches: commits, reset, checkout, rebase, merge. Find the entry from before your mistake in git reflog, for example HEAD@{2}, and restore that state with git reset --hard HEAD@{2}, or create a branch with git branch rescue HEAD@{2}.

git reflog is the reference log: Git records every move of HEAD and branch tips in it. You made a commit, switched branches, ran reset, rebase or merge — a line appears in the reflog with the hash the ref pointed to after the operation. That's why even a commit no branch points to anymore can be found and recovered.

The key thing to know: the reflog is local. It exists only in your clone, isn't pushed to the server, and doesn't come along with git clone. But in your own repository it saves you from almost any history mistake.

How to read git reflog output

git reflog
3c4d5e6 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
9a8b7c6 HEAD@{1}: commit: Add email validation
1f2e3d4 HEAD@{2}: commit: Move config to a separate file
3c4d5e6 (HEAD -> main) HEAD@{3}: checkout: moving from feature to main
7b6a5c4 (feature) HEAD@{4}: commit: Draft login form

Each line is one state of HEAD, newest at the top:

  • hash — the commit HEAD pointed to after the operation;
  • HEAD@{n} — "where HEAD was n moves ago"; it's a full-fledged ref you can pass to any command;
  • description — the operation type (commit, reset, checkout, rebase, merge, pull, cherry-pick, commit (amend)) and details.

In the example you can see that two commits — "Move config" and "Add validation" — were made, and then reset HEAD~2 moved the branch back. They're gone from git log, but they're still in the reflog.

The reflog of a specific branch

Each branch has its own log. It's shorter and more useful when you've been switching between branches a lot:

git reflog show feature
git reflog show origin/main   # how the remote branch changed on fetch

Time-based refs

Instead of a number you can give a point in time — Git finds where the ref pointed back then:

git show main@{yesterday}
git diff main@{"2 hours ago"} main
git log -g --since="1 day ago" --oneline

The -g (--walk-reflogs) flag on git log walks the reflog instead of parent links and lets you use the usual formats and filters. To show dates, git reflog --date=iso is handy.

How to recover commits after git reset --hard

The most common situation: you ran git reset --hard to the wrong commit and your work "disappeared". The commits haven't actually gone anywhere — they're just not visible from any branch.

  1. Open the log: git reflog.
  2. Find the line right before reset: moving to ... — that's the state before the mistake. In the example above it's HEAD@{1} with hash 9a8b7c6.
  3. Check that it's the right one: git log --oneline -5 HEAD@{1}.
  4. Restore the branch: git reset --hard HEAD@{1}.
git reset --hard HEAD@{1}
HEAD is now at 9a8b7c6 Add email validation

Be careful: the numbers shift after every operation. What was HEAD@{1} becomes HEAD@{2} after the reset. So it's safer to copy the hash rather than the number.

An important limitation: the reflog only stores committed work. Uncommitted changes wiped out by reset --hard or git checkout -- file can't be recovered through the reflog. There's a little hope left if the files had been staged — more on that below.

How to recover a deleted branch

You deleted a branch with git branch -D feature, and it had unmerged commits. The branch's own log is deleted along with it, but the HEAD log stays — and HEAD was on its commits while you worked on it.

git reflog | grep feature
7b6a5c4 HEAD@{4}: commit: Draft login form
3c4d5e6 HEAD@{3}: checkout: moving from feature to main

# The branch's last commit is the one you were on when you left it
git branch feature 7b6a5c4

Look for the line checkout: moving from feature to ...: the hash in the previous entry (lower in the list) is the branch's last state. By the way, Git prints the hash itself when deleting: Deleted branch feature (was 7b6a5c4). — if the terminal is still open, that's all you need.

How to undo a botched rebase

Rebase rewrites commits, and if you resolved conflicts incorrectly along the way, the result can be worse than what you started with. The reflog keeps the state from before the rebase began.

git reflog
e1f2a3b HEAD@{0}: rebase (finish): returning to refs/heads/feature
e1f2a3b HEAD@{1}: rebase (pick): Draft login form
c4d5e6f HEAD@{2}: rebase (start): checkout main
7b6a5c4 HEAD@{3}: commit: Draft login form

git reset --hard HEAD@{3}

You need the entry before rebase (start). There's also a shortcut: before risky operations (rebase, merge, reset) Git saves the previous position in the ORIG_HEAD ref, so right after a rebase git reset --hard ORIG_HEAD will work. But ORIG_HEAD gets overwritten by the next such operation, while the reflog keeps the full history.

How to get back the commit from before git commit --amend

--amend doesn't modify a commit; it creates a new one in its place. The old one stays in the reflog:

git reflog
5a6b7c8 HEAD@{0}: commit (amend): Add email validation
9a8b7c6 HEAD@{1}: commit: Add email validation

git diff HEAD@{1} HEAD          # what the amend changed
git reset --soft HEAD@{1}       # restore the old commit, the amend's changes stay staged

Safe recovery: a branch instead of reset

If you're not sure which commit you need, don't run reset --hard blindly. Create a rescue branch — it doesn't break anything and protects the found commit from being deleted:

git branch rescue HEAD@{5}
git log --oneline rescue
git switch rescue

From there you can bring commits over from rescue with cherry-pick or merge them, and delete the branch once everything is back in place.

How long the reflog is kept

Entries aren't forever. They're cleaned up by git gc, which Git runs on its own from time to time. The retention periods are set by these options:

SettingDefaultWhat it means
gc.reflogExpire90 daysRetention for entries with commits reachable from current branches
gc.reflogExpireUnreachable30 daysRetention for entries with commits not reachable from any branch — exactly the "lost" ones
gc.pruneExpire2 weeksHow long before unreachable objects that nothing references anymore are deleted

The practical takeaway: a lost commit is guaranteed to be reachable through the reflog for about a month. Don't put off recovery, and don't manually run git gc --prune=now or git reflog expire --expire=now --all until you've got back what you need.

When the reflog doesn't help: git fsck

Sometimes a commit isn't in the reflog: for example, it was created by git stash and then the stash was deleted with git stash drop, or the changes were only in the staging area. In that case, look for "dangling" objects:

git fsck --lost-found
dangling commit 2b3c4d5e6f...
dangling blob 8e9f0a1b2c...

git show 2b3c4d5e6f            # view the contents
git branch recovered 2b3c4d5e6f

Dangling commits from a dropped stash have a "WIP on ..." message. Dangling blobs are the contents of files that were once added with git add but never committed. They have no file name, so you'll have to identify them by content, but this sometimes rescues work after a reset --hard. With the --lost-found flag, Git also copies them into .git/lost-found/.

What the reflog can't do

  • It doesn't work across machines. If a teammate force pushed and wiped out your commits on the server, look for them in the reflog of whoever had them locally — for example, git reflog show origin/main on anyone who fetched before the overwrite.
  • It doesn't see uncommitted work. That's why the best insurance before a risky operation is a commit or git stash.
  • It doesn't keep history forever. See the retention periods above; plus, a fresh clone starts with an empty log.

In practice: a safety net before a risky operation

The reflog saves you, but recovering from it takes care: you have to find the right entry among dozens of similar ones. It's much easier to leave a marker in advance. Before a rebase, a big reset, or experimenting with history, create a temporary branch or tag:

git branch backup/before-rebase
git rebase -i main
# something went wrong
git reset --hard backup/before-rebase
# everything worked
git branch -D backup/before-rebase

It costs one command and spares you hunting for a hash. The reflog remains the fallback for when you remember the safety net too late.

How to find the right entry quickly

In a long log, filtering helps. Search by operation type or commit message text, and when in doubt, look at the contents:

git reflog | grep -E "reset|rebase \(start\)"
git log -g --grep="validation" --oneline
git show --stat HEAD@{7}

git log -g with --grep searches the messages of commits in the reflog, and git show --stat shows which files were touched — that makes it easy to identify the right commit even if the message was something unhelpful like "wip".

Wrapping up

git reflog is Git's safety net: anything committed in your repository over the past few weeks can be found and recovered. The steps are always the same: open the log, find the entry from before the mistake, check it with git log or git show, and then create a branch or run reset --hard. Working with restore, reset, revert and recovering from mistakes is covered hands-on in the Git course.

FAQ

What's the difference between git reflog and git log?

git log follows the parent links of commits and shows a branch's history. git reflog shows where HEAD and branches have moved in your repository, including commits that are no longer in any branch's history.

Does the reflog get pushed to GitHub or GitLab?

No, the reflog is stored only in the local .git/logs folder and isn't transferred on push or clone.

Can I clear the reflog?

Yes, with git reflog expire --expire=now --all followed by git gc --prune=now, but after that lost commits can no longer be recovered.

Where is the reflog physically stored?

In the text files .git/logs/HEAD and .git/logs/refs/heads/branch-name, one line per move.

Does the reflog work in a bare repository on the server?

By default the reflog is disabled in bare repositories (core.logAllRefUpdates), so you can't rely on it on the server.

Learn this topic on video

This topic is covered in depth with practice in the course “Git from Scratch to Confident Developer”.

Go to course