Understanding the Git Reflog
The Git reflog (reference log) is a hidden safety net that records every movement of HEAD and branch tips in your local repository. Think of it as a journal that quietly tracks where your HEAD has been, what commits you've checked out, and every branch creation, deletion, rebase, or reset you've performed — even the destructive ones. Unlike Git's commit history which only records permanent snapshots, the reflog captures transient pointer movements, giving you a way to rewind time when things go wrong.
By default, the reflog is local to your clone and is not pushed to remotes. It lives in the .git/logs/ directory and retains entries for 90 days by default (configurable via gc.reflogExpire). This makes it a powerful, private undo mechanism that works even when your commit history appears to have lost commits entirely.
What Exactly Does the Reflog Track?
The reflog tracks every time a ref (like HEAD or a branch name) is updated to point to a new commit. Specifically, it records:
- HEAD movements — every checkout, commit, merge, rebase, or reset that changes where HEAD points
- Branch tip updates — every commit, merge, reset, or rebase that moves a branch pointer
- Stash operations — every git stash push, pop, or apply
- Branch deletions — the exact commit a branch pointed to right before it was deleted
Each reflog entry contains a timestamp, the action performed, and the before-and-after commit hashes. This gives you a chronological audit trail of every significant pointer change in your repository.
Why the Reflog Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →The reflog is arguably Git's most important recovery tool. Here are the disaster scenarios where it saves the day:
- Accidental hard reset — you ran
git reset --hard HEAD~5and lost five commits you actually needed - Detached HEAD panic — you made commits on a detached HEAD, checked out a branch, and can't find those commits anymore
- Branch deletion remorse — you deleted a branch with
git branch -D feature-xand immediately realized you still needed that work - Botched rebase — an interactive rebase went sideways, commits were squashed or dropped incorrectly, and you want the pre-rebase state back
- Amend regret — you amended a commit and now need the original version for reference or comparison
- Stash recovery — you dropped a stash entry and need those changes back
In all these cases, the commits themselves often still exist as dangling objects in Git's object database. The problem is you no longer have a branch or tag pointing to them. The reflog gives you the commit hashes you need to resurrect them.
Viewing the Reflog
The primary command for inspecting the reflog is git reflog. Running it without arguments shows the reflog for HEAD:
git reflog
This outputs something like:
a1b2c3d HEAD@{0}: commit: Add payment validation
e4f5g6h HEAD@{1}: rebase (finish): returning to refs/heads/main
e4f5g6h HEAD@{2}: rebase (pick): Refactor user model
i7j8k9l HEAD@{3}: checkout: moving from feature/auth to main
m0n1o2p HEAD@{4}: reset: moving to HEAD~2
q3r4s5t HEAD@{5}: commit: Fix login bug
Each entry shows the commit hash where HEAD now points after the operation, the relative index (HEAD@{n}), and a human-readable description of what happened. The most recent entry is HEAD@{0}, and you can reference any entry using this syntax.
Viewing the Reflog for a Specific Branch
You can also view the reflog for any branch or ref by passing it as an argument:
git reflog show main
git reflog show feature/checkout
git reflog show stash
For even more detail, use git reflog show with the --date= flag to see precise timestamps:
git reflog show --date=iso main
git reflog show --date=relative HEAD
The Underlying Log Files
The reflog data is stored as plain text files. You can inspect them directly:
cat .git/logs/HEAD
cat .git/logs/refs/heads/main
Each line follows the format: old-hash new-hash author timestamp action description. While you can read these files directly, using git reflog is far more practical for recovery purposes.
Recovery Scenarios: Step-by-Step
Scenario 1: Recovering Lost Commits After a Hard Reset
You've accidentally run git reset --hard HEAD~3 and wiped out three commits. The commits still exist as objects; you just need to find their hashes and point a branch at them.
First, examine the reflog to find the commit that HEAD pointed to before the reset:
git reflog
Look for the line that shows the reset operation. The commit hash in that line is where HEAD was before the destructive reset. For example:
d4e5f6g HEAD@{1}: reset: moving to HEAD~3
a7b8c9d HEAD@{2}: commit: Add export feature <-- this is the commit you want back
The hash a7b8c9d is the tip of your branch before the reset. To restore it, create a new branch pointing to that commit:
git branch recovered-work a7b8c9d
Alternatively, if you want to move your current branch back to that point directly (and you haven't made new commits since the reset), use:
git reset --hard HEAD@{1}
The HEAD@{1} notation directly references the state of HEAD one step before the current position — exactly the commit you were on before the reset.
Scenario 2: Recovering Commits Made on a Detached HEAD
You were exploring a commit, made several commits on a detached HEAD, then checked out a branch and lost track of those commits.
Run git reflog and look for the checkout entries that bracket your detached HEAD session:
git reflog
You'll see something like:
f1e2d3c HEAD@{0}: checkout: moving from 9a8b7c6 to main
a4b5c6d HEAD@{1}: commit: Experimental feature v3
e7f8g9h HEAD@{2}: commit: Experimental feature v2
i0j1k2l HEAD@{3}: commit: Experimental feature v1
9a8b7c6 HEAD@{4}: checkout: moving from main to 9a8b7c6
The commits at HEAD@{1} through HEAD@{3} are your detached work. To save them, create a branch pointing to the most recent commit:
git branch experimental-findings f1e2d3c
Or use the relative reference directly:
git branch experimental-findings HEAD@{1}
Now you have a proper branch pointing to all that work, and you can merge or rebase it as needed.
Scenario 3: Recovering a Deleted Branch
You deleted a branch with git branch -D feature/payments and need it back. The branch's reflog is deleted along with the branch, but HEAD's reflog may still contain clues if you recently checked out that branch. More reliably, you can use git reflog with the --all flag or search for the branch's last commit.
First, try to find the commit hash from the HEAD reflog where you last interacted with the branch:
git reflog | grep -B 2 -A 2 "feature/payments"
If that doesn't work, Git still retains the branch's reflog entries temporarily. You can try:
git reflog show feature/payments
This often works even after deletion because Git doesn't immediately expire branch reflogs. If successful, you'll see the branch's tip commit. Recreate the branch:
git branch feature/payments <commit-hash>
As a fallback, use git fsck to find dangling commits, then inspect them to identify your lost branch tip:
git fsck --lost-found
git show <suspicious-commit-hash>
Scenario 4: Undoing a Botched Rebase
An interactive rebase went wrong — you accidentally dropped commits, squashed things incorrectly, or the rebase simply produced a mess. You want the exact state of your branch before the rebase began.
The reflog records both the start and finish of a rebase. Look for entries like:
git reflog show feature/refactor
You'll see:
c4d5e6f feature/refactor@{0}: rebase (finish): ...
a1b2c3d feature/refactor@{1}: rebase (pick): ...
e4f5g6h feature/refactor@{2}: rebase (pick): ...
i7j8k9l feature/refactor@{3}: rebase (start): returning to refs/feature/refactor
m0n1o2p feature/refactor@{4}: commit: Pre-rebase state <-- this is what you want
Find the commit just before the rebase (start) entry — that's your pre-rebase branch tip. Reset your branch to that commit:
git checkout feature/refactor
git reset --hard feature/refactor@{4}
Or use the specific commit hash:
git reset --hard m0n1o2p
Your branch is now exactly as it was before the rebase. You can try the rebase again with more careful planning.
Scenario 5: Recovering an Amended Commit's Original Version
You amended a commit and now need the original version. The reflog captured both states:
git reflog
Look for consecutive commit entries where one is an amend:
d4e5f6g HEAD@{0}: commit (amend): Add payment validation
a1b2c3d HEAD@{1}: commit: Add payment validation <-- original commit
The hash a1b2c3d is the original commit before amending. You can create a branch pointing to it, compare it, or cherry-pick it:
git branch original-commit a1b2c3d
git diff a1b2c3d..d4e5f6g
If you want to undo the amend and go back to the original, simply reset:
git reset --soft HEAD@{1}
This moves HEAD back to the original commit while keeping your working directory and index intact, effectively un-amending while preserving your staged changes.
Scenario 6: Recovering a Dropped Stash
You dropped a stash entry with git stash drop or cleared all stashes with git stash clear. The stash's reflog retains the commit hashes.
git reflog show stash
You'll see entries like:
a1b2c3d stash@{0}: WIP on main: 9a8b7c6 Fix login bug
e4f5g6h stash@{1}: WIP on feature: i7j8k9l Add export
To recover a dropped stash, create a branch from its commit hash:
git branch recovered-stash a1b2c3d
Then you can inspect the branch, cherry-pick from it, or apply its changes:
git checkout recovered-stash
git switch main
git cherry-pick a1b2c3d
After confirming the recovery, you can delete the temporary branch:
git branch -D recovered-stash
Advanced Reflog Techniques
Using Timed References for Precision
The reflog supports time-based qualifiers that let you reference states at specific moments. This is incredibly powerful when you know when a mistake happened:
git checkout HEAD@{2.hours.ago}
git show main@{yesterday}
git diff HEAD@{2024-01-15T09:30:00}
git reset --soft HEAD@{30.minutes.ago}
These time qualifiers work with any ref and resolve to the nearest reflog entry at or before the specified time. They're perfect for undoing changes you made "about an hour ago" without hunting through reflog output.
Combining Reflog with git log for Context
When recovering commits, you often need more context than just a hash. Pipe reflog output into git log to see full commit messages:
git log --oneline $(git reflog --pretty=%H | head -10)
Or use the reflog's verbose format directly:
git reflog --format="%h %gd %gs %s" --date=short
Expiring and Managing the Reflog
By default, reflog entries expire after 90 days for unreachable commits and never for reachable ones. You can tune this with configuration:
# Set reflog expiration to 180 days
git config gc.reflogExpire 180.days.ago
# Never expire reflog entries
git config gc.reflogExpire never
# Set expiration specifically for unreachable commits
git config gc.reflogExpireUnreachable 30.days.ago
You can also manually expire entries with:
git reflog expire --expire=30.days.ago --all
Be cautious with manual expiration — once expired entries are removed and garbage collected, recovery becomes impossible without external backups.
Best Practices for Reflog Recovery
- Don't panic — the reflog almost always has your back. Avoid running
git gcorgit reflog expireimmediately after a mistake, as this could permanently remove the very entries you need for recovery. - Read before you reset — before doing any recovery reset, run
git reflogfirst and confirm you're targeting the correct entry. A second mistaken reset compounds the problem. - Create a recovery branch first — instead of immediately resetting a branch, create a temporary branch pointing to the recovered commit. This gives you a safe space to inspect, diff, and decide without risking further pointer movement.
- Use time-based references for quick fixes — when you know you messed up five minutes ago,
git reset --hard HEAD@{5.minutes.ago}is often faster and less error-prone than manually scanning reflog output. - Configure longer expiration windows — set
gc.reflogExpireto a generous value like 180 days orneverin your global Git config. The storage cost is negligible and the safety net is invaluable. - Pair the reflog with
git fsck— if reflog entries have expired,git fsck --lost-foundcan still find dangling commit objects. Combine both tools for maximum recovery coverage. - Don't push reflog-dependent recoveries — the reflog is strictly local. If you need to recover a pushed branch that was force-pushed incorrectly, you'll need remote reflogs (if your hosting service provides them) or collaboration from teammates who still have the old history.
- Document recovery procedures for your team — make sure everyone on your team knows that
git reflogexists and how to use it. A simple team wiki page with these scenarios can save hours of panic.
Conclusion
The Git reflog is your local time machine — an automatic, low-overhead journal that tracks every significant movement of HEAD and branch pointers. Whether you've accidentally hard-reset away a week of work, lost commits on a detached HEAD, deleted a branch prematurely, or botched an interactive rebase, the reflog provides a reliable path back to safety. By learning to read reflog output, use relative and time-based references, and follow a calm, methodical recovery workflow, you transform what could be a catastrophic data loss into a minor, reversible misstep. The key is knowing the tool exists, keeping it well-configured with generous expiration policies, and practicing recovery in low-stakes situations so you're ready when real disaster strikes. With the reflog in your toolkit, you can work more boldly, experiment freely, and recover gracefully from almost any local Git mishap.