Skip to content
Codeloom
Git

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.

·8 min read · By Codeloom
Intermediate 12 min read

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:

  1. Do not panic. If the commit existed at any point, it is almost certainly still in the object store.

  2. Check the reflog first:

git reflog
  1. Search for specific actions:
git reflog | grep "commit:"     # Find commits
git reflog | grep "rebase"      # Find rebase operations
git reflog | grep "reset"       # Find reset operations
  1. If the reflog does not help, check for dangling objects:
git fsck --no-reflog
  1. Recover the commit using reset, cherry-pick, or branch:
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
  1. 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.