应用一个stash是涉及三方合并还是仅将stash打补丁到当前工作树?

4
< p > git stash 的 Manpage 上写道

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.

“应用状态”是否涉及三方合并,还是只将stash补丁应用到当前工作树中?

这里的“冲突”是哪种类型?我只知道三方合并中的冲突。

谢谢。


我会说是三路合并。否则,存储、切换分支,然后应用更改将无法实现期望的结果(在新分支上应用更改)。 - devoured elysium
Git stash pop 合并的三种方式是什么? - user10082400
1个回答

6
"应用状态"确实是一种三方合并。
您在问题如何将存储条目作为HEAD提交和索引提交的子提交?中注意到(来自文档),其中提到:

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.

我将把git stash pop拆分为两个组成部分,即git stash applygit stash drop。如果应用步骤成功,pop也会执行drop步骤。如果失败——例如停止于冲突——pop将跳过drop。目前git stash代码实际上是一个Shell脚本,我想说“只是”一个Shell脚本,但它是一个相当庞大且复杂的脚本,超过700行,并且pop代码实际上是这样写的:
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
}

正如您所看到的,它说:“应用,如果成功,则删除; 如果失败,则打印一条信息行并以与应用程序退出相同的状态代码退出。”

应用索引(I)提交

因为每个存储都至少有两个提交——I索引状态和W工作树状态——所以应用步骤可以同时应用两者,或者不应用。当您运行git stash applygit stash pop时,选择是否同时应用两者,或仅应用W状态。如果选择同时应用两者,则使用以下内容应用I状态:

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

(虽然实际代码行略有不同,以允许在索引中包含二进制文件,并且前后都有一些非常棘手的魔法来处理各种角落和难点情况)。

因为这只是diff | patch,所以它不是真正的三方合并。有关其含义的详细信息,请参见git cherry-pick和git format-patch | git am之间的区别是什么?Mark Adelsberger的被接受的回答我自己的回答)。无论如何,补丁可能会失败。如果失败,您可以选择使用git stash branch而不是git stash apply --index,这是保证能够工作的。

应用工作树 (W) 提交

无论如何,假设您选择忽略提交的I或已成功应用该提交并且Git已将结果存储在应用程序过程的稍后阶段,Git现在转向您所询问的三方合并。
这部分非常棘手。 但其中核心是此行
if git merge-recursive $b_tree -- $c_tree $w_tree

该命令使用提交H作为合并基础,--ours提交是您开始合并时索引中的内容,--theirs提交是W提交的内容。

注意:git merge-recursive会尝试适应您在合并开始时工作树中的任何内容,即使它与作为ours树的索引内容不匹配。这并不总是成功的,这就是为什么将git stash pop到脏工作树中通常是一个坏主意的原因。

如果存在合并冲突,这三个树——$b_tree 来自 H 提交,$c_tree 来自 在运行 git merge-recursive 之前 git stash 从您的索引中创建的树,以及来自提交 W$w_tree——是进入合并冲突暂存槽的文件的三个来源,如我在 我的答案 中所描述的,用于 使用 mergetool 解决 Git Stash Pop 的冲突
请注意,如果您使用git mergetool来合并它们,则通常会丢弃开始合并时在工作目录中的任何未暂存更改。这也是通常不建议在工作目录处于脏状态时开始git stash popgit stash apply的原因之一。如果您在开始git stash pop时的工作目录与索引匹配,则如果出现合并冲突,您将会处于更好的状态。
编辑,2022年6月: git stash现在是 C 代码,因此很难看出它做了什么。 它现在内部使用 git merge-ort 而不是 git merge-recursive 。 但基本原理是相同的,包括在此处遇到的问题。 我通常建议尽可能避免使用git stash,因为很容易出错。 如果有什么东西可以被称为“用户友好”,那就是正常提交。)

谢谢。打补丁和三方合并有什么区别? - user10082400
无论如何,补丁可能会失败。如果它失败了,您可以选择使用git stash branch而不是git stash apply --index,后者不能保证成功。为什么git stash branch总是成功的,而git stash apply --index可能不成功? - user10082400
git stash branch 的作用是首先检出存储的父提交 H,并创建一个指向该提交的新分支名称。然后像往常一样应用索引和工作树更改,由于它们相对于 H 并且正在应用于 H,因此应用索引和工作树更改总是成功的。 - torek
快速问题:您特别提到在脏工作树中不建议使用git stash pop(在两个粗体段落中都是如此)。但是,git stash apply不也会发生同样的情况吗?如果它们的行为相同,那么可能会被误导认为不建议使用git stash pop,因此git stash apply可能是一个合法的替代方案。 - Michael
@Michael:实际上,我强烈反对使用git stash,所以你问错人了。然而,git stash apply表示一件事,而git stash pop表示*首先是git stash apply,然后再做另一个git stash的操作,即git stash drop*,如果应用步骤不可见出现问题,则drop步骤会使情况变得更加严重。当应用出现问题时已经够糟糕了。但总体而言,我建议人们完全避免使用git stash;如果必须使用,请尽量避免使用git stash pop - torek
话虽如此,我会更新一下,提到 apply 和 pop,因为你是对的! - torek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接