GitRecovering Lost Commits

Recovering Lost Commits

Lost commits feel terrifying, but they are usually still in .git/objects/ — just disconnected from any branch or tag. Git's garbage collector will not delete them for at least 30–90 days. That window is your safety net: with git reflog and git fsck you can almost always resurrect work that seemed gone.

The scenarios people panic about

Scenario

First place to look

Recovery command

Deleted a branch

git reflog show <branch>

git branch <name> <sha>

git reset --hard went too far

git reflog

git reset --hard HEAD@{n}

Bad rebase

git reflog or ORIG_HEAD

git reset --hard ORIG_HEAD

Force-pushed over good work

git reflog on the local clone

git push --force-with-lease <sha>:branch

git commit --amend overwrote a commit you liked

git reflog

git reset --hard HEAD@{1}

Lost a stash

git fsck --unreachable

git stash apply <sha>

Detached HEAD with new commits, then switched away

git reflog

git branch rescue <sha>

The reflog is the first stop

Find the SHA you lost

Bash
git reflog
# 1a2b3c4 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
# 9f8e7d6                HEAD@{1}: commit: the work I just lost
# 7c8b9a0                HEAD@{2}: commit: previous good work
# ...

# Note the SHA you want and restore it
git branch rescue 9f8e7d6           # safest: park it on a branch first
git switch rescue
# Verify the work is really there, then merge or reset as you wish.
When the reflog is not enough: git fsck

If you cloned fresh, lost the reflog entry, or deleted a stash, git fsck walks the object database looking for dangling objects — commits, trees, and blobs that no ref points to.

Look for orphaned commits

Bash
git fsck --lost-found
# Checking object directories: 100% (256/256), done.
# dangling commit 9f8e7d6abcdef123...
# dangling commit 1a2b3c4567890def...
# dangling blob   3a4b5c6789abcdef...

# Inspect each candidate
git show 9f8e7d6abcdef123

# Resurrect on a branch
git branch recovered 9f8e7d6abcdef123
What dangling means
A dangling object is in `.git/objects/` but is not reachable from any branch, tag, or reflog entry. Git keeps it around until `git gc` runs (and only then if it has aged past the expiry threshold).
Recovering a deleted branch

Branches do not delete commits — they just remove the label

Bash
git branch -D feature           # branch deleted

# Step 1: find its last tip
git reflog show feature
# 9f8e7d6 feature@{0}: commit: WIP — almost done
# (or if reflog show fails because the branch is gone, search the HEAD reflog)
git reflog | grep feature

# Step 2: recreate the branch at that SHA
git branch feature 9f8e7d6
# Or in one step with switch
git switch -c feature 9f8e7d6
Recovering after a hard reset

The classic 'I just lost 4 hours of work' recipe

Bash
# What happened
git reset --hard HEAD~5

# The fix
git reflog
# 1a2b3c4 HEAD@{0}: reset: moving to HEAD~5
# 9f8e7d6 HEAD@{1}: commit: the commit I want back

git reset --hard HEAD@{1}
# main is back to its pre-reset position.
Recovering after a bad force-push
Warning
If a teammate force-pushed over good work, **do not pull yet** — your local clone still has the old commits, and pulling will align you with their broken history. Read the reflog first.

Restore on the remote

Bash
# On your local clone, find the last good SHA you had
git reflog show origin/main
# 9f8e7d6 origin/main@{1}: fetch origin: forced-update

# Push the good SHA back to the remote, overwriting the bad push
git push --force-with-lease origin 9f8e7d6:main
# (Do this only after coordinating with the team!)
Recovering a lost stash

Stash entries are commits in the object database with no normal ref. After git stash drop they become unreachable — but fsck can still find them.

Find an orphaned stash

Bash
git fsck --unreachable | grep commit
# unreachable commit 9f8e7d6abcdef...
# unreachable commit 7c8b9a0123456...

# Each is a candidate. Stash commits have two parents (HEAD + index).
git show 9f8e7d6
# Look for: "WIP on main: ..." — that is a stash commit.

git stash apply 9f8e7d6        # bring it back into the working tree
ORIG_HEAD and FETCH_HEAD: pre-built lifelines

Some Git operations record the previous tip in special refs before they move HEAD. They are easier to type than digging through the reflog.

The cheat-sheet refs

Bash
# After a reset, merge, or rebase, ORIG_HEAD points at where you were
git reset --hard ORIG_HEAD

# After a fetch, FETCH_HEAD points at the just-fetched tip
git log FETCH_HEAD

# After a merge, MERGE_HEAD points at the merged-in branch tip
git log MERGE_HEAD
When recovery is impossible
Warning
Some operations leave no trace. Past these points the commits are gone for good — even `git fsck` will return nothing.
  • You ran git gc --prune=now after the commits became unreachable.

  • You ran git reflog expire --expire-unreachable=now --all and then git gc.

  • The commit aged past gc.reflogExpireUnreachable (default 30 days) and gc has since run.

  • The commits were only ever in another clone you have since deleted.

  • The work was only in the working directory or stash that you then clean-ed.

The golden habit: a safety branch before risk

One command that prevents most disasters

Bash
# Before any operation you are unsure about
git switch -c backup-$(date +%Y%m%d-%H%M%S)
git switch -                              # back to where you were

# The backup branch costs you nothing and lives until you delete it.
# Recovery becomes a one-line: git switch backup-...
Mental model
Commits are content-addressed objects. As long as the object is on disk, it can be made reachable again by pointing any ref at it. The reflog and `fsck --lost-found` are just two different ways of discovering the SHAs Git is still holding onto for you.
Tip
Once you have recovered your work, double-check by running `git log --oneline --all` and inspecting the files. *Then* delete the rescue branch. Confirming recovery is part of recovery — do not stop at “the SHA exists.”