通过“递归”策略进行合并

tnk*_*nkh 6 git merge

我知道git merge递归实际上是在有多个共同祖先的情况下发生的,它会创建一个虚拟提交来合并这些共同祖先,然后再继续合并较新的提交(对不起,我不确定是否应该有一个术语这个)。

但是我一直在尝试寻找更多有关git merge递归策略如何实际工作的详细信息,但找不到太多信息。

谁能详细说明git merge递归的实际效果,并提供示例和可能的流程图来帮助更好地可视化?

Von*_*onC 5

您可以在此处找到描述(另请参阅第2部分):

什么时候需要合并递归?

如果我们找到“两个共同的祖先”怎么办?下面的分支资源管理器视图显示了一个替代方案,其中有两个可能的“共同祖先”。

初始情况

请注意:该示例有点强制,因为最初没有充分的理由让开发人员从变更集11合并到16,而不是从变更集15合并(合并点的最新分支)。
但是,让我们假设必须这样做是有原因的,例如,变更集11稳定,而当时的13和15则不稳定。

关键是:在15到16之间,没有一个唯一的祖先,而是两个在相同“距离”上的祖先:12和11。

虽然这种情况不会经常发生,但确实很可能在寿命长的分支或复杂的分支拓扑中发生。(上面描述的情况是最短的驱动“多重祖先”问题的情况,但是在“交叉”合并之间的多个变更集和分支也可能发生)。

一种解决方案是“选择”一个祖先作为合并的有效祖先(这是Mercurial采取的选择),但是它有很多缺点。

合并递归如何工作?

当找到多个有效祖先时,递归合并策略将创建一个新的唯一“虚拟祖先”,以合并最初发现的祖先。

下图描述了该算法:

算法

新的祖先2将用作“祖先”以合并“ src”和“ dst”。

合并

我将在下面介绍,“合并递归策略”能够找到比“选择两者之一”更好的解决方案。


注意:合并递归策略最初是Fredrik Ku​​ivinen之后的合并“ fredrik”策略(请参阅commit e4cf17c,2005年9月,Git v0.99.7a)。 这是一个python脚本,在提交720d150中启动,它说明了原始算法。

有关更多详细信息,请参阅第17页,“ Petr Baudi?s 2009-09-11的版本控制系统中的当前概念 ”。

|B| = 1 : b(B) = B0
|B| = 2 : b(B) = M(LCA(B0, B1), B0, B1)
M(B, x, y) = ??1
(b(B), x ? y)
m(x, y) = M(LCA(x, y), x, y)
Run Code Online (Sandbox Code Playgroud)

(是的,我也不知道该怎么读)

在发生冲突的情况下,该算法的主要思想是在将结果用作进一步合并的基础时,将冲突标记简单地保留在原位。
这意味着较早的冲突以及较新版本中的冲突更改都可以正确传播。

这是指revctrl.org/CrissCrossMerge,它描述了交叉合并中的递归合并的上下文。

纵横交错合并是一个祖先图,其中最小的共同祖先不是唯一的。
标量的最简单示例是:

   a
  / \
 b1  c1
 |\ /|
 | X |
 |/ \|
 b2  c2
Run Code Online (Sandbox Code Playgroud)

一个人可以在这里说的故事是,鲍勃和克莱尔分别进行了一些更改,然后将每个更改合并在一起。
他们发生了冲突,鲍勃(当然)认为他的改变更好,而克莱尔(通常)选择了她的版本。
现在,我们需要再次合并。这应该是一个冲突。

请注意,这可以通过文本合并来很好地完成-他们每个人都在文件中编辑了相同的位置,并且在解决冲突时,他们每个人都选择使结果文本与原始版本相同(即,他们不进行修改两种编辑方式以某种方式组合在一起,他们只是选择了一个即可获胜)。

所以:

另一种可能的解决方案是首先将“ b1”和“ c1” 合并到一个临时节点(基本上,假设图中的“ X”实际上是一个修订,而不仅仅是边缘交叉),然后将其用作合并“ b2”和“”的基础。c2'。

有趣的部分是合并' b1'和' c1'导致冲突时-诀窍是在这种情况下,' X'包含在内部记录的冲突中(例如,使用经典冲突标记)。

由于“ b2”和“ c2”都必须解决相同的冲突,因此,在以相同的方式解决冲突的情况下,它们都以相同的方式消除了“ X”中的冲突,从而得出清晰的合并结果。如果他们以不同的方式解决它,则来自' X' 的冲突会传播到最终合并结果。

这就是“ git merge:我如何在BASE文件中发生冲突”中描述的torek 作为“不对称结果”:

“这些不对称的结果是无害的,除了定时炸弹本身以及您以后进行递归合并的事实。
您会看到冲突。这取决于您自己解决- 再来一次 -但是这次我们/他们不容易欺骗,如果工作对人员CD“。

从恢复revctrl.org/CrissCrossMerge

如果合并将导致两个以上的碱基(',b1',' c1,' d1),则将它们连续合并-首先将' b1'与' c1',然后将结果与' d1'。

这就是“ Git”的“递归合并”策略的作用。


请注意,Git 2.22(2019年第二季度)将改进该递归合并策略,因为最近git merge-recursive“后端(Git 2.18)学会了一种新的启发式方法,可以根据同一目录中其他文件的移动方式来推断文件的移动。

由于这与基于文件本身的内容相似性(而不是基于其邻居正在执行的操作)相比,其固有的鲁棒性启发式方法不强,因此有时会给最终用户带来意想不到的结果。这已被减小,以使重命名的路径在索引中处于较高/冲突的阶段,以便用户可以检查并确认结果。

提交8c8e5bd提交e62d112提交6d169fd提交e0612a1提交8daec1d提交e2d563d提交c336ab8提交3f9c92e提交e9cd1b5提交967d6be提交043622b提交93a02c5提交e3de888提交259ccb6提交5ec1e72(2019年4月5日)由伊莱贾纽伦(newren
(通过合并JUNIOÇ滨野- gitster-提交96379f0,2019年5月8日)

merge-recursive:切换目录重命名检测默认

当所有的x/ax/bx/c已经转移到z/az/bz/c上一个分支,有一个关于是否的问题x/d在不同的分支添加应保持在x/d或出现在z/d当两个分支合并。
这里有不同的观点:

A)文件放在x / d;它与其他文件无关,x/因此所有文件都从一个x/转移到z/一个分支都无关紧要;x/d仍应停留在x/d

B)x/d与中的其他文件有关x/x/并被重命名为z/;。因此x/d应移至z/d

由于在Git 2.18之前无法检测目录重命名,因此(A)无论上下文如何,用户都可以体验。
选择(B)已在Git 2.18中实现,没有选择返回到(A),此后一直在使用。
但是,一位用户报告说合并结果不符合他们的期望,这使得更改默认值成为问题,尤其是因为在目录重命名检测移动文件时没有打印通知。

请注意,这里还有第三种可能性:

C)根据上下文和内容,Git无法确定有不同的答案,因此这是一个冲突。
在索引中使用较高的阶段来记录冲突并通知用户潜在的问题,而不是为它们静默地选择解决方案。

添加一个选项供用户指定是否使用目录重命名检测的首选项,默认为(C)
即使启用了目录重命名检测,也要添加有关移入新目录的文件的通知消息。