DEV Community

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

Posted on • Edited on • Originally published at blog.mehdi.cc

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.

Top comments (26)

Collapse
 
htps611 profile image
htps

Saved my day ❤️

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
 
neelmagicedtech profile image
neel-magicedtech

This command for windows saved my life and job. Thankyou so much!!

Collapse
 
meduzen profile image
Mehdi M.

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

Collapse
 
samar_hussain profile image
Samar Hussain

Mehdi bro you have no idea how big of a trouble you saved me from. I accidentally stashed and cleared hard work of my several days and night (My mistake I shouldn't have piled up that big). And your post just saved my life. I was almost crying. Thanks a lot dude. God bless you.

Collapse
 
meduzen profile image
Mehdi M.

Hey Samar, you know what? I feel there's a lot of gratitude in your message, so I want to say thank you for having taken the time to put it down. It sincerely made my day better (and today is not the easiest one for me 😅).

I hope everything's fine with your work, now. Sharing is caring. ✌️

Collapse
 
ericus123 profile image
AMANI Eric

Thanks a lot. This saved me .

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.

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.

: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.

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

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
 
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
 
shankarshastri profile image
ShankarShastri

Thanks, it saved my day !!