What Is Git Reflog? Recovering Lost Commits
Git reflog records every place HEAD and branch tips have pointed, letting you recover commits that look lost after a reset, rebase, or deleted branch.
Git reflog is a local, per-repository log that records every point HEAD and your branch references have moved to — every commit, checkout, reset, rebase, and merge. It’s the safety net that makes Git’s history-rewriting commands far less dangerous than they look, because almost nothing a reset or rebase does actually deletes data immediately. It just moves references around, and the reflog remembers where they used to point.
Why the reflog exists
Git’s object database is append-only in normal use — commits, once made, aren’t deleted just because a branch no longer points at them. What changes when you run git reset --hard, force-push over a branch, or rebase a series of commits is which reference points where, not whether the underlying commit objects still exist.
The reflog is Git’s record of those reference movements. Every time HEAD changes — a commit, a checkout, a merge, a reset, an amend — an entry gets added. That log is what lets you find a commit again after an operation that looks, from the outside, like it erased history.
Reading the reflog
Running git reflog in any repository shows a chronological list of where HEAD has pointed, most recent first:
a1b2c3d HEAD@{0}: reset: moving to HEAD~3
e4f5g6h HEAD@{1}: commit: add validation logic
i7j8k9l HEAD@{2}: commit: fix typo in error message
m1n2o3p HEAD@{3}: rebase (finish): returning to refs/heads/main
Each entry has a HEAD@{n} reference — a relative pointer into the reflog itself, usable anywhere a commit reference is expected. HEAD@{1} means “where HEAD was one move ago,” regardless of what that move was. This is different from HEAD~1, which means “one commit before the current commit in the commit graph” — the reflog’s @{n} notation tracks reference movements, not the commit ancestry chain.
Recovering a “lost” commit
The most common reason to reach for the reflog is recovering from an operation that appeared to discard work:
git reset --hard HEAD~3 # oops, went too far
git reflog # find the commit from before the reset
git reset --hard HEAD@{1} # move back to where you were
This works because git reset --hard only moved the branch pointer and updated the working directory — it didn’t delete the commits that were previously reachable. As long as you catch it before Git’s garbage collector eventually cleans up genuinely unreferenced objects (which doesn’t happen automatically for a long time in normal use), those commits are recoverable straight from the reflog.
The same technique rescues you from a botched interactive rebase, an accidental git checkout that seemed to lose uncommitted work tracked in a prior commit, or even a deleted branch — deleting a branch just removes the reference, and the commits it pointed to remain in the reflog (and therefore recoverable) until they age out.
Reflog vs the commit graph
It helps to keep two different histories straight, since both use similar-looking commands.
Commit graph (git log) | Reflog (git reflog) | |
|---|---|---|
| What it tracks | Ancestry between commits | Movements of HEAD and branch refs |
| Shared with others | Yes — pushed and fetched | No — purely local |
| Survives a clone | Yes | No — a fresh clone has no reflog history |
| Typical use | Understanding project history | Recovering from local mistakes |
| Entry lifetime | Permanent (commits aren’t removed) | Expires — default 90 days for reachable entries, 30 for unreachable |
That local-only, expiring nature is important context: the reflog is not a backup mechanism for anything you care about long-term, and it doesn’t help you recover someone else’s force-push on a shared branch. It only helps with your own local reference movements, and only for a limited window. For a shared branch that got force-pushed over, the fix usually involves understanding how git rebase differs from git merge and coordinating with whoever pushed.
When the reflog won’t save you
A few situations the reflog doesn’t cover:
- A fresh clone has no history of another machine’s operations. Reflog entries live in
.gitlocally and are never pushed or fetched — they’re specific to the repository copy on your disk. - Entries eventually expire. Git periodically runs garbage collection that prunes reflog entries older than its expiry window, after which the referenced commits become eligible for real deletion if nothing else points to them.
- It won’t undo changes to tracked files that were never committed. The reflog only tracks reference movements — uncommitted changes to your working directory aren’t commits and were never in the reflog to begin with. Git stash is the right tool for protecting uncommitted work before running something risky.
Building good habits around it
Because most Git operations are more recoverable than they appear, the practical lesson is less “avoid rebase and reset” and more “know the reflog exists before you need it.” Combined with tools like git bisect for finding when a bug was introduced and git cherry-pick for moving individual commits between branches, the reflog rounds out a toolkit for confidently manipulating history locally, since almost every mistake is one git reflog away from being reversible.
The takeaway
Git reflog is the local log of everywhere HEAD and your branches have pointed, and it’s what makes a bad reset --hard or a botched rebase recoverable instead of catastrophic — the commits usually still exist, only the reference to them moved. It’s local-only, it expires, and it won’t help with someone else’s force-push, but for your own mistakes it’s often the fastest way back to safety. Run git reflog before you panic about lost work; there’s a good chance it’s still there.
Tagged
Keep reading
The Lycoris Team · · 5 min read What Are Conventional Commits? A Commit Convention
Conventional Commits structures commit messages as type(scope): description so tools can auto-generate changelogs and version bumps from git history.
The Lycoris Team · · 4 min read Git Stash Explained: Shelve Changes Without Committing
Git stash saves uncommitted work to a hidden stack so you can switch branches cleanly. How stash, pop, apply, and stash branches actually work.
The Lycoris Team · · 4 min read Git Cherry-Pick Explained: Copying Commits Between Branches
Git cherry-pick applies one commit's changes onto a different branch without merging the whole branch. How it works, and when to use it instead of merging.