我了解到git merge递归实际上是在有多个共同祖先的情况下发生的,它会创建一个虚拟提交来合并这些共同祖先,然后再继续合并最近的提交(对不起,我不确定是否应该一个术语)。
但是我一直在尝试寻找有关git merge递归策略实际工作原理的更多信息,但找不到太多信息。
有人可以详细说明git merge递归如何真正地执行,并附带示例和可能的流程图以帮助更好地可视化吗?
答案 0 :(得分:2)
您可以找到一个description here(另请参见part 2):
何时需要合并递归?
如果我们找到“两个共同的祖先”怎么办?下面的分支资源管理器视图显示了一个替代方案,其中有两个可能的“共同祖先”。
请注意:该示例有点强制,因为最初没有充分的理由让开发人员从变更集11合并到16,而不是从变更集15合并(合并时最新的分支主要) )。
但是,让我们假设必须这样做是有原因的,例如,变更集11稳定,而当时的13和15则不稳定。要点是:在15到16之间,没有一个唯一的祖先,而是两个“距离”相同的祖先:12和11。
虽然这种情况不会经常发生,但对于寿命长的分支或复杂的分支拓扑,确实很可能发生。 (上面描述的情况是驱动“多祖先”问题的最短情况,但是在“交叉”合并之间的多个变更集和分支也可能发生)。
一种解决方案是“选择”一个祖先作为合并的有效祖先(这是Mercurial采取的选择),但是有很多缺点。
合并递归如何工作?
当找到多个有效祖先时,递归合并策略将创建一个新的唯一“虚拟祖先”,以合并最初发现的祖先。
下图描述了算法:
新的祖先2将用作“祖先”,以合并“ src”和“ dst”。
“合并递归策略”比仅仅“选择两者之一”能够找到更好的解决方案。
请注意:在 Fredrik Kuivinen 之后,合并递归策略最初是合并“ 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?"描述为“不对称结果”:
“这些非对称结果是无害的,除了定时炸弹本身以及以后您进行递归合并的事实。
您会看到冲突。再次由您决定要解决- -但是,如果这对C
和D
的人有效,那么这次我们/他们的技巧绝非易事。”
从revctrl.org/CrissCrossMerge
恢复:
如果合并将导致两个以上的碱基('
b1
,'c1
,'d1
'),则它们将连续合并-第一个'b1
'与'c1
',然后结果与'd1
'。这就是“ Git”的“递归合并”策略的作用。
请注意,由于git merge-recursive“后端(Git 2.18)最近学会了一种新的启发式方法,因此Git 2.22(2019年第二季度)将改进该递归合并策略。 根据同一目录中其他文件的方式推断文件移动 感动。
由于这在本质上不如基于文件本身的内容相似性(而不是基于其邻居正在做什么的启发式)强健的启发式,因此有时会给最终用户带来意想不到的结果。这已被减小,以使重命名的路径在索引中处于较高/冲突的阶段,因此 用户可以检查并确认结果。
请参见commit 8c8e5bd,commit e62d112,commit 6d169fd,commit e0612a1,commit 8daec1d,commit e2d563d,commit c336ab8,{{3} },commit 3f9c92e,commit e9cd1b5,commit 967d6be,commit 043622b,commit 93a02c5,commit e3de888,commit 259ccb6(2019年4月5日)作者:{ {3}}。
(由commit 5ec1e72在Elijah Newren (newren
)中合并,2019年5月8日)
merge-recursive
:切换目录重命名检测默认值当
x/a
,x/b
和x/c
的所有一个都移到z/a
,z/b
和z/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)
。
即使启用了目录重命名检测,也要添加有关已移动到新目录中的文件的通知消息。