DEV Community

Cover image for Recover a lost Git stash in two steps
Mehdi M.
Mehdi M.

Posted on • Updated on

Recover a lost Git stash in two steps

Sometimes you end up stashing code, then at some point those stashes get cleaned. And one day, you may encounter the situation I faced this week:

Fine, this code is merged, so let’s delete the related stashes. Done! And… hey… wasn’t this part of the feature supposed to be in the codebase? Is the stash I just deleted lost forever?

Fortunately, I managed to recover lost stashes. I’m not a Git expert, but here’s what worked for me after going through various readings (including some Stack Overflow answers).

Here’s the two-steps recovery procedure.

1. List lost stashes

Let’s run this command for a project where all stashes were trashed:

git fsck --unreachable | grep commit | cut -d ' ' -f3 | xargs git log --merges --no-walk
Enter fullscreen mode Exit fullscreen mode

It returns a list of lost stashes, ordered by date.
Ho, 3 lost stashes!

  • To quit the list of stashes, press the Q key.
  • To navigate in a long stashes list, use up and down arrows.
  • For Windows user, maybe johnwait’s comment will help you during the battle.

2. Send a lost stash back where it comes from

Let’s use the commit hash of the second stash:

git update-ref refs/stash 4b3fc45c94caadcc87d783064624585c194f4be8 -m "My recovered stash"
Enter fullscreen mode Exit fullscreen mode

And that’s it! You’ll find your stash as usual, using git stash list or by having a look in your favorite Git client.

Gotchas

1. I still can’t see my recovered stash

Retry using the --create-reflog parameter (thanks studoggithub):

git update-ref refs/stash 4b3fc45c94caadcc87d783064624585c194f4be8 --create-reflog -m "My recovered stash"
Enter fullscreen mode Exit fullscreen mode

2. My Git isn’t in English

If your Git isn’t in English, you’ll have to run alias git='LANG=en_GB git' each time you want to recover a set of stashes (thanks mathieuschopfer).

Some advices

Commit messages are healthy

Always use a commit message using git stash save -m "My commit message": without message, the only informations helping to identify a stash are its timestamp and the branch it was saved from, which may not be enough compared to a strong explicit name.

Commit messages also help Git clients:

  • GitUp, the Git client I use, completely fails at showing unnamed stashes. That’s probably why you can’t create a stash in GitUp without giving it a name, which is great!
  • The well-known SourceTree succeeds at showing unnamed stashes, but as you can guess, the list isn’t friendly to browse: Unnamed stashes in SourceTree

Yes, git stash apply > git stash pop

Unlike git stash pop, git stash apply does not remove the stash from the list of stashes, which can avoid some loss.

Branches > stashes

Finally, I’d recommend to avoid git stash. Instead, try to use a branch. This seems obvious but it only comes to me as I was finding a way to recover a stash: maybe I should use temporary branches instead of stashes. Using the Git Flow method at work, this could have come to my mind before encountering a painful experience.

If you have any stash hint or experience that you want to share, comments are welcome.

Discussion (21)

Collapse
johnwait profile image
johnwait

On Windows, in a good old command window (your usual cmd.exe), step 1. could be translated to:

for /f "tokens=3" %a in ('git fsck --unreachable ^| find "commit"') do @git log --merges --no-walk %a

If your Git speaks français, and you're one to choose the more complicated path, you could use something like:

for /f "tokens=1,2,3,4" %a in ('git fsck --unreachable ^| find "commit"') do @if "%c"=="inatteignable" (@git log --merges --no-walk %d) else (@git log --merges --no-walk %c)

or, really, keep it simple with alias git='LANG=en_GB git' instead.

Bonus

For PowerShell aficionados, here's a command that should work regardless of your Git's locale (well, as long as a commit is still referred to as commit):

(git fsck --unreachable | Select-String "commit") -split '\s+' |
Select-String -pattern "^[0-9a-fA-F]{40}$" |
ForEach-Object { git log --merges --no-walk $_ }

(Really useful post btw!)

Collapse
meduzen profile image
Mehdi M. Author

Thanks! A link to your comment has been added to the article.

Collapse
studoggithub profile image
studog-github

This almost worked for me. I found that the update-ref command created the stash ref correctly but git stash list still did not show anything.

I added the --create-reflog parameter on a second try, and then things worked.

git version 2.22.0 on Ubuntu 18.04

Collapse
meduzen profile image
Mehdi M. Author

Thanks! I don’t have this issue in Git 2.20.1 (macOS 10.14.16) but added a note in the article.

Collapse
mathieuschopfer profile image
Mathieu Schopfer

This may fail if you don't use git in English. To quickly fix it:
alias git='LANG=en_GB git'

Collapse
meduzen profile image
Mehdi M. Author

:o

What is different when you use Git in another language? I didn't know it existed.

Collapse
mathieuschopfer profile image
Mathieu Schopfer

In French (I cannot tell for other languages), commit seems to have been translated by objet commit.

git fsck --unreachable returns
objet commit inatteignable 977ee79082f2e1179c3d2156f8f0e6c66682ea2d
instead of
unreachable commit 977ee79082f2e1179c3d2156f8f0e6c66682ea2d

Thus, cut -d ' ' -f3 returns inatteignable instead of the commit tag.

Thread Thread
meduzen profile image
Mehdi M. Author

Ha oui, carrément. :D

Gonna update the article. Thanks a lot!

Collapse
xrzhuang profile image
xrzhuang

this saved my life

Collapse
rjean99 profile image
rjean99

Mine too!

Collapse
rstrausslogyx profile image
Randy Strauss • Edited on

I tried both commands (git 2.21.1):
git update-ref refs/stash b68ecd901f90158d7c41edf2d2d3868e3599ca29 -m "My recovered stash"
git update-ref refs/stash b68ecd901f90158d7c41edf2d2d3868e3599ca29 --create-reflog -m "My recovered stash"

both give usage (below, removing the '-d' and '-stdin' stuff:
usage: git update-ref [] []
-m reason of the update
--no-deref update not the one it points to
-z stdin has NUL-terminated arguments
--create-reflog create a reflog

Is refs/stash the refname? Or is the sha the refname?
What's the "new-val" and "old-val" - values of what?

This page seems to help:
gist.github.com/joseluisq/7f0f1402...

git log --graph --oneline --decorate ( git fsck --no-reflog | awk '/dangling commit/ {print $3}' )
and then
git stash apply YOUR_WIP_COMMIT_HASH

Thanks for the hope, and being a stepping stone to most of what I needed. :?)

(4 of my files weren't stashed, though. It turns out NASA's backup utility has been silently failing for months, so I've lost a few days work. Under-staffed projects are a pain. Luckily, the project and my attitude don't really matter...)

Collapse
mansehej profile image
Mansehej Singh

This was extremely helpful. Thank you for sharing your knowledge!

Collapse
moopet profile image
Ben Sinclair

It never occurred to me you could do this!

Collapse
simesy profile image
Simon Hobbs

Thanks, saved me some work!

Collapse
pedroverceze profile image
Pedro Verceze

Thanks!!
It is good to know, after the fist command, pick your hash and use:

$ git stash apply e3cf5932f4816f5b0022190ce6b871f51cf882de

It worked for me

Collapse
shankarshastri profile image
ShankarShastri

Thanks, it saved my day !!

Collapse
daniaaalarshad profile image
daniaaalarshad

Thanks a lot lot lot lot lot lot. You saved my time and I recovered 70 - 80 percent of my work :)

Collapse
zeke profile image
Zeke Hernandez

saved my butt! thanks!

Collapse
abelgriffen profile image
Abel Griffen

Thanks for the solution. It worked for me.

Collapse
mahipalabhijith profile image
Abhijith Mahipal M

Thanks, i got my changes back while my rebase autostash failed

Collapse
ericus123 profile image
AMANI Eric

Thanks a lot. This saved me .