首页 > 解决方案 > 合并冲突后暂存区中的文件是什么?

问题描述

当我git merge b2使用当前分支运行时,我与 file1.txt 发生冲突master

ls-files -somcdt file1.txt然后显示:

M 100644 4111d50ada6cc03ec6079f226c23efa3142c9c94 1     file1.txt
C 100644 4111d50ada6cc03ec6079f226c23efa3142c9c94 1     file1.txt
M 100644 74a940a72da050886c6d46ca46270b990a5b12bd 2     file1.txt
M 100644 0d02047f8540dc3f81ed8645a9d912479e731d83 3     file1.txt
C 100644 0d02047f8540dc3f81ed8645a9d912479e731d83 3     file1.txt

Blob4111似乎是两个分支在分道扬镳之前通用的版本。

Blob74a9是分支中的版本master,blob0d02是分支中的版本b2

在这种情况下,标签 C 和 M 是什么意思?

标签: git

解决方案


这个输出很奇特,部分原因是我认为是一个错误git ls-files(不过,这个错误还不足以关心,而且我不清楚我会做些什么不同 - 可能包括一个额外的列对于这封信,一个有两列状态信函C的 la ?)。git status --short特别是,当您使用该-t选项时,您会获得一个状态标志,但是当您使用该-m标志时,您会获得额外的一行,其中包含某些文件的C状态(那些工作树副本与索引副本不匹配的文件)。这意味着您可以看到两次文件名。

但是,在这里,您会看到一个文件名五次。你会看到它三次,除了这个-m标志插入额外的行(两次)。这让我们想到了你在标题中提出的问题:

合并冲突后暂存区中的文件是什么?

这是术语暂存区有点分崩离析的地方。大多数情况下,它比无意义的词索引或过度使用(因此,毫无意义)的词缓存更好:暂存区保存为提交而暂存的文件。这就说得通了。但是当发生合并冲突时,索引/暂存区/缓存中的文件根本不会“暂存以供提交”,因此暂存区一词现在是错误的。在这种情况下,我喜欢回到第一个无意义的术语“索引”。

这里真正的关键是staging slot number,它出现在 blob 哈希 ID 之后和文件名之前:

4111d50ada6cc03ec6079f226c23efa3142c9c94 1 file1.txt

这些“staging slot numbers”允许一个文件在 index / staging-area 中多次出现:每个条目都有一个不同的slot number,这允许我们使用 Git 的:1:file.txt:2:file.txt:3:file.txt语法(在git rev-parse/ gitrevisions中)访问它。

当暂存区域没有为合并目的而扩展时,“正常”插槽编号始终为零。git ls-files -s(在没有发生冲突的合并时尝试。)槽零文件已正确暂存并准备好提交。:file.txt您可以使用gitrevisions 语法访问此“副本”(实际上是 blob 实例) 。

Blob4111似乎是两个分支在分道扬镳之前通用的版本。Blob74a9是分支中的版本master,blob0d02是分支中的版本b2

这是正确的,这就是这里的想法。更准确地说,插槽 1 中的文件是合并基础提交中的文件。slot 2 中的文件是来自当前分支的tip commitmaster的文件,即来自当前commit的文件,slot 3 中的文件是来自被合并的commit 的文件,即 的tip commit b2

git merge在进行真正的合并时,这正是工作原理的核心:

  • Git 找到要合并的两个提交。其中一个是当前或HEAD提交,另一个是您在命令行中命名的提交 ( git merge b2)。

  • Git 使用存储在这两个提交中的元数据以及通过这两个提交找到的早期提交中的元数据来定位公共起点提交。

  • 因此准确定位了三个提交后,1现在可以开始合并:

    • Git 将合并基础提交读入“slot 1”处的索引。
    • Git 将当前提交读入“slot 2”处的索引中。请注意,由于git merge要求所有内容都“干净”,因此这相当于每个 slot-0 条目移动到 slot-2 条目。
    • Git 将另一个提交读入“slot 3”处的索引。

    所以现在我们索引中的三个插槽中拥有每个文件的所有三个实例。下一步是确定最终合并文件的外观是否有快捷方式。

这个“捷径”步骤实际上是在早期发生的,没有大量的索引条目创建和洗牌,作为一种优化,但我们可以假装它没有。请记住,合并的目标是合并更改,如果我们有某个文件的三个副本,它们可能完全相同,或者其中两个可能匹配,我们可以采用以下捷径:

  • 如果所有三个副本都匹配,则使用任何副本。没有人改变任何东西,所以我们完成了!(停在这里,不要继续剩下的测试。)
  • 如果合并基础副本与我们的副本匹配,请使用他们的副本。我们没有碰文件,他们也碰了,所以合并结果就是他们的文件。
  • 如果合并基础副本与他们的副本匹配,请使用我们的副本。他们没有碰文件,我们碰了,所以合并结果就是我们的文件。
  • 如果我们的副本和他们的副本匹配,请使用这些副本中的任何一个:我们都对文件进行了相同的更改,因此任何一个都有效。
  • 这三个副本都不匹配:我们需要做真实的、实际的、努力的工作。

如果快捷方法找到正确的结果文件,则合并代码将该版本的文件移动到插槽零,擦除其他两个插槽的条目,如果需要,还更新文件的工作树副本。该文件现在已完全合并,没有其他事情发生。

如果快捷方法未能找到正确的结果文件,则合并代码将所有三个文件留在 index中,在这三个插槽中。然后,它使用更多代码——你可以自己运行的相同代码git merge-file,如果你愿意,可以使用 ——尝试进行完整的三向合并,将你所做的更改与他们所做的更改结合起来:

  • 这种完整的三向合并可以成功,在这种情况下,合并的文件在工作树中,并且合并代码执行内部git add操作以将此副本写入索引的插槽 0,这也会擦除插槽 1-3。

  • 或者,这个完整的三向合并可能会失败,在这种情况下,合并尝试在工作树中完成并带有冲突标记,并且合并代码什么都不做(除了记住将合并称为“冲突”)。

合并代码对三个暂存槽中的每个文件重复此操作。忽略所有其他特殊情况——例如检测重命名或处理新文件或删除文件,或脚注 1 中提到的项目——这涵盖了所有需要的内容。最后,要么所有文件都已合并,现在一切都在零槽,要么合并有冲突并git merge停止并让您修复混乱。

在这种情况下,标签 C 和 M 是什么意思?

M表示unmerged,即槽号不为零。这就是它的全部含义,所以-s,这个标志有点没用,因为你可以只看插槽号。

C表示已更改,即此文件与工作树副本不匹配。


1如果完全是三个提交,我们该怎么办?

这种情况以多种不同的方式发生。一种明显的方法是 Git 所谓的octopus merge,您可以在其中运行:

git merge b1 b2 b3

将其他三个分支提示与当前(HEAD)提交合并以进行四父合并提交。这种合并是由git-merge-octopus strategy完成的,它根本不以相同的方式使用索引,并且通常不允许我们试图解决的那种冲突git merge-file。因此,幸运的是,这一切都回避了。解释git-merge-octopus实际是如何工作的……很棘手,特别是因为我自己不了解章鱼合并基础计算。2

但即使使用两次提交作为输入的合并,自动合并基础查找也可能存在问题。Git 将合并基础定义为最佳共同祖先,使用为 DAG 扩展的最低共同祖先算法。该算法在 Wikipedia上通过示例图进行了描述。节点 x 和 y 的 LCA 不仅仅是一个节点,而是两个。在这种情况下,git merge-base --all将找到这两个“最佳共同祖先”提交。(一般来说,在一个足够复杂的图中,可能有很多合并基。由于交叉合并,肯定会不时出现两个合并基的情况。)

目前,Git 对这个问题有两个答案:

  1. 使用git-merge-resolve,我们选择N个合并基中的一个,并假设它是唯一的合并基。
  2. 使用git-merge-recursive,我们选择所有合并基,并将它们与 合并git merge。这会生成一个新的但临时的提交,然后我们将其用作原始问题的合并基础。

使用方法二时,合并合并基可以再次找到多个合并基;如果是这样,Git 会合并这些合并库,并将生成的临时提交用作合并两个合并库的合并库。这反过来可以递归,但由于每个人都在 DAG 上“蚕食”,因此可以保证递归终止。

(新git-merge-ort代码——尚未在任何已发布的 Git 版本中标准使用;如果你有它,你必须调用它-s ort——按照我的理解,它执行相同类型的递归,但我没有查看代码本身。)

2 Runninggit merge-base --octopus可以为你做一个计算,run git merge-base without --octopus可以为你做另一个计算。这些产生不同的结果。我从来没有深入研究过章鱼策略代码,以确定它是否使用这两种算法中的一种,或者甚至可能是第三种算法。


推荐阅读