这个输出很奇特,部分原因是我认为git ls-files 中的一些错误(不过,这还不是足够需要关心的错误,而且还不清楚对我来说,我会做些什么不同的事情——也许为C 信增加一列,或者git status --short 有两列用于状态信?)。特别是,当您使用-t 选项时,您会得到一个状态标志,但是当您使用-m 标志时,您会得到一个带有C 状态的额外行,用于某些文件(那些用于工作树副本与索引副本不匹配)。这意味着您可以看到一个文件名两次。
不过,在这里,您会看到一个文件名五次。你会看到它三次次,除了这个-m 标志插入额外的行(两次)。这让我们想到了你在标题中提出的问题:
合并冲突后暂存区中的文件是什么?
这就是暂存区这个术语有点分崩离析的地方。大多数情况下,它比无意义的词 index 或过度使用(因此,毫无意义)的词 cache: 暂存区 更好用保存文件为提交暂存。这就说得通了。但是当发生合并冲突时,索引/暂存区/缓存中的文件根本不会“为提交暂存”,因此使用术语暂存区现在是错误的。在这种情况下,我喜欢回到第一个无意义的术语“索引”。
这里真正的关键是 staging slot number,它出现在 blob 哈希 ID 之后和文件名之前:
<sub><sup>4111d50ada6cc03ec6079f226c23efa3142c9c94</sup></sub><strong>1</strong> <sub><sup>file1.txt</sup></sub>
这些“暂存槽编号”允许一个文件在索引/暂存区域中出现多次:每个条目都有一个不同的槽编号,这允许我们使用 Git 的 @987654332 访问它@、:2:file.txt 和 :3:file.txt 语法(在 git rev-parse / gitrevisions 中)。
“正常”插槽编号,当暂存区域未扩展以进行合并时,始终为零。 (尝试git ls-files -s,当不在冲突合并的中间时。)槽零文件已正确暂存并准备好提交。您可以使用 gitrevisions 语法以 :file.txt 的身份访问此“副本”(实际上是 blob 实例)。
Blob 4111 似乎是两个分支在分道扬镳之前的通用版本。 Blob 74a9 是master 分支中的版本,blob 0d02 是b2 分支中的版本。
没错,这就是这里的想法。更准确地说,槽 1 中的文件是 merge base commit 中的文件。 slot 2 中的文件是来自当前分支master 的 tip commit 的文件,即来自 current commit 的文件,而 slot 3 中的文件是来自提交被合并,即b2的提示提交。
这正是git merge 工作原理的核心,在进行真正的合并时:
-
Git 定位要合并的两个提交。其中一个是当前或HEAD 提交,另一个是您在命令行中命名的提交 (git merge b2)。
-
Git 使用存储在 in 这两个提交中的元数据,以及在较早的提交中 via 这两个提交找到的元数据来定位公共起点提交。
-
因此准确定位了三个提交,1现在可以开始合并:
- Git 将合并基础提交读入“slot 1”处的索引中。
- Git 将当前提交读入“槽 2”处的索引。请注意,由于
git merge 要求一切都“干净”,这相当于将每个 slot-0 条目移动到 slot-2 条目。
- Git 将另一个提交读入索引中的“slot 3”。
所以现在我们将每个文件的所有三个实例 in 放在索引的三个插槽中。下一步是确定最终合并文件的外观是否有快捷方式。
这个“捷径”步骤实际上是在早期发生的,没有大量的索引条目创建和洗牌,作为一种优化,但我们可以假装它没有。请记住,合并的目标是合并更改,如果我们有某个文件的三个副本,它们可能完全相同,或者其中两个可能匹配,我们可以走以下捷径:
- 如果所有三个副本都匹配,则使用任何副本。没有人改变任何东西,所以我们完成了! (停在这里,不要继续剩下的测试。)
- 如果合并基础副本与我们的副本匹配,请使用他们的副本。我们没有碰文件,他们碰了,所以合并结果就是他们的文件。
- 如果合并基础副本与其副本匹配,请使用我们的副本。他们没有碰文件,我们碰了,所以合并的结果就是我们的文件。
- 如果我们的副本和他们的副本匹配,请使用以下任一副本:我们都对文件进行了相同的更改,因此任何一个都可以。
- 三个副本都不匹配:我们需要做真正的、实际的、努力的工作。
如果快捷方法找到正确的结果文件,则合并代码将文件的 那个 版本移动到插槽零,擦除其他两个插槽的条目,如果需要,更新文件的工作树副本。该文件现在已完全合并,无需发生任何其他事情。
如果快捷方法未能找到正确的结果文件,则合并代码将所有三个文件留在索引中,在这三个槽中。然后,它会使用更多代码(您可以自己运行相同的代码,如果您愿意,可以使用 git merge-file)来尝试进行完整的三向合并,将您所做的更改与他们所做的更改结合起来:
合并代码对三个暂存槽中的每个文件重复此操作。忽略所有其他特殊情况——例如检测重命名或处理新的或删除的文件,或脚注 1 中提到的项目——这涵盖了所有需要的内容。最后,要么所有文件都已合并,现在所有内容都在零槽,要么合并发生冲突,git merge 停止并让您修复混乱。
标签 C 和 M 在这种情况下是什么意思?
M 表示未合并,即槽号不为零。这就是它的全部含义,所以对于-s,这个标志有点用处,因为你可以只看槽号。
C 表示已更改,即此文件与工作树副本不匹配。
1如果不恰好是三个提交,我们该怎么办?
这种情况以多种不同的方式发生。一种明显的方法是 Git 所谓的 octopus merge,您可以在其中运行:
git merge b1 b2 b3
将 三个 其他分支提示与当前 (HEAD) 提交合并以进行四父合并提交。这种合并是由git-merge-octopus策略完成的,它根本不以相同的方式使用索引,并且一般不会允许种冲突我们会尝试用git merge-file 解决。因此,幸运的是,这一切都回避了。解释 git-merge-octopus 的实际工作原理是……很棘手,特别是因为我自己不了解章鱼合并基础计算。2
但即使使用两次提交作为输入的合并,自动合并基础查找也可能会出现问题。 Git 将合并基础定义为最佳共同祖先,使用为 DAG 扩展的最低共同祖先算法。该算法在here on Wikipedia 中使用示例图进行了描述。节点x和y的LCA不仅仅是一个节点,而是两个。在这种情况下,git merge-base --all 将找到这两个“最佳共同祖先”提交。 (一般来说,在一个足够复杂的图中,可能有很多合并基。由于交叉合并,肯定会不时出现两个合并基的情况。)
目前,Git 对这个问题有两个答案:
- 使用
git-merge-resolve,我们从 N 个合并基中选择 一个,并假设它是唯一的合并基。
- 使用
git-merge-recursive,我们选择所有个合并基,并将它们与git merge合并。这会生成一个新的临时提交,然后我们将其用作原始问题的合并基础。
使用方法2时,合并合并基可以再次找到多个合并基;如果是这样,Git 会合并这些合并库,并将生成的临时提交用作合并两个合并库的合并库。这反过来又可以递归,但由于每个人都“蚕食”了 DAG,因此递归肯定会终止。
(新的git-merge-ort 代码——尚未在任何已发布的 Git 版本中标准使用;如果你有它,你必须用-s ort 调用它——据我了解,它执行相同类型的递归,但我没有看代码本身。)
2运行git merge-base --octopus 可以为你做一个计算,运行git merge-base 没有 --octopus 可以为你做另一个计算。这些产生不同的结果。我从未对章鱼策略代码进行过深入研究,无法弄清楚它是使用这两种算法中的一种,还是使用第三种算法。