On this page
You stashed something, moved on, and now it’s gone. Maybe you ran git stash drop one too many times. Maybe a rebase went sideways and took the stash list
with it. Maybe you ran git stash pop into a conflict, cleaned up, and
dropped the entry before noticing the resolution was wrong.
The good news is that Git almost never deletes anything on the spot. A dropped stash is unlinked, not erased, and it stays in the object database until garbage collection decides otherwise. Here’s how to get it back.
A stash is just commits
This is the part that makes the recovery make sense. git stash push doesn’t
write to a special sidecar file — it creates real commits and points a ref at
them. Two commits, in the normal case:
$ git stash push -m "wip: the thing I care about"
Saved working directory and index state On main: wip: the thing I care about
Look at what that actually produced:
$ git log --format='%h parents=%p %s' --all --reflog
45199d0 parents=61bfbab 813b9d5 On main: wip: the thing I care about
813b9d5 parents=61bfbab index on main: 61bfbab init
61bfbab parents= init
The index on main commit holds what was staged. The On main: commit holds
the working tree, and it has two parents: the commit you were sitting on,
and that index commit. stash@{0} is just a ref pointing at the second one.
git stash drop deletes the ref. The commits are untouched. They’re simply
unreachable now — nothing points to them, so nothing walks to them.
Finding it again
git fsck walks the object database and reports anything nothing else points
at:
$ git fsck --unreachable | grep commit
unreachable commit 813b9d5fb7fef45cc200e7b0f10e5a1e60714c1d
unreachable commit 45199d06525ecbd5658491c9b02a04f73ebbac64
Two hits for one stash — which is exactly where most recovery instructions leave you guessing. They are not interchangeable. Apply the wrong one and you get only what happened to be staged at the time, which can look convincingly like your work while quietly missing most of it.
The stash commit is the one with two parents. You can read that straight off the object:
$ git cat-file -p 45199d06 | grep '^parent'
parent 61bfbab...
parent 813b9d5...
Or let the shell pick it for you:
git fsck --unreachable | grep commit | awk '{print $3}' |
while read sha; do
[ "$(git cat-file -p "$sha" | grep -c '^parent')" -ge 2 ] &&
echo "$sha $(git log -1 --format='%s' "$sha")"
done
That prints only real stash commits, each with the message you wrote — which is usually enough to recognise the one you want.
Getting the work back
Once you have the SHA, git stash apply takes it directly. No ref required:
$ git stash apply 45199d06525ecbd5658491c9b02a04f73ebbac64
On branch main
Changes not staged for commit:
modified: a.txt
Your changes are back in the working tree.
If you’d rather look before you leap, inspect it first — a stash commit diffs against its first parent like anything else:
git show 45199d06 # the working-tree changes
git show 45199d06^2 # what was staged at stash time
And if you want it back in the stash list as a proper entry rather than applied straight to your tree:
git stash store -m "recovered" 45199d06
The catch worth knowing
This works because the objects are still there, and that isn’t forever. git gc
prunes unreachable objects once they age past gc.pruneExpire, which defaults to
two weeks. Garbage collection also runs automatically — git gc --auto fires
during ordinary commands once enough loose objects pile up.
So: a stash you dropped this morning is almost certainly recoverable. One from
last quarter probably isn’t. If you’ve just realised something important is
missing, do the fsck now and don’t run git gc first.
One thing that will not help: git reflog. The stash has its own reflog at
refs/stash, and dropping the stash takes that with it. The plain git reflog
only covers HEAD.
Or don’t do any of this
I write a Git client, so I’ll be honest about the pitch: everything above is a recipe I’d rather nobody had to run under pressure. Knowing that a stash is two commits and that the real one has two parents is genuinely useful, and I think it’s worth knowing. But at the moment you actually need it, you’re stressed, you’re guessing at SHAs, and the failure mode is silently restoring the wrong half of your work.
In GitDesktop that same fsck walk runs behind a list: recoverable stashes,
each with its message, its date, and a diff you can read before you commit to
anything. Same objects, same operation, minus the shell pipeline and minus the
chance of applying the index twin by mistake.
Either way, the thing to take away is the one that surprises people: it’s still there. Git is much more reluctant to destroy your work than it looks.
Written while building GitDesktop.
A native Git client for GitHub, GitLab and Bitbucket — free and open source, with AI you control or hide entirely.