How do you use git reflog to recover lost commits after a hard reset, bad rebase, or accidental branch deletion?
Quick Answer
git reflog records every movement of HEAD and branch tips, including commits orphaned by reset, rebase, or branch deletion. To recover, run git reflog to find the lost commit's SHA, then use git reset --hard <sha>, git branch <name> <sha>, or git cherry-pick <sha> to restore it. Entries expire after 30-90 days.
Detailed Answer
Think of git reflog as the undo history in a word processor, except it records every cursor movement across your entire document's lifetime. Even if you delete a paragraph, rewrite a chapter, or revert to an earlier draft, every previous state is saved in the undo stack. As long as the history has not expired, you can jump back to any prior state. The reflog is Git's ultimate safety net that makes almost every destructive operation reversible.
Git reflog (reference log) tracks every time HEAD changes position: every commit, checkout, merge, rebase, reset, cherry-pick, and pull. Each entry records the old SHA, new SHA, timestamp, and a human-readable description of the action. Unlike git log, which only shows commits reachable from the current branch tip, reflog shows every commit HEAD has ever pointed to, including those orphaned by destructive operations. Branch-specific reflogs also exist, recording every position each branch pointer has occupied.
Internally, reflog data is stored as plain text files in .git/logs/HEAD (for HEAD movements) and .git/logs/refs/heads/<branch> (for branch-specific history). Each line follows the format: old-sha new-sha author timestamp action-description. The reflog is purely local and is never pushed to remotes or shared with collaborators. Reflog entries have configurable expiration: gc.reflogExpire controls how long reachable entries live (default 90 days), and gc.reflogExpireUnreachable controls unreachable entries (default 30 days). After expiration, git gc removes the entries and may prune the corresponding objects if no other reference points to them.
In production, the three most common reflog rescue scenarios are recovering from git reset --hard, undoing a bad rebase, and restoring deleted branches. For a hard reset: you ran git reset --hard HEAD~3 and lost three commits. Run git reflog, find the entry just before the reset (it will show 'reset: moving to HEAD~3'), note the SHA at HEAD@{1}, and run git reset --hard HEAD@{1} to restore. For a bad rebase: find the pre-rebase state in reflog (look for 'rebase: start') and create a branch at the previous position. For a deleted branch: the branch tip's last known SHA is in the reflog, so git branch recovered-branch <sha> recreates it. Some teams train developers to run git reflog immediately after any 'oh no' moment, before doing anything else that might further complicate recovery.
A critical gotcha: reflog is local only. If you lose your laptop or re-clone the repository, the reflog is gone and so is your safety net. This is why pushing regularly to the remote matters even for work-in-progress branches. Another trap: running git reflog expire --all or aggressive git gc will destroy reflog entries and potentially make orphaned commits unrecoverable. Never run these commands casually. Also, reflog entries use the HEAD@{N} syntax where N is the position, but these positions shift as new entries are added. Prefer using the actual SHA hash rather than HEAD@{N} when scripting recovery steps, because a new commit or checkout between finding and using the reflog entry will change the N offset.