Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Actually undo git stash pop

Tags:

git

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

UNDO git stash pop

in other words

// in branch foo
git stash
git checkout bar
git stash pop       # ERROR. I didn't want to pop on top of bar, lots of conflicts
git stash undo-pop  # NEED COMMAND TO PUT STASH AND LOCAL FILES BACK AS THEY WERE
git checkout foo
git stash pop

Is there a way to get everything back to the state just before I typed git stash pop? In other words to actually UNDO the pop and put the stashed stuff back into the stash and put the local files back in the state they were before I typed git stash pop.

This is also not a dupe of How to recover a dropped stash in Git? Although that might be helpful in certain situations

like image 840
gman Avatar asked Feb 05 '18 09:02

gman


People also ask

Can you undo a stash pop?

We can use the reset command with some options to git undo failed stash pop. The git reset has recently learned the --merge option. The --merge option is like the default --mixed option, but it only applies to the files affected by the merge operation.

How do I get rid of git stash pop?

You need to resolve the conflicts by hand and call git stash drop manually afterwards. This will revert your local repo to the last commit. You should be able to push all of your commits (since the last push) without issue. If it does not work, then post the results back to your previous question).

Can you undo a git stash?

To undo a git stash , use the git stash pop command. It will re-apply your stash to your working copy.

How do you resolve conflicts after stash pop?

The stash entry is kept in case you need it again. There's no magic remedy for such merge conflicts. The only option for developers is to edit the file by hand and keep what they want and dispose of what they don't want. Once they merge and save the file, they will have effectively resolved the git stash conflict.


2 Answers

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.

like image 95
Greg Hewgill Avatar answered Sep 22 '22 19:09

Greg Hewgill


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.

like image 34
torek Avatar answered Sep 21 '22 19:09

torek