The Reflog: Git's Safety Net

"I lost my commits" is almost never true. A bad reset, a botched rebase, a deleted branch — the commits are usually still there, sitting unreachable in the object store, waiting out their grace period before garbage collection. The reflog is the log of everywhere your refs have been, and it's the tool that turns "I destroyed my work" into a thirty-second recovery. Knowing it exists changes how fearlessly you can use Git.

By now we can explain what most commands do: they create objects and move refs, changing reachability. That power to move refs is also the power to lose things — point a branch somewhere else and its old commits may become unreachable. This post is about the mechanism that makes that reversible, and why “lost” in Git rarely means “gone.”

Unreachable is not deleted

Recall two facts from earlier posts. First, commits are immutable content-addressed objects — nothing edits them in place. Second, git gc only removes objects that are both unreachable and past a grace period. Put those together and a reassuring conclusion falls out: when an operation “loses” commits, it doesn’t delete them — it just stops any ref from pointing at them.

A git reset --hard HEAD~3 doesn’t erase three commits. It moves your branch pointer back three commits. The three you “removed” still exist as objects; they’re simply no longer reachable by walking parents from any branch. They sit in .git/objects, intact, until gc eventually prunes them — which, by default, won’t happen for about two weeks, and not while the reflog still references them. The window to recover is wide, and the reflog is how you find the way back in.

What the reflog records

Every time a ref’s value changes, Git appends a line to that ref’s reflog — a local log under .git/logs/. There’s one for HEAD (.git/logs/HEAD) and one per branch (.git/logs/refs/heads/main). Each entry records the old hash, the new hash, who changed it, when, and a short description of the operation:

$ git reflog
e5f6a7b (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
1a2b3c4 HEAD@{1}: commit: add retry backoff
9d8e7f6 HEAD@{2}: commit: parse config
b4c5d6e HEAD@{3}: rebase finished: returning to refs/heads/main

The syntax HEAD@{n} means “where HEAD pointed n moves ago.” HEAD@{1} is where it was before the most recent change; you can also use time expressions like HEAD@{yesterday}. This is fundamentally different from git log. git log walks the commit DAG via parent edges — it shows ancestry. git reflog is a chronological journal of pointer movements — it shows where your refs have been, including commits that are now unreachable and would never appear in log. When a commit has fallen off the graph, the reflog is often the only remaining name for it.

Recovering lost work

The recovery pattern is always the same: find the lost commit’s hash in the reflog, then point a ref at it to make it reachable again.

Undoing a bad reset. You ran git reset --hard HEAD~3 and want it back:

$ git reflog                       # find the pre-reset tip
1a2b3c4 HEAD@{1}: commit: add retry backoff
$ git reset --hard HEAD@{1}        # move the branch back to it

The branch pointer returns to 1a2b3c4, and the three commits are reachable — and therefore safe — once more.

Recovering a deleted branch. Deleting a branch removes the ref file, but HEAD’s reflog still holds the commits you made on it:

$ git reflog                       # locate the branch's last tip
7f8a9b0 HEAD@{5}: commit: feature work
$ git branch recovered 7f8a9b0     # re-anchor it with a new branch

Rescuing a botched rebase. A rebase rewrites commits (post 4) and abandons the originals — but the reflog recorded where the branch was before the rebase started. git reset --hard HEAD@{...} at that pre-rebase entry restores the original commits exactly.

In every case you’re doing the same thing: the object is alive but unreferenced; the reflog gives you its hash; you attach a ref and reachability makes it permanent again.

The reflog is local and expiring

Two properties are worth internalizing so you neither over-trust nor under-trust it:

Why this changes how you work

The deepest lesson of the reflog is psychological. Beginners treat Git operations as irreversible and approach reset, rebase, and branch deletion with dread. But given the object model — immutable objects, reachability-based collection, a grace period, and a journal of every ref movement — almost every local operation is reversible. The commits you’re afraid of losing are one git reflog away from being found.

This is why experienced engineers use Git’s sharper tools without fear: they know the floor is padded. reset --hard, interactive rebase, history surgery — all become routine once you trust that the reflog remembers where you were. The one genuine way to lose work is to operate on content you never committed at all (uncommitted working-directory changes have no object, so there’s nothing to recover) — which is the best argument for committing early and often. Everything you’ve committed, the reflog can find.

Key takeaways

Further reading

Sources & References

Recovering unreachable commits via the reflog