"应用状态"确实是一种三方合并。
您在问题
如何将存储条目作为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 apply
和
git 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 apply
或git 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 pop
或
git stash apply
的原因之一。如果您在开始
git stash pop
时的工作目录与索引匹配,则如果出现合并冲突,您将会处于更好的状态。
(
编辑,2022年6月: git stash
现在是 C 代码,因此很难看出它做了什么。 它现在内部使用
git merge-ort
而不是
git merge-recursive
。 但基本原理是相同的,包括在此处遇到的问题。 我通常建议尽可能避免使用
git stash
,因为很容易出错。 如果有什么东西可以被称为“用户友好”,那就是正常提交。)