Git,如何判断合并“我们删除的文件”时发生了什么变化

时间:2019-01-24 10:00:07

标签: git git-merge merge-conflict-resolution

  1. 我在功能分支中删除了文件,因为我在其他地方重构了其代码。

  2. Develop分支已更改此文件中的代码以修复错误。

  3. 我将develop分支合并到我的feature分支中,以便在继续进行该功能时保持其最新状态。

有关文件我遇到了“被我们删除”的冲突。

如何获取对develop分支上的文件所做的更改的区别,以便可以将这些更改重新实现到我的功能分支上的重构代码中?

1 个答案:

答案 0 :(得分:2)

您要查看索引槽#1,并将其与索引槽#3的路径进行比较:

git diff :1:path :3:path

您还可以使用git checkout-index 提取各种索引槽版本,然后使用常规文件操作而不是仅使用Git工具进行检查。 git mergetool程序执行后者,因此,如果您使用git mergetool,则将具有文件的两个版本。 (我自己从未使用过git mergetool。)

这是什么意思?

根据定义,任何合并都有三个输入。我将按倒序列出它们,因为最后一个是此处的关键:

  • 三个输入之一是提交,位于develop分支的顶端:您运行了git merge develop。请记住,每次提交都是所有文件的完整快照。这不是一组更改,它只是一个快照。但是不知何故,Git知道他们更改了一些特定文件。关于什么?

  • ,已更改
  • 这三个输入之一是您自己当前的提交(也称为HEAD)。在这种情况下,HEAD中的某些文件已被删除。但是:Git如何知道您删除了文件?关于什么?

  • 已删除
  • 三个输入中的最后一个是合并基础。这就是这两者中的什么。合并基础是(单个/最佳)常见提交,您的分支提示从此开始。

也就是说,如果我们在启动git merge时绘制提交图,则它看起来像这样-尽管详细信息会有所不同,并且通常很难扫描图来找到{{1 }}和B(通常很容易找到R或提交HEAD):

L

提交 o--...--L <-- our-branch (HEAD) / ...--A--B \ o--...--R <-- develop A(以及所有早于A的内容)都在两个分支上,因此是共享的,但是B最佳之一,因为它是 last 共享的一个。

为了进行合并,Git将BB进行了比较,以查看我们发生了什么变化,并比较了LB查看他们发生了什么变化。在三个提交中没有更改的文件都是相同的,因此Git使用此类文件的任何版本。对于仅他们进行了更改的文件,Git从提交R获取该文件的版本。对于仅我们更改过的文件,Git从提交R中获取该文件的版本。

对于我们两个都更改过的文件,以某种方式(包括“完全删除文件”),Git必须更加努力。现在,重要的是要了解索引(Git生成 new 所提交的内容)在合并期间具有扩展的作用。

通常,每个文件的索引只有一个插槽。这个插槽是带编号的,但它是零编号的插槽,因此您通常不需要做任何特殊的引用即可:您只是告诉Git L复制文件 git add somefile 从工作树进入索引,使其准备提交。

但是,对于合并情况,在我们和他们都对文件进行处理的情况下,Git需要 3 (每个文件最多 三个)副本。因此,对于这种特殊情况,Git将文件{em> merge base 的版本从提交somefile放入索引插槽#1。 Git将文件的我们的版本(从提交B并已在索引插槽#0中移动到索引插槽#2中,并将文件的另一版本从提交L移入索引插槽#3中。

对于已删除的文件(在这种情况下),Git将插槽2或#3留空,具体取决于谁删除了文件。对于添加/添加冲突(文件在R中不存在,但在BL中都存在),Git将插槽#1留空。 (不存在删除/删除冲突的事情:如果我们都删除了文件,Git只会删除文件并继续前进。但是有一些重命名的情况比较棘手。)

当合并由于合并冲突而停止时,这些索引槽将始终填充文件的三个版本(在本例中为两个)。因此,您可以检查索引,查看高级(非零)插槽,并了解哪些文件有冲突。包括Rgit status在内的各种Git工具。

解决冲突后,无论如何,都必须告诉Git清除较高级的插槽,然后将文件的良好副本放入索引插槽#0。最简单的方法是git diff正确版本的文件。 (如果没有正确的版本(如果应该删除该版本,则可以git add,将其从所有索引插槽和工作树中删除。通常,如果它不在工作树中,则{{ 1}}还将其从索引中删除,尽管我习惯git rm-处理应该消失的冲突文件,所以我没有测试git add是否与删除较高的文件一致阶段条目。如果它不在工作树中,则git rm从索引中删除它,抱怨它不在工作树中,然后一切都很好。)

脚注:更纠结的图

这是一个带有一些重复合并的分支的示例图:

git add

此处,git rm...--A--B--C--D--G--H--K <-- branch1 \ \ \ E-----F--I--J--L <-- branch2 都是合并提交,有两个父级:F的两个父级分别为JF,并且两个E的父级是D H J git checkout branch2; git merge branch1 I and M . If you run L you'll be attempting to make a new merge commit K whose first parent is H and second is K . The merge base here is commit H because starting at L and working backwards, we get to顶部是J , while starting at I and working backwards, we get to H and then—simultaneously, as it were—to both H`,这是合并基数。

请注意,合并基础计算是对称的。如果我们and,Git仍会选择, and as we already got to作为合并基础。如果合并基础不明显,则可以运行:

git checkout branch1 && git merge branch2

这将产生“最佳合并基础”的所有候选。

理想情况下,只有一个。但是,这样的历史记录如下:

H

git merge-base --all branch1 branch2 ...--o--o---A--... <-- branch1 \ / X / \ ...--o--o---B--... <-- branch2 都是与两个分支提示“相等接近”的合并提交,具有两个合并基础。这是通过从branch1合并到branch2并立即通过A从branch2合并到branch1(有时称为纵横交叉合并)进行的。

在这种模棱两可的合并基础案例中,没有一个最佳候选者。由于合并通常是对称的,因此此处的提交Bgit merge --no-ff内容可能是相同的。在那种情况下,选择AB Git中的哪一个都不重要。但是,如果内容不同,那确实很重要,这就是Git的递归合并的所在。当有多个合并基础时,Git首先将作为内部合并的两个合并基础(使用他们的最佳共同祖先,无论是什么),然后从结果中进行新的提交。 Git将使用该新提交的内容作为外部合并的合并基础。

A合并策略(不是默认策略)以明显的随机性选择(实际上,只要算法中更方便),就选择B-s resolve这两者之一。如果AB具有相同的内容,则可以正常工作;如果不是,则来自递归“先合并A和B” 的内部合并可能会产生更好的结果。如果递归合并有冲突,它将产生混乱。 (通常最好避免纵横交错的合并。)