datarekha

reflog & recovering lost work

How git reflog records every place HEAD has pointed so you can recover commits that seem lost after a bad reset, deleted branch, or botched rebase.

8 min read Advanced Git Lesson 15 of 15

What you'll learn

  • Why commits that become unreachable after reset --hard are almost never truly deleted — and how reflog proves it
  • How to read git reflog output and identify the SHA of the state you want to restore
  • The difference between resetting the current branch back to a lost commit vs. creating a safe recovery branch

Before you start

Why commits are not deleted when they become unreachable

Every time you run git commit, Git writes a permanent snapshot object to the object store inside .git/objects. That object does not disappear when you run git reset --hard, delete a branch, or rebase. What changes is merely the pointer — the branch label or HEAD — that told Git which commit to show you. The commit itself sits in the object store until Git’s garbage collector decides to prune it, which by default happens no sooner than 30 days for unreachable commits and 90 days for entries in the reflog.

The technical term for a commit that no label points to is an orphaned or unreachable commit. It is invisible to normal commands like git log, but it has not been erased.

What the reflog is

The reflog (reference log) is a local journal that records every position HEAD has occupied, in reverse-chronological order. Every commit, reset, checkout, rebase step, merge, and branch switch appends a new line. The entries are numbered HEAD@{0} (most recent), HEAD@{1}, HEAD@{2}, and so on, where the number in braces is written as HEAD@{n} in backtick notation throughout this lesson.

Each entry stores the SHA of the commit HEAD pointed to at that moment, the operation that moved it, and a timestamp. This makes the reflog your personal time-machine for a repository — one that works even when no remote exists.

git reflog
a3f9c12 HEAD@{0}: reset: moving to HEAD~3
e87b504 HEAD@{1}: commit: Add payment retry logic
c4d2a91 HEAD@{2}: commit: Wire up Stripe webhook
1f0e38a HEAD@{3}: commit: Scaffold checkout controller
8ab5d07 HEAD@{4}: checkout: moving from main to feature/checkout
8ab5d07 HEAD@{5}: pull: Fast-forward
3c21e60 HEAD@{6}: commit: Update README

Reading this output: the bad reset --hard is at HEAD@{0}. The three commits you thought you lost are at HEAD@{1}, HEAD@{2}, and HEAD@{3}. The topmost of those — the one that was your branch tip before the reset — is e87b504.

The recovery loop

Step 1 — find the SHA

Run git reflog and scan for the last good state. Look at the description column: commit: entries name the commit message, and reset: or checkout: entries describe the operation. You want the SHA of the commit that was HEAD just before things went wrong.

If the output is long, pipe it through a pager:

git reflog | head -20

You can also inspect a specific entry before committing to any recovery action:

git show e87b504

Step 2 — choose your recovery method

Option A — move the current branch back (destructive, only if certain)

git reset --hard e87b504

This repoints the current branch to e87b504 and restores the working directory to match. It is the direct inverse of the bad reset. Use this only when you are sure e87b504 is exactly where you want the branch to be.

Option B — create a recovery branch (safer, recommended)

git switch -c recover-branch e87b504
# or, with older Git:
git checkout -b recover-branch e87b504

This creates a new branch at e87b504 without touching your current branch at all. You can inspect the recovered work, cherry-pick individual commits, or merge — without any risk of making the situation worse. This is the safer default whenever you are not 100% certain of the target SHA.

1f0e38aScaffold checkoutc4d2a91Wire up webhooke87b504Add retry logicHEAD after bad resetrecover-branchgit switch -c recover-branch e87b504
The three commits remain in the object store after the bad reset. The reflog points you to e87b504; a new branch re-attaches a label so Git stops treating it as orphaned.

Reflog for a specific branch

git reflog without arguments shows HEAD’s history. To see what happened to a specific branch pointer (useful when you deleted the branch entirely), use:

git reflog show feature/checkout

The output format is the same: SHA, operation, message. Find the SHA of the last commit on that branch and recover it with git switch -c as above.

Last resort: git fsck

If the reflog has expired or you are working in a fresh clone, git fsck can scan the entire object store for commits that are no longer reachable from any reference:

git fsck --lost-found

Dangling commits are written as files under .git/lost-found/commit/. You can inspect them with git show <sha> and then use git switch -c to recover whichever you need. This is slower and noisier than reflog, but it reaches further back.

Quick reference

SituationCommand
See HEAD’s full movement historygit reflog
See a specific branch’s historygit reflog show feature/checkout
Inspect an entry before recoveringgit show HEAD@{3} or git show e87b504
Move current branch back (certain)git reset --hard e87b504
Create a safe recovery branchgit switch -c recover-branch e87b504
Scan for all orphaned commitsgit fsck --lost-found

Quick check

0/3
Q1You run `git reset --hard HEAD~3` and immediately realize that was a mistake. You check `git reflog` and see the commit you want is at `HEAD@{1}`. Which command safely recovers your work without touching the current branch?
Q2A teammate clones your repository to their laptop and then asks to use your reflog to find a commit you lost two weeks ago. Why can't they help?
Q3You spent two hours editing three files but forgot to commit. You accidentally ran `git reset --hard HEAD` and now those edits are gone. You check `git reflog` — the last entry is the commit from two hours ago. Can you recover?

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Explore further

Cheat sheets

Related lessons

Skip to content