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

时间:2019-05-06 03:59:09

标签: git merge

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

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

有人可以详细说明git merge递归如何真正地执行,并附带示例和可能的流程图以帮助更好地可视化吗?

1 个答案:

答案 0 :(得分:2)

您可以找到一个description here(另请参见part 2):

  

何时需要合并递归?

     

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

initial situation

  
    

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

         

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

  
     

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

     

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

     

合并递归如何工作?

     

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

     

下图描述了算法:

algo

  

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

merge

  

“合并递归策略”比仅仅“选择两者之一”能够找到更好的解决方案。


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

有关更多详细信息,请考虑第17页的“ Current Concepts in Version Control Systems from 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)

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

  

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

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

  

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

   a
  / \
 b1  c1
 |\ /|
 | X |
 |/ \|
 b2  c2
  

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

     

请注意,通过文本合并可以很好地实现这一点-他们每个人都在文件中编辑了相同的位置,解决冲突时,他们各自选择使结果文本与原始版本相同(即,不会以某种方式一起修改这两个剪辑,他们只是选了一个就赢了。)

所以:

  

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

     

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

     

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

torek中将"git merge: how did I get a conflict in BASE file?"描述为“不对称结果”:

  

“这些非对称结果是无害的,除了定时炸弹本身以及以后您进行递归合并的事实。
  您会看到冲突。再次由您决定要解决- -但是,如果这对CD的人有效,那么这次我们/他们的技巧绝非易事。”

revctrl.org/CrissCrossMerge恢复:

  

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

     

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


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

由于这在本质上不如基于文件本身的内容相似性(而不是基于其邻居正在做什么的启发式)强健的启发式,因此有时会给最终用户带来意想不到的结果。这已被减小,以使重命名的路径在索引中处于较高/冲突的阶段,因此 用户可以检查并确认结果。

请参见commit 8c8e5bdcommit e62d112commit 6d169fdcommit e0612a1commit 8daec1dcommit e2d563dcommit c336ab8,{{3} },commit 3f9c92ecommit e9cd1b5commit 967d6becommit 043622bcommit 93a02c5commit e3de888commit 259ccb6(2019年4月5日)作者:{ {3}}。
(由commit 5ec1e72Elijah Newren (newren)中合并,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/dx/中的其他文件有关,并且x/重命名为z/;因此应将x/d移至z/d

  
     

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

     

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

     
    

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

  
     

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