4

Manpage of git stash says

pop [--index] [-q|--quiet] [<stash>]

Remove a single stashed state from the stash list and apply it on top of the current working tree state, i.e., do the inverse operation of git stash push . The working directory must match the index.

Applying the state can fail with conflicts; in this case, it is not removed from the stash list. You need to resolve the conflicts by hand and call git stash drop manually afterwards.

Does "Applying the state" involve three way merge, or just patching a stash to the current working tree?

What kind of "conflicts" is it? I only know the conflict from three way merge.

Thanks.

  • 3way merge, I'd say. Otherwise stashing, switching branch and then applying it would not lead to the desired outcome (apply the changes on the new branch). – devoured elysium Jan 23 '19 at 03:37
  • what are the three ways in the merging by git stash pop? –  Jan 23 '19 at 03:43

1 Answers1

6

"Applying the state" is indeed a three-way merge.

You noted (from the documentation) in your question How is a stash entry a child commit of HEAD commit and index's commit? that:

The ancestry graph looks like this:

       .----W
      /    /
-----H----I

where H is the HEAD commit, I is a commit that records the state of the index, and W is a commit that records the state of the working tree.

I'm going to break git stash pop into its two component parts, which are git stash apply followed by git stash drop. If the apply step succeeds, pop executes the drop step as well. If not—if it stops with a conflict, for instance—pop skips the drop. The git stash code is currently literally a shell script—I'd say "just" a shell script, but it is a pretty big and complex one weighing in at well over 700 lines—and the pop code literally reads this way:

pop_stash() {
    assert_stash_ref "$@"

    if apply_stash "$@"
    then
        drop_stash "$@"
    else
        status=$?
        say "$(gettext "The stash entry is kept in case you need it again.")"
        exit $status
    fi
}

which as you can see says apply, and if that works, drop; if it doesn't work, print an informative line and quit with the same status code that apply would have quit with.

Applying the index (I) commit

Because each stash has at least two commits—the I index-state and the W work-tree-state—the apply step can apply both of them, or not. At the time you run git stash apply or git stash pop, you choose whether to apply both, or just the W state. If you choose to apply both, the I state is applied with what amounts to:

git diff <hash-of-H> <hash-of-I> | git apply --cached

(though the actual line of code is slightly different, to allow for binary files in the index, and is preceded and followed by some rather tricky magic to handle various corner and hard cases).

Because this is just diff | patch it is not a true three-way merge. For details on what this implies, see What is the difference between git cherry-pick and git format-patch | git am? (both Mark Adelsberger's accepted answer and my own). In any case, the patch may fail. If it does fail, you have the option of using git stash branch instead of git stash apply --index, which is guaranteed to work.

Applying the work-tree (W) commit

In any case, assuming you've either chosen to ignore the I commit, or have successfully applied it and Git has squirreled the result away for later in the apply process, Git now moves on to the three-way merge you were asking about.

This part is pretty tricky. The heart of it, though, is this line:

if git merge-recursive $b_tree -- $c_tree $w_tree

which runs a three-way merge with the merge base being commit H, the --ours commit being whatever was in the index when you started the merge, and --theirs commit being the contents of the W commit.

Note: git merge-recursive tries to accommodate whatever is in the work-tree when you start the merge, even if it does not match the index contents that act as the ours tree. This is not always successful, which is why it's often a bad idea to git stash pop into a dirty work-tree.

If there are merge conflicts, these three trees—$b_tree from the H commit, $c_tree from a tree that git stash made from your index before running git merge-recursive, and $w_tree from commit W—are the three sources for files that go into the index in the merge-conflict staging slots, as I described in my answer to Git Stash Pop - Resolve Conflicts with mergetool.

Note that if you do use git mergetool to merge them, this generally throws away any unstaged changes you had in your work-tree when you started the merge. This, too, is a reason it is generally unwise to start a git stash pop or git stash apply with a dirty work-tree. If your work-tree matches your index when you start git stash pop, you will be in much better shape if a merge conflict occurs.

(Edit, Jun 2022: git stash is now C code, so that it's much harder to see what it does. It now uses git merge-ort instead of git merge-recursive internally. But the essentials are the same, including the problems that come up here. I generally recommend avoiding git stash as much as you can, as it's really easy to have things go wrong. Regular commits are much more user-friendly, if anything in Git can be called "user friendly" without a lot of loud sarcastic laughter.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks. What is the difference between patching and three way merge? –  Jan 23 '19 at 15:34
  • "In any case, the patch may fail. If it does fail, you have the option of using git stash branch instead of git stash apply --index, which is guaranteed to work". Why does `git stash branch` always succeed, while `git stash apply --index` might not? –  Jan 23 '19 at 22:04
  • What `git stash branch` does is to first check out the parent of the stash—commit `H` in the diagram—and create a new branch name pointing to that commit. Then it applies the index and work-tree changes as usual, and since they were relative to `H` and are being applied *to* `H`, applying the index and work-tree changes always succeeds. – torek Jan 23 '19 at 22:37
  • Quick question: you specifically mention that `git stash pop` is not recommended in a dirty work-tree (in both of the bold paragraphs). But wouldn't the same happen with `git stash apply`? If they behave the same, it could be interpreted in a misleading way in that `git stash pop` might not be advised and thus maybe `git stash apply` would be a legit alternative. – Michael Jun 02 '22 at 11:15
  • @Michael: I dis-recommend (recommend against) `git stash` entirely, actually, so you're asking the wrong person. However, `git stash apply` means one thing, and `git stash pop` means *first, `git stash apply`, then do another git stash thing, namely `git stash drop`* and if the apply step has gone wrong invisibly, the `drop` step makes it significantly worse. It's bad enough when apply goes wrong. But overall I advise people to avoid `git stash` entirely; if you must use it, try to avoid `git stash pop` in particular. – torek Jun 02 '22 at 12:45
  • All that said, I'll update this to mention both apply and pop, because you're right! – torek Jun 02 '22 at 12:46