Actually undo git stash pop

前端 未结 4 2102
迷失自我
迷失自我 2021-02-13 15:16

This question had the same title but it is NOT the same question. That question is really asking \"Discard results of git stash pop\". This question is actually

相关标签:
4条回答
  • 2021-02-13 15:19

    You can reverse patch the stash (which as already mentioned should still be there as git wont drop a stash if it doesn't apply cleanly)

    git stash show -p stash@{0} | git apply -R
    
    0 讨论(0)
  • 2021-02-13 15:21

    Greg Hewgill's answer is right (and upvoted, and the OP should accept it) but there's are several additional caveats here, in case anyone wants to use the answer in a more general fashion. Let's look first at the specific sequence of commands used:

    git stash
    git checkout bar
    git stash pop       # ERROR ... lots of conflicts
    

    Now, let's list the caveats:

    • It's important that git stash pop has failed. (Greg already noted this.)
    • You did not use git stash --keep-index when creating the stash.
    • After running git stash, you made no changes to your work-tree.
    • The git checkout command succeeded, so it may have made changes to your work-tree—in fact, it must have done so for the pop to fail—but your work-tree is still "clean", as git status would say.

    It's this last point, that git status would (before the attempt to git stash pop) tell you that your work-tree is clean, that is the key. Had you made changes to your work-tree, either before or after git checkout bar, you would be in more trouble.

    Because you did not do those things, git reset --hard is the answer.

    Why this is the case

    What git stash does, in general, is make two commits. One saves the current index and the other saves the current work-tree.1 These commits are slightly special in a few ways; the most important is that they are on no branch at all.2 Having made its commits, git stash then normally runs git reset --hard.3

    What the git reset --hard step does is to make the index and work-tree match the current commit. That is, we've saved the (whole) index and (the entire tracked part of the) work-tree in the stash; so whatever is different between the current HEAD commit and the index, can be re-set to be the same again; and whatever is different between the HEAD commit and the work-tree, can be re-set to be the same again.

    After the git reset, both the index and work-tree are "clean", as git status will say: they both match the HEAD commit. You can then git checkout other branches, as you have no unsaved work. You can then attempt to git stash apply, or even git stash pop, to "move" your changes to this other branch.

    When that fails—as in this case—the stash remains in the saved stashes. The current index and work-tree are now full of merge conflicts. If you run git reset --hard, Git will re-set the index and work-tree to match the HEAD commit as usual, so that you'll be back to the same situation you were in after the git checkout step. Since you had no unsaved work (your saved work is still in the stash commits), you're OK here.

    (If you did have unsaved work, the git stash apply step will have mangled that unsaved work by attempting to merge the stashed work-tree changes. This is very difficult to undo, in general.)


    1While git stash typically makes two commits, if you run it with --all or --include-untracked, it will make three commits. I like to call these the i (index), w (work-tree), and u (untracke files) commits.

    When using --all or --include-untracked, the save or push step will do more than just git reset --hard: it will also run git clean to remove whatever went into the third commit (untracked-only files, or untracked-including-ignored files). You may have to repeat this git clean work before applying such a stash, which is tricky and annoying.

    Later, when you run git stash apply, Git will (try to) apply the u commit if it exists. It will always (try to) apply the w commit. It will (try to) apply the i commit only if you give it the --index flag. There are some minor bugs in many versions of git stash surrounding this whole "separate index restoration" stuff. They tend to affect people who want to use the --keep-index and --index flags in, e.g., pre-commit hooks.

    Note that git stash pop is just git stash apply && git stash drop: that is, attempt to apply the stash, and then, if Git thinks the apply went well, also drop the stash. I find it better to use git stash apply first, to avoid dropping the stash even if Git thinks it went well, because Git and I sometimes disagree as to what "went well" means. :-)

    2Git uses the name refs/stash to remember the current stash, and (ab)uses the reflog for refs/stash to maintain the remainder of the "stash stack". Branch names internally all have the form refs/heads/name, so refs/stash is not a branch name.

    3If you use git stash --keep-index, it runs more than just git reset --hard: it extracts the saved index state to the work-tree. The goal here is to leave the work-tree set up the way the index was set up, so that you can test what you were about to commit. As noted in footnote 1, there's a small but fairly nasty bug here with many versions of git stash, where the stashed work-tree state accidentally takes the index version instead of the work-tree version if the correct work-tree version matches the HEAD version.

    0 讨论(0)
  • 2021-02-13 15:35

    So, I'm not 100% sure of the answer in all cases. In my case when I typed git stash pop and I was on branch bar there were conflicts. So while it applied the stash it didn't drop the stash. So to undo and correctly apply the stash to branch foo it was just

    git reset --hard      # resets bar to before the stash pop
    git checkout -b foo
    git stash pop         
    

    If there had been no conflicts with branch bar then it's just

    git stash             # restash
    git checkout -b foo
    git stash pop
    
    0 讨论(0)
  • 2021-02-13 15:40

    In your example, to restore to the state before git stash pop, use:

    git reset --hard bar
    

    This form of the git reset command restores the state of the index and the working directory to the head of the bar branch.

    Because you had conflicts on the first git stash pop, the stash remains on the top of the stash stack.

    From there, you can git checkout foo and git stash pop again.

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