How to recover a dropped stash in Git?

前端 未结 19 1704
别跟我提以往
别跟我提以往 2020-11-22 01:42

I frequently use git stash and git stash pop to save and restore changes in my working tree. Yesterday I had some changes in my working tree that I

相关标签:
19条回答
  • 2020-11-22 02:11

    If you didn't close the terminal, just look at the output from git stash pop and you'll have the object ID of the dropped stash. It normally looks like this:

    $ git stash pop
    [...]
    Dropped refs/stash@{0} (2ca03e22256be97f9e40f08e6d6773c7d41dbfd1)
    

    (Note that git stash drop also produces the same line.)

    To get that stash back, just run git branch tmp 2cae03e, and you'll get it as a branch. To convert this to a stash, run:

    git stash apply tmp
    git stash
    

    Having it as a branch also allows you to manipulate it freely; for example, to cherry-pick it or merge it.

    0 讨论(0)
  • 2020-11-22 02:13

    To get the list of stashes that are still in your repository, but not reachable any more:

    git fsck --unreachable | grep commit | cut -d" " -f3 | xargs git log --merges --no-walk --grep=WIP
    

    If you gave a title to your stash, replace "WIP" in -grep=WIP at the end of the command with a part of your message, e.g. -grep=Tesselation.

    The command is grepping for "WIP" because the default commit message for a stash is in the form WIP on mybranch: [previous-commit-hash] Message of the previous commit.

    0 讨论(0)
  • 2020-11-22 02:14

    Once you know the hash of the stash commit you dropped, you can apply it as a stash:

    git stash apply $stash_hash
    

    Or, you can create a separate branch for it with

    git branch recovered $stash_hash
    

    After that, you can do whatever you want with all the normal tools. When you’re done, just blow the branch away.

    Finding the hash

    If you have only just popped it and the terminal is still open, you will still have the hash value printed by git stash pop on screen (thanks, Dolda).

    Otherwise, you can find it using this for Linux, Unix or Git Bash for Windows:

    git fsck --no-reflog | awk '/dangling commit/ {print $3}'
    

    ...or using Powershell for Windows:

    git fsck --no-reflog | select-string 'dangling commit' | foreach { $_.ToString().Split(" ")[2] }
    

    This will show you all the commits at the tips of your commit graph which are no longer referenced from any branch or tag – every lost commit, including every stash commit you’ve ever created, will be somewhere in that graph.

    The easiest way to find the stash commit you want is probably to pass that list to gitk:

    gitk --all $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' )
    

    ...or see the answer from emragins if using Powershell for Windows.

    This will launch a repository browser showing you every single commit in the repository ever, regardless of whether it is reachable or not.

    You can replace gitk there with something like git log --graph --oneline --decorate if you prefer a nice graph on the console over a separate GUI app.

    To spot stash commits, look for commit messages of this form:

            WIP on somebranch: commithash Some old commit message

    Note: The commit message will only be in this form (starting with "WIP on") if you did not supply a message when you did git stash.

    0 讨论(0)
  • 2020-11-22 02:14

    What I came here looking for is how to actually get the stash back, regardless of what I have checked out. In particular, I had stashed something, then checked out an older version, then poped it, but the stash was a no-op at that earlier time point, so the stash disappeared; I couldn't just do git stash to push it back on the stack. This worked for me:

    $ git checkout somethingOld
    $ git stash pop
    ...
    nothing added to commit but untracked files present (use "git add" to track)
    Dropped refs/stash@{0} (27f6bd8ba3c4a34f134e12fe69bf69c192f71179)
    $ git checkout 27f6bd8ba3c
    $ git reset HEAD^    # Make the working tree differ from the parent.
    $ git stash # Put the stash back in the stack.
    Saved working directory and index state WIP on (no branch): c2be516 Some message.
    HEAD is now at c2be516 Some message.
    $ git checkout somethingOld # Now we are back where we were.
    

    In retrospect, I should have been using git stash apply not git stash pop. I was doing a bisect and had a little patch that I wanted to apply at every bisect step. Now I'm doing this:

    $ git reset --hard; git bisect good; git stash apply
    $ # Run tests
    $ git reset --hard; git bisect bad; git stash apply
    etc.
    
    0 讨论(0)
  • 2020-11-22 02:15

    I just constructed a command that helped me find my lost stash commit:

    for ref in `find .git/objects | sed -e 's#.git/objects/##' | grep / | tr -d /`; do if [ `git cat-file -t $ref` = "commit" ]; then git show --summary $ref; fi; done | less
    

    This lists all the objects in the .git/objects tree, locates the ones that are of type commit, then shows a summary of each one. From this point it was just a matter of looking through the commits to find an appropriate "WIP on work: 6a9bb2" ("work" is my branch, 619bb2 is a recent commit).

    I note that if I use "git stash apply" instead of "git stash pop" I wouldn't have this problem, and if I use "git stash save message" then the commit might have been easier to find.

    Update: With Nathan's idea, this becomes shorter:

    for ref in `git fsck --unreachable | grep commit | cut -d' ' -f3`; do git show --summary $ref; done | less
    
    0 讨论(0)
  • 2020-11-22 02:15

    Why do people ask this question? Because they don't yet know about or understand the reflog.

    Most answers to this question give long commands with options almost nobody will remember. So people come into this question and copy paste whatever they think they need and forget it almost immediately after.

    I would advise everyone with this question to just check the reflog (git reflog), not much more than that. Once you see that list of all commits there are a hundred ways to find out what commit you're looking for and to cherry-pick it or create a branch from it. In the process you'll have learned about the reflog and useful options to various basic git commands.

    0 讨论(0)
提交回复
热议问题