Recovering Lost Commits with reflog
Git rarely truly deletes anything immediately. Even when a commit becomes unreachable from any branch or tag — because you ran git reset --hard, force-pushed over a branch, deleted a branch, or amended history during a rebase — the commit object itself typically remains in the object database until garbage collection eventually prunes it, which by default doesn't happen for at least 30 days. The reflog is the mechanism that makes recovering these commits practical: it's a local, chronological log of every place HEAD and each branch ref has pointed, recorded every time you check out, commit, merge, rebase, reset, or otherwise move a ref. Because it records the *before* state before each ref update, the reflog lets you find and restore the SHA a branch pointed to before the operation that seemingly destroyed your work.
Cricket analogy: This is like a ground's official scorer keeping a private working log of every recalculation even after an official declaration is overturned — the underlying raw data isn't actually erased right away, so a statistician can still dig it up before the archive is eventually cleaned out months later.
Reading the reflog
Running git reflog (shorthand for git reflog show HEAD) prints a list of entries such as HEAD@{0}, HEAD@{1}, each showing the resulting SHA and the action that produced it — commit, checkout, reset, rebase, pull, merge, and so on. Each branch also has its own reflog, viewable with git reflog show <branch-name>. Entries are local only: they live in .git/logs and are never transmitted on push or fetch, so reflog-based recovery only works on the machine and clone where the history-altering operation happened.
Cricket analogy: This is like a scorer's personal notebook that logs every single change to the scoreboard chronologically — declaration, review, revised total — kept only in the scorer's own book at the ground, never transmitted to the broadcast feed, so it only helps if you're standing at that exact ground.
# Oops — a hard reset just discarded three commits of work
$ git log --oneline -1
a1b2c3d Add rate limiter middleware
$ git reset --hard HEAD~3
HEAD is now at 9f8e7d6 Initial API scaffolding
# The commits look gone from git log, but the reflog remembers
$ git reflog
9f8e7d6 HEAD@{0}: reset: moving to HEAD~3
a1b2c3d HEAD@{1}: commit: Add rate limiter middleware
c4d5e6f HEAD@{2}: commit: Add request logging
b7a8c9d HEAD@{3}: commit: Add auth middleware skeleton
9f8e7d6 HEAD@{4}: checkout: moving from main to feature/api-hardening
# Recover by resetting back to the commit just before the mistake
$ git reset --hard HEAD@{1}
HEAD is now at a1b2c3d Add rate limiter middleware
# Or, if you're unsure, create a safety branch first instead of resetting
$ git branch recovered-work HEAD@{1}Common recovery scenarios
Beyond git reset --hard, reflog rescues you from a botched interactive rebase (rebases rewrite each commit and record intermediate steps as separate reflog entries, so you can jump back to any point mid-rebase), an amend that changed the wrong commit, or deleting a branch with git branch -D before merging it — the branch pointer is gone, but HEAD's reflog (or, if you know the SHA, git fsck --unreachable --lost-found) can still find the tip commit. Once you have the SHA, you can git checkout <sha>, git cherry-pick <sha>, or simply git branch <new-name> <sha> to bring the work back into a proper branch.
Cricket analogy: This is like recovering an over that seemed lost after a botched over-rate recalculation mid-match, or a declared innings later found to still have valid data in the official records office (fsck --lost-found) even after the scoreboard operator deleted the entry — you can still restore it into the official record.
A useful mental model: git log shows history reachable by walking parent pointers from a ref, while git reflog shows a separate, local audit trail of where a ref has been — the reflog is Git's own undo history for your undo history.
The reflog is not permanent. Entries expire — unreachable reflog entries are pruned after 30 days by default (gc.reflogExpireUnreachable) and reachable ones after 90 days (gc.reflogExpire) — and an explicit git gc --prune=now or git reflog expire --expire=now --all can remove entries immediately. Don't treat reflog as a permanent backup; commit and push important work to a remote as soon as possible.
- git reflog records every movement of HEAD (and each branch) locally, even across resets and rebases.
- Recovery works because 'deleted' commits usually still exist as unreachable objects until garbage collected.
- Use git reset --hard HEAD@{n} or git branch <name> <sha> to restore lost work found in the reflog.
- Reflog entries are local-only and are never pushed, fetched, or cloned to another machine.
- git fsck --unreachable --lost-found can locate dangling commits even without a reflog entry, e.g. after a deleted branch.
- Reflog entries expire (30/90 days by default) — it's a safety net, not a permanent archive.
Practice what you learned
1. What does the reflog primarily track?
2. After running `git reset --hard HEAD~3`, how can you typically recover the discarded commits?
3. Why can't you use a teammate's reflog to recover a commit they accidentally deleted on their machine?
4. By default, roughly how long are unreachable reflog entries kept before they become eligible for garbage collection?
5. If a branch was deleted with `git branch -D` and you don't remember its last commit SHA, which command can help locate dangling commits?
Was this page helpful?
You May Also Like
git reset Explained
How git reset moves the current branch pointer and optionally rewrites the staging area and working directory, and the meaningful difference between its --soft, --mixed, and --hard modes.
Stashing Changes
Learn how git stash lets you temporarily shelve uncommitted work so you can switch context cleanly, then reapply it later without polluting your commit history.
Interactive Rebase
Interactive rebase lets you reorder, squash, edit, or drop commits before they land, turning a messy work-in-progress history into a clean, reviewable narrative.
git revert vs git reset
Two different ways to undo changes in Git — revert creates a new commit that inverses a prior one, while reset moves the branch pointer and rewrites history — and when to use each safely.
Related Reading
Related Study Notes in Software Engineering
Browse all study notesMicroservices Study Notes
Software Architecture · 30 topics
Software EngineeringTesting & TDD Study Notes
Software Testing · 30 topics
Software EngineeringDesign Patterns Study Notes
Software Design · 30 topics
Software EngineeringSoftware Engineering Study Notes
Python · 40 topics
Software EngineeringSystem Design Study Notes
Architecture · 40 topics