It is 6:40 p.m. and you just ran git reset --hard HEAD~3 to clean up a messy branch. Then your stomach drops. Two of those three commits were the afternoon's real work, and the third was a fix you had not pushed anywhere. The terminal says the work is gone. Your editor agrees. Your heartbeat does not.

Take a breath. In most cases like this, Git has not deleted anything. It has only stopped pointing at it. And there is a built-in tool, git reflog, that remembers where you have been. This guide explains how it works, how to use it to rescue lost commits, and where its limits are.

Why "deleted" commits usually are not deleted

Git stores your project as a graph of commits. Branches and HEAD are just labels pointing at nodes in that graph. When you run git reset --hard HEAD~3, Git moves the label back three steps. The three commits still exist as objects on disk, but nothing points at them anymore, so they stop showing up in git log.

Git does eventually clean up unreachable objects with garbage collection, but not right away. By default, unreachable commits are kept for at least 30 days (and entries in the reflog itself for 90 days if reachable, 30 if not). That window is your safety net.

A commit is only truly lost when Git has both forgotten it and cleaned it up. The first happens in a second. The second takes weeks.

This is the key mental model: branches are bookmarks, commits are pages. Moving a bookmark does not tear out the pages.

What the reflog actually records

The reflog is a local diary of every time HEAD (or a branch tip) moved: commits, checkouts, merges, rebases, resets, pulls. Run:

git reflog

You will see something like this:

a1b2c3d (HEAD -> feature/login) HEAD@{0}: reset: moving to HEAD~3
9f8e7d6 HEAD@{1}: commit: Add password strength meter
4c5b6a7 HEAD@{2}: commit: Fix session timeout bug
2d3e4f5 HEAD@{3}: commit: Add login form validation
8a9b0c1 HEAD@{4}: checkout: moving from main to feature/login

Read it from the top: newest first. HEAD@{0} is where you are now, and HEAD@{1} is where you were one move ago. In this example the three "lost" commits are sitting right there at HEAD@{1}, HEAD@{2} and HEAD@{3}.

Two details are worth knowing. First, the reflog is local only. It is never pushed, so a fresh clone has no history of your mistakes. Second, it is per-repository, so if you cloned the project twice, each copy has its own diary.

Recovering after a bad reset

For the scenario above, the fix is a single command. You want your branch to point at the commit you were on before the reset, which is HEAD@{1}:

AD
git reset --hard HEAD@{1}

Or, using the hash directly:

git reset --hard 9f8e7d6

Your branch jumps forward again and all three commits are back. If you would rather not touch your current branch at all, create a new one at the rescued commit instead:

git branch rescue/login-work 9f8e7d6

This is the safer habit when you are unsure. A new branch costs nothing, it guarantees the commits are now reachable, and you can compare it with your current state before deciding what to keep.

Here is the same idea as a quick comparison:

GoalCommandRisk
Put the branch back exactly as it wasgit reset --hard HEAD@{1}Overwrites uncommitted changes
Keep current state, save old workgit branch rescue 9f8e7d6None
Take one commit onlygit cherry-pick 9f8e7d6Possible conflicts

Recovering from a deleted branch or a botched rebase

The same trick works for other classic disasters.

A deleted branch. Suppose you ran git branch -D old-experiment and then remembered it had one good idea. Look in the reflog for the last commit on that branch, or search it:

git reflog | grep old-experiment
git branch old-experiment 7e6d5c4

The branch is recreated pointing at the old tip, with its whole history intact.

A rebase that went sideways. Rebases rewrite history, and a bad conflict resolution can quietly drop changes. Before the rebase began, your branch pointed at some commit, and Git remembers it. Find the line that says rebase (start) and look at the entry just before it:

git reflog
# ...
# 3b2a1c0 HEAD@{5}: rebase (start): checkout main
# 8d7c6b5 HEAD@{6}: commit: Last commit before rebase
git reset --hard 8d7c6b5

Newer Git versions also record a special reference called ORIG_HEAD that points to where HEAD was before a dangerous operation such as a reset, merge or rebase. So git reset --hard ORIG_HEAD is often the fastest undo button. Check it with git log ORIG_HEAD -1 first to be sure it is what you expect.

AD

A detached HEAD. If you checked out a commit hash, made some commits, and then switched branches, those commits are on no branch. The reflog still lists them. Grab the newest hash and attach a branch to it.

Habits that make recovery boring

The best use of the reflog is as a calm, routine step rather than a panic. A few habits help.

  1. Commit early, even messily. A reflog can only restore what was committed. Uncommitted changes that you discard with reset --hard or checkout . are genuinely gone, because Git never stored them. A throwaway commit such as wip is cheap insurance, and you can squash it later.
  2. Stash before risky moves. git stash push -u saves tracked and untracked files, and the stash itself shows up in git reflog stash.
  3. Use --force-with-lease instead of --force. When you push rewritten history, it refuses to overwrite commits you have not seen. That protects teammates, and it protects you from your own stale copy.
  4. Make a rescue branch before experiments. git branch backup/before-rebase takes one second and removes all doubt.
  5. Do not wait too long. The 30-day default is generous, but if you run git gc --prune=now or clone elsewhere, the safety net is gone.

If the reflog does not show what you need, there is one more tool of last resort:

git fsck --lost-found

It scans the repository for unreachable commits and dangling objects. The output is noisy and unlabeled, so it is slower than the reflog, but it can find work that no log entry points to.

When the reflog cannot help

Being honest about the limits matters. The reflog will not recover files that were never committed or staged. It will not exist in a different clone. It will not help if garbage collection already ran, if the reflog entries expired, or if you deleted the whole .git folder. Staged-but-uncommitted files are a gray area: their contents can sometimes be dug out with git fsck --lost-found because staging writes objects to disk, but treat that as luck, not a plan.

It also will not undo something that was already pushed and then force-overwritten on the server. For that, a teammate's local clone, or your hosting provider's own history, may be your only hope.

Summary

Git is much more forgiving than it feels at 6:40 p.m. with a failing stomach. Remember three things: moving a branch does not delete commits, git reflog is your local diary of every move, and git reset --hard HEAD@{1} or a new rescue branch will bring most "lost" work back. Commit often, branch before experiments, and check the reflog before you assume anything is gone.

The next time your terminal says the work has vanished, pause, run git reflog, and take a look. Chances are your afternoon is still there, quietly waiting to be found.