Git Reflog: Recovering Lost Commits, Branches, and Stashes
Master git reflog to recover lost work after bad rebases, accidental resets, deleted branches, and dropped stashes. Your safety net for every Git disaster.
What you'll learn
- ✓What the reflog is and how Git tracks HEAD movements
- ✓Recovering commits lost after git reset --hard
- ✓Restoring deleted branches and their commits
- ✓Retrieving dropped stashes
- ✓Using reflog with cherry-pick and rebase to rebuild history
Prerequisites
- •Basic Git commands (commit, reset, branch, stash)
- •Understanding of commit hashes
Your Safety Net: The Reflog
Every time HEAD moves in your Git repository — every commit, checkout, rebase, reset, merge, pull — Git records where it was and where it went. This log is called the reflog, and it is your safety net for nearly every Git disaster.
Unlike git log, which shows commit history following parent pointers, the reflog shows the history of where HEAD has pointed. Even if you remove a commit from your branch history with git reset --hard, the commit still exists in the object store, and the reflog still has the reference.
git reflog
# a1b2c3d HEAD@{0}: commit: feat: add search API
# f4e5d6c HEAD@{1}: checkout: moving from main to feature/search
# f4e5d6c HEAD@{2}: commit: docs: update README
# 7890abc HEAD@{3}: pull: Fast-forward
Each entry shows: the commit hash, the reflog reference (HEAD@{n}), the action, and a description.
Scenario 1: Recovering After git reset —hard
This is the most common disaster. You reset to an earlier commit and lost your recent work:
# You had three important commits
git log --oneline
# a1b2c3d feat: add search results page
# b2c3d4e feat: add search API endpoint
# c3d4e5f feat: add search database schema
# d4e5f6a chore: initial project setup
# Oops -- you meant to reset one commit but went back three
git reset --hard d4e5f6a
# Your three commits are gone from git log
git log --oneline
# d4e5f6a chore: initial project setup
The reflog remembers everything:
git reflog
# d4e5f6a HEAD@{0}: reset: moving to d4e5f6a
# a1b2c3d HEAD@{1}: commit: feat: add search results page
# b2c3d4e HEAD@{2}: commit: feat: add search API endpoint
# c3d4e5f HEAD@{3}: commit: feat: add search database schema
Recover by resetting back to where you were:
# Reset to the commit before the bad reset
git reset --hard HEAD@{1}
# Or use the commit hash directly
git reset --hard a1b2c3d
# All three commits are back
git log --oneline
# a1b2c3d feat: add search results page
# b2c3d4e feat: add search API endpoint
# c3d4e5f feat: add search database schema
# d4e5f6a chore: initial project setup
Scenario 2: Recovering a Deleted Branch
You deleted a branch and then realized you still needed it:
# Delete a branch
git branch -D feature/payment-flow
# Deleted branch feature/payment-flow (was e5f6a7b).
Git tells you the commit hash it was pointing to. If you missed it, the reflog has it:
git reflog | grep payment-flow
# e5f6a7b HEAD@{5}: commit: feat: add payment confirmation
# d4e5f6a HEAD@{6}: checkout: moving from main to feature/payment-flow
Recreate the branch:
git branch feature/payment-flow e5f6a7b
# Or create and switch to it
git checkout -b feature/payment-flow e5f6a7b
All commits on that branch are restored because the objects never left the repository.
Scenario 3: Recovering After a Bad Rebase
Interactive rebase went wrong and your branch is mangled:
# Before rebase: clean branch with 5 commits
git log --oneline
# a1b2c3d feat: add validation
# b2c3d4e feat: add form component
# c3d4e5f feat: add form styles
# d4e5f6a feat: add form tests
# e5f6a7b feat: add form route
# Rebase goes wrong (conflicts, wrong squash, etc.)
git rebase -i HEAD~5
# ... mistakes happen ...
# Branch is now broken
git log --oneline
# x9y8z7w feat: add form (squashed incorrectly, lost changes)
# e5f6a7b feat: add form route
The reflog saves you:
git reflog
# x9y8z7w HEAD@{0}: rebase (finish): ...
# ... rebase entries ...
# a1b2c3d HEAD@{7}: commit: feat: add validation
# Go back to before the rebase started
git reset --hard a1b2c3d
Your original 5 commits are back, exactly as they were.
Scenario 4: Recovering a Dropped Stash
You dropped a stash by accident:
git stash drop stash@{0}
# Dropped stash@{0} (abc123def456...)
Stashes are commits too, and the reflog tracks them. But stash entries use a separate reflog. Once dropped, you need to search for the orphaned commit:
# Find dangling stash commits
git fsck --no-reflog | grep "dangling commit"
# dangling commit abc123def456
# dangling commit 789xyz000111
# Inspect each to find your stash
git show abc123def456
# This shows the stash content -- look for your changes
# Apply the recovered stash
git stash apply abc123def456
# Or create a branch from it
git checkout -b recovered-stash abc123def456
If you know roughly when you created the stash, filter by date:
git log --oneline --all --walk-reflogs --since="2 hours ago" | head -20
Scenario 5: Finding What Happened
Sometimes you just need to understand what went wrong. The reflog is a detailed audit trail:
# Full reflog with timestamps
git reflog --date=iso
# a1b2c3d HEAD@{2026-07-08 14:30:15 -0500}: commit: feat: add search
# f4e5d6c HEAD@{2026-07-08 14:28:03 -0500}: checkout: moving from main to feature/search
# Reflog for a specific branch
git reflog show feature/search
# a1b2c3d feature/search@{0}: commit: feat: add search
# f4e5d6c feature/search@{1}: branch: Created from main
# Reflog with diff stats
git reflog --stat
Advanced Reflog Techniques
Using time-based references
The reflog supports time-based lookups:
# Where was HEAD 30 minutes ago?
git show HEAD@{30.minutes.ago}
# Where was main yesterday at 3pm?
git show main@{"yesterday 15:00"}
# Diff between now and 2 hours ago
git diff HEAD@{2.hours.ago}
# What did the branch look like last Monday?
git log --oneline feature/search@{"last Monday"}
Cherry-picking recovered commits
If you do not want to reset your entire branch, cherry-pick specific recovered commits:
# Find the lost commits in reflog
git reflog | grep "feat: add validation"
# a1b2c3d HEAD@{12}: commit: feat: add validation
# Cherry-pick just that commit onto your current branch
git cherry-pick a1b2c3d
Recovering from a force push
Someone force-pushed over your commits on a remote branch. If you had a local copy:
# Your local reflog still has the pre-force-push state
git reflog show origin/main
# d4e5f6a origin/main@{0}: fetch: forced-update (new, wrong)
# a1b2c3d origin/main@{1}: fetch: fast-forward (old, correct)
# Recover the commits
git checkout -b recovered-main origin/main@{1}
Reflog Expiry and Limits
The reflog is not permanent. Git cleans up old entries based on configuration:
# Check current expiry settings
git config gc.reflogExpire # default: 90 days
git config gc.reflogExpireUnreachable # default: 30 days
- Reachable entries (commits still in branch history) expire after 90 days.
- Unreachable entries (commits removed from branches) expire after 30 days.
After expiry, git gc removes the reflog entries and eventually the orphaned objects.
To extend the window:
# Keep unreachable reflog entries for 180 days
git config gc.reflogExpireUnreachable "180 days"
To prevent expiry entirely (not recommended for large repos):
git config gc.reflogExpire never
Practical Recovery Checklist
When something goes wrong, follow this sequence:
-
Do not panic. If the commit existed at any point, it is almost certainly still in the object store.
-
Check the reflog first:
git reflog
- Search for specific actions:
git reflog | grep "commit:" # Find commits
git reflog | grep "rebase" # Find rebase operations
git reflog | grep "reset" # Find reset operations
- If the reflog does not help, check for dangling objects:
git fsck --no-reflog
- Recover the commit using
reset,cherry-pick, orbranch:
git reset --hard <hash> # Restore entire branch state
git cherry-pick <hash> # Pick individual commits
git branch recovery <hash> # Create a branch at the commit
- Verify the recovery:
git log --oneline
git diff HEAD~1
Wrap-Up
The reflog makes Git remarkably forgiving. Almost no local operation is truly destructive as long as the reflog entries and object store are intact. The key insight is that git reset --hard, branch deletion, and failed rebases do not delete commits — they only move pointers. The reflog tracks every pointer movement, so you can always trace back to where things were before they went wrong. Make a habit of checking git reflog before resorting to more drastic recovery measures. It is almost always the fastest path back to safety.
Related articles
- Git Git reflog Recovery Tutorial
Use git reflog to recover lost commits, branches, and stashes after rebases, resets, and bad merges. A practical walkthrough of how Git remembers where HEAD has been.
- Git Advanced Git Rebase Techniques: Interactive, Autosquash, and Rebase Onto
Master interactive rebase, autosquash, fixup commits, and rebase --onto for clean Git history. Advanced techniques for rewriting, reorganizing, and cleaning up commits.
- Git Finding Bugs with Git Bisect: A Practical Guide
Use git bisect to binary search through your commit history and pinpoint the exact commit that broke your code. Covers manual, automated, and advanced bisect workflows.
- Git Git Hooks for Automation: Pre-commit, Pre-push, and Husky
Automate code quality checks with Git hooks. Set up pre-commit linting, pre-push testing, commit message validation, and manage hooks with Husky across teams.