我知道git merge递归实际上是在有多个共同祖先的情况下发生的,它会创建一个虚拟提交来合并这些共同祖先,然后再继续合并较新的提交(对不起,我不确定是否应该有一个术语这个)。
但是我一直在尝试寻找更多有关git merge递归策略如何实际工作的详细信息,但找不到太多信息。
谁能详细说明git merge递归的实际效果,并提供示例和可能的流程图来帮助更好地可视化?
什么时候需要合并递归?
如果我们找到“两个共同的祖先”怎么办?下面的分支资源管理器视图显示了一个替代方案,其中有两个可能的“共同祖先”。
请注意:该示例有点强制,因为最初没有充分的理由让开发人员从变更集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脚本,在提交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 ?作为“不对称结果”:
“这些不对称的结果是无害的,除了定时炸弹本身以及您以后进行递归合并的事实。
您会看到冲突。这取决于您自己解决- 再来一次 -但是这次我们/他们不容易欺骗,如果工作对人员C和D“。
从恢复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/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)。
即使启用了目录重命名检测,也要添加有关移入新目录的文件的通知消息。