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
It returns a list of lost stashes, ordered by date.
- To quit the list of stashes, press the Q key.
- To navigate in a long stashes list, use
up
anddown
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"
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"
- 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:
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 (25)
Saved my day ❤️
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!)
This command for windows saved my life and job. Thankyou so much!!
Thanks! A link to your comment has been added to the article.
This almost worked for me. I found that the
update-ref
command created the stash ref correctly butgit 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.04Thanks! I don’t have this issue in Git 2.20.1 (macOS 10.14.16) but added a note in the article.
Thanks a lot. This saved me .
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.
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. ✌️
This may fail if you don't use git in English. To quickly fix it:
alias git='LANG=en_GB git'
:o
What is different when you use Git in another language? I didn't know it existed.
In French (I cannot tell for other languages),
commit
seems to have been translated byobjet commit
.git fsck --unreachable
returnsobjet commit inatteignable 977ee79082f2e1179c3d2156f8f0e6c66682ea2d
instead of
unreachable commit 977ee79082f2e1179c3d2156f8f0e6c66682ea2d
Thus,
cut -d ' ' -f3
returnsinatteignable
instead of the commit tag.Ha oui, carrément. :D
Gonna update the article. Thanks a lot!
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...)
this saved my life
Mine too!
Thanks!!
It is good to know, after the fist command, pick your hash and use:
$ git stash apply e3cf5932f4816f5b0022190ce6b871f51cf882de
It worked for me
Thanks, it saved my day !!