Git Reflogs and Recovering Lost Commits
Git reflogs are local logs of changes to Git references: names such as branches, HEAD, and remote-tracking branches that point to commits. Each entry records where a reference pointed before and after it moved, along with who moved it, when, and usually why.
Their practical purpose is recovery. If a branch is reset, rebased, deleted, or accidentally moved, the commits it used to name may no longer be reachable through the normal branch and tag structure. A reflog can still show the old commit ID, allowing you to create a new reference and save the work.
The problem reflogs solve
Git commits are normally found by starting at a reference and walking through parent commits. A branch is one such reference: if main points to commit B, Git can follow B to its parents and find the branch's history.
Suppose you run:
git reset --hard HEAD~3
The branch now points three commits earlier. The later commits have not necessarily been erased; their objects may still be in the repository. However, no branch may point to them anymore, so ordinary commands such as git log --all might not find them.
Before reflogs, recovering this kind of mistake required knowing or guessing commit IDs, or finding them through lower-level repository inspection. Reflogs provide a local record of the reference's previous positions.
A reflog is not a second, permanent commit history. It is a time-limited record of reference movements in one repository.
What a reflog records
Git maintains a reflog for HEAD and can maintain one for individual references such as refs/heads/main. HEAD is Git's special name for the commit currently checked out, or, commonly, for the branch currently checked out.
A simplified reflog might look like this:
9fceb02 HEAD@{0}: reset: moving to HEAD~3
31a8d77 HEAD@{1}: commit: Add payment retry
The entry at HEAD@{0} is the current position. The next entry is the previous position. In the real on-disk log, each record includes the old object ID, the new object ID, the identity of the person making the change, a timestamp, and an action description supplied by Git or by the command that changed the reference.
You can inspect the current reference's log with:
git reflog
To inspect a particular branch's log, use its full or short name:
git reflog show main
Commands that move references create entries. Committing advances the current branch and HEAD; checking out another branch moves HEAD; merging, rebasing, cherry-picking, resetting, and deleting or recreating references can all leave useful records. The exact entries depend on which references moved and on the operation.
Git stores these logs under the repository's logs area, conceptually as files such as logs/HEAD and logs/refs/heads/main. This is an implementation detail, but it explains two important properties: reflogs belong to the local repository, and they are separate from the commit objects and from the shared branch history.
Reflogs are local, not shared
When you push a branch, Git sends commits and updates the remote branch. It does not send your reflog. Another clone may have a different reflog, or none at all, depending on what happened there.
This matters during incident recovery. The reflog in the clone where someone performed the destructive reset may identify the lost commit, while a freshly cloned repository may not. If the commit was pushed before it became unreachable, the remote or another developer's clone may provide a separate route to it.
Reflogs also depend on configuration. Ordinary non-bare repositories normally log updates to local branches, but reflog recording can be disabled or adjusted. A reflog is therefore a recovery aid, not a guarantee that every past reference value is available.
Recovering a lost commit
Start by looking at the relevant reflog and identify the commit ID before the mistaken movement:
git reflog
You can refer to an entry symbolically, for example HEAD@{1}, or use its full commit ID. Inspect it before changing anything:
git show HEAD@{1}
If it is the work you want, create a new branch pointing there:
git switch -c rescue-branch HEAD@{1}
Creating the branch gives the commit a current reference, so it is no longer dependent on the reflog entry remaining available. You can then compare it with the current branch, copy individual commits, or reset a branch deliberately after checking the result.
A branch deletion can be recovered in the same way if another reflog still contains the deleted branch's old tip, often through HEAD or the branch's own reflog. If no useful reflog remains, git fsck can sometimes list unreachable objects, but that is a less informative fallback: it may find commits without telling you which branch or operation made them relevant.
Expiration and actual deletion
Reflog entries are deliberately temporary. Git's maintenance commands, including git gc, expire old entries according to configuration. The commonly used defaults are approximately:
- 90 days for ordinary reflog entries
- 30 days for entries whose commits are no longer reachable from the repository's current references
These periods are controlled by gc.reflogExpire and gc.reflogExpireUnreachable. They are defaults, not laws: a repository can set different durations, and expiration can be requested immediately with options such as --expire=now or prevented with --expire=never when running the reflog-expiration machinery.
Expiration removes the record of the old reference position; it does not necessarily delete the commit at that instant. A commit can remain as an unreachable object for a while longer. Git's pruning step eventually removes unreachable objects older than its own retention threshold, commonly about two weeks by default. Once both the reflog evidence and the unreachable object are gone, ordinary reflog-based recovery is no longer possible.
That timing creates a useful but finite recovery window: act before maintenance expires the evidence and prunes the object. As soon as you find the desired commit, create a branch or tag for it. A live reference keeps the commit reachable and protects it from ordinary unreachable-object pruning.
Reflogs explain why a frightening reset or rebase is often reversible. Git records reference movement separately from the visible branch history, preserving enough local information to find the old tip—until normal expiration and garbage collection eventually reclaim it.