这个输出很奇怪,部分原因是我认为有点错误git ls-files
(not enough不过,我并不清楚我会采取什么不同的做法,也许可以为该错误添加一个额外的列C
信,拉git status --short
有两列状态字母?)。特别是,当您使用-t
选项你会得到一个状态标志,但是当你使用-m
标记你得到一个额外的线与C
某些文件的状态(工作树副本与索引副本不匹配的文件)。这意味着您可以看到一个文件名两次。
但在这里,您看到的是一个文件名五次。你会看到它three次,除了这个-m
标记插入额外的行(两次)。这让我们想到了您在标题中提出的问题:
合并冲突后暂存区中有哪些文件?
这就是术语的所在暂存区有点崩溃了。大多数情况下,它是一个比无意义的词更好的术语index或过度使用(因此毫无意义)的词cache: the 暂存区保存文件暂存以供提交。这就说得通了。但是当发生合并冲突时,索引/暂存区/缓存中的文件aren't根本就是“为提交而上演”,所以这个术语暂存区现在只是错误的。在这种情况下,我想回到第一个无意义的术语“索引”。
这里真正的关键是暂存槽号,它出现在 blob 哈希 ID 之后、文件名之前:
4111d50ada6cc03ec6079f226c23efa3142c9c94 1 file1.txt
这些“暂存槽编号”允许一个文件在索引/暂存区域中多次出现:每个条目都有不同的槽位号,这允许我们使用 Git 访问它:1:file.txt
, :2:file.txt
, and :3:file.txt
语法(在git rev-parse
/ git 修订版 https://git-scm.com/docs/gitrevisions).
当暂存区域未出于合并目的而扩展时,“正常”槽号始终为零。 (尝试git ls-files -s
当不在冲突的合并过程中时。)零槽文件已正确暂存并准备好提交。您可以访问此“副本”(实际上是 blob 实例):file.txt
使用 gitrevisions 语法。
Blob 4111
似乎是两个分支在分道扬镳之前的共同版本。斑点74a9
是版本中的master
分支和斑点0d02
是版本中的b2
branch.
这是正确的,这就是这里的想法。更准确的说法是,槽 1 中的文件是位于槽 1 中的文件。合并基础提交。槽 2 中的文件来自提示提交当前分支的master
,即从当前提交,槽 3 中的文件来自正在合并的提交,即尖端提交b2
.
这正是问题的核心git merge
在进行真正的合并时有效:
-
Git 找到要合并的两个提交。其中之一是当前或HEAD
commit,另一个是你在命令行命名的提交(git merge b2
).
-
Git 使用存储的元数据in这两个提交,以及在早期的提交中发现via这两个提交,以找到公共起点提交。
-
Having thus located exactly three commits,1 the merge can now begin:
- Git 将合并基础提交读取到“槽 1”处的索引中。
- Git 将当前提交读取到“槽 2”处的索引中。请注意,自从
git merge
要求一切都是“干净的”,这相当于moving每个 slot-0 条目到一个 slot-2 条目。
- Git 将另一个提交读取到“槽 3”处的索引中。
现在我们有了每个文件的所有三个实例in索引,在三个槽中。下一步是弄清楚最终合并文件的外观是否有快捷方式。
作为一种优化,这个“捷径”步骤实际上很早就发生了,没有大量的索引条目创建和洗牌,但我们可以假装它没有。请记住,合并的目标是合并更改,如果我们有某个文件的三个副本,它们可能是全部相同,或者其中两个可能匹配,我们可以采取以下捷径:
- 如果所有三个副本都匹配,则使用任何副本。没有人改变任何事情,所以我们完成了! (到此为止,不要继续剩下的测试。)
- 如果合并基础副本与我们的副本匹配,请使用他们的副本。我们没有触及该文件,而他们却触及了,因此合并结果是他们的文件。
- 如果合并基础副本与他们的副本匹配,请使用我们的副本。他们没有触及文件,而我们触及了,所以合并结果就是我们的文件。
- 如果我们的副本和他们的副本匹配,请使用以下副本之一:我们都制作了相同的变化到文件,所以任何一个都可以工作。
- 三份副本都不匹配:我们需要做真正的、实际的、努力的工作。
如果快捷方法找到正确的结果文件,则合并代码将移动that将文件的版本复制到插槽 0,擦除其他两个插槽的条目,并且如果需要,还更新文件的工作树副本。该文件现已完全合并,无需发生任何其他事情。
如果采用捷径方法fails要找到正确的结果文件,合并代码将所有三个文件保留在索引中,在这三个插槽中。然后它使用更多代码 - 您可以自己运行相同的代码,使用git merge-file https://git-scm.com/docs/git-merge-file,如果您愿意的话,尝试进行完整的三向合并,将您所做的更改与他们所做的更改结合起来:
合并代码重复此操作每个文件在三个暂存槽中。忽略所有的other特殊情况——例如检测重命名或处理新的或删除的文件,或脚注 1 中提到的项目——这涵盖了所需的一切。最后,要么所有文件都已合并并且所有内容现在都位于插槽零,要么合并发生冲突并且git merge
停下来并让你收拾残局。
在此上下文中,标签 C 和 M 意味着什么?
M
means unmerged,即槽号不为零。这就是它的全部意思,所以-s
,这个标志有点无用,因为你可以只看槽号。
C
means changed,即该文件与工作树副本不匹配。
1What do we do if there aren't exactly three commits?
这种情况以多种不同的方式发生。一种明显的方法是 Git 的所谓章鱼合并,你运行的地方:
git merge b1 b2 b3
to merge three other branch tips with the current (HEAD) commit to make a four-parent merge commit. This kind of merge is done by the git-merge-octopus
strategy, which doesn't use the index in the same way at all, and generally does not permit the kinds of conflicts that we'd try to resolve with git merge-file
. So that one, fortunately, sidesteps all of this. Explaining how git-merge-octopus
actually works is ... tricky, especially since I don't understand the octopus merge base computation myself.2
但即使使用两次提交作为输入合并,自动合并基础查找也可能存在问题。 Git 将合并基础定义为最好的共同祖先,使用最低公共祖先算法作为 DAG 的扩展。该算法描述为维基百科上的这里 https://en.wikipedia.org/wiki/Lowest_common_ancestor#Extension_to_directed_acyclic_graphs带有示例图。节点 x 和 y 的 LCA 不仅仅是one节点,而是two。在这种情况下,git merge-base --all
将找到这两个“最佳共同祖先”提交。 (一般来说,在足够复杂的图中,可能有很多合并基。由于交叉合并,两个合并基的情况肯定会时不时地出现。)
目前,Git 对于这个问题有两个答案:
- Using
git-merge-resolve
,我们选择one of the N合并基地,并假装这是唯一的合并基地。
- Using
git-merge-recursive
,我们选择all合并碱基,并将它们与git merge
。这会生成一个新的临时提交,然后我们将其用作原始问题的合并基础。
使用方法2时,将合并基进行合并可以再次找到多个合并基;如果是,Git 会合并这些合并基础,并使用生成的临时提交作为合并两个合并基础的合并基础。这反过来又可以递归,但由于每个 DAG 都会“蚕食”DAG,所以递归肯定会终止。
(新的git-merge-ort
代码——尚未在任何已发布的 Git 版本中标准使用;如果你有的话,你必须打电话索取-s ort
——据我了解,执行相同类型的递归,但我没有查看代码本身。)
2Running git merge-base --octopus
can do one computation for you, and running git merge-base
without --octopus
can do another computation for you. These produce different results. I never delved enough into the octopus strategy code to figure out whether it uses one of these two algorithms, or perhaps even some third algorithm.