你什么时候会使用不同的git合并策略?

时间:2008-12-14 19:12:12

标签: git merge git-merge

在git-merge的手册页中,您可以使用许多合并策略。

  • 解决 - 这只能使用3向合并算法解析两个头(即当前分支和你从中拉出的另一个分支)。它试图仔细检测纵横交错的合并模糊性,并且通常被认为是安全和快速的。

  • 递归 - 这只能使用3路合并算法解析两个磁头。当有多个可用于3向合并的共同祖先时,它会创建共同祖先的合并树,并将其用作3向合并的参考树。据报道,这可以减少合并冲突,而不会因为从Linux 2.6内核开发历史记录中进行的实际合并提交而导致错误合并。此外,这可以检测和处理涉及重命名的合并。这是拉动或合并一个分支时的默认合并策略。

  • 章鱼 - 这解决了两个以上的案例,但拒绝进行需要手动解决的复杂合并。它主要用于将主题分支头捆绑在一起。这是拉动或合并多个分支时的默认合并策略。

  • 我们的 - 这解决了任意数量的头,但合并的结果始终是当前的分支头。它旨在用来取代边支的旧发展历史。

  • 子树 - 这是一种修改后的递归策略。当合并树A和B时,如果B对应于A的子树,则首先调整B以匹配A的树结构,而不是读取相同级别的树。此调整也适用于共同的祖先树。

我应该何时指定与默认值不同的内容?哪些场景最适合?

5 个答案:

答案 0 :(得分:295)

我不熟悉决心,但我已经使用了其他人:

递归

递归是非快进合并的默认值。我们都熟悉那个。

章鱼

当我有几棵树需要合并时,我已经使用了章鱼。你可以在大型项目中看到这一点,在这些项目中,许多分支机构已经进行了独立开发,并且已经准备好将它们组合在一起。

章鱼分支在一次提交中合并多个头,只要它可以干净地完成。

为了说明,假设你有一个拥有master的项目,然后有三个分支要合并(称为a,b和c)。

一系列递归合并看起来像这样(注意第一次合并是快进,因为我没有强制递归):

series of recursive merges

然而,单个章鱼合并将如下所示:

commit ae632e99ba0ccd0e9e06d09e8647659220d043b9
Merge: f51262e... c9ce629... aa0f25d...

octopus merge

我们

我们的==我想拉进另一个脑袋,但要扔掉所有引入的变化。

这样可以保留分支的历史记录,而不会产生分支的任何影响。

(阅读:甚至没有看到这些分支之间的变化。分支刚刚合并,没有对文件做任何事情。如果你想在另一个分支中合并,每次都有问题“我们的文件版本或其版本“您可以使用git merge -X ours

子树

如果要将另一个项目合并到当前项目的子目录中,则子树很有用。当您有一个库时,您不希望将其包含为子模块。

答案 1 :(得分:46)

实际上,您想要选择的唯一两种策略是我们的,如果您想放弃分支带来的更改,但保留分支的历史记录,子树,如果您将独立项目合并到超级项目的子目录中(如'git'存储库中的'git-gui')。

合并两个以上的分支时会自动使用

章鱼合并。 resolve 主要是出于历史原因,以及当您遇到递归合并策略极端情况时。

答案 2 :(得分:21)

“解决”与“递归”合并策略

递归是当前默认的双头策略,但经过一些搜索后我终于找到了一些关于“解析”合并策略的信息。

取自O'Reilly的书Version Control with GitAmazon)(转述):

  

最初,“resolve”是Git合并的默认策略。

     

在纵横交错合并的情况下,有多个可能的合并基础,解决策略的工作方式如下:选择一个可能的合并基础,并希望最好。这实际上没有它听起来那么糟糕。事实证明,用户一直在处理代码的不同部分。在这种情况下,Git检测到它正在重新出现已经存在的一些更改并跳过重复的更改,从而避免冲突。或者,如果这些是导致冲突的轻微变化,至少冲突应该很容易让开发人员处理..

我使用默认递归策略失败的“resolve”成功合并了树。我收到fatal: git write-tree failed to write a tree错误,感谢this blog postmirror),我尝试了“-s resolve”,这有效。我仍然不确定为什么......但我认为这是因为我在两棵树上都有重复的变化,并且解决了“跳过”它们的问题。

答案 3 :(得分:1)

对于Git 2.30(第2021年第1季度),将有一个合并策略: ORT (“ 表面上递归的Twin ”)。< / p>

git merge -s ort

请在commit 14c4586前参阅commit fe1a21d(2020年11月2日),commit 47b1e89(2020年10月29日)和commit 17e5574Elijah Newren (newren)(2020年10月27日)。
(由Junio C Hamano -- gitster --commit a1f9595中合并,2020年11月18日)

merge-ort:具有空实现的新合并策略的准系统API

签名人:伊利亚·纽伦

这是新合并策略的开始。

尽管API有所不同,并且实现在行为上也有所不同,但这实际上是最终替代merge-recursive.c的方式。

但是,它被构建为与合并递归并排存在,以便我们有足够的时间来找出那些差异在现实世界中的表现,而人们仍然可以回到合并递归。
(此外,我打算避免在此过程中修改merge-recursive,以使其保持稳定。)

此处的主要区别在于,工作树和索引的更新不是与合并算法同时完成,而是一个单独的后处理步骤。
新API的设计使其可以重复合并(例如,在重新设置基数或进行选择时),并且最后只更新索引和工作树一次,而不用每个中间结果对其进行更新。

此外,您可以在两个分支之间执行合并,而这两个分支都不与索引或工作树匹配,而不会破坏索引或工作树。

并且:

请参见commit 848a856commit fd15863commit 23bef2ecommit c8c35f6commit c12d1f2commit 727c75bcommit 489c85f,{{3} },commit ef52778(2020年10月26日)通过commit f06481f
(由Elijah Newren (newren)Junio C Hamano -- gitster --中合并,2020年11月18日)

commit 66c62ea:注意改进了处理脏文件的操作

签名人:伊利亚·纽伦

“递归”后端依靠unpack_trees()检查合并是否覆盖了未暂存的更改,但是unpack_trees()不了解重命名-一旦重命名,它就已经编写了许多更新到工作树和索引。
因此,“递归”必须进行特殊的4向合并,在这种情况下,它还需要将工作副本视为差异的额外来源,我们必须谨慎地避免覆盖并导致将文件移至新位置以避免冲突。

相比之下,“ ort”后端执行完整的合并内存,并且仅在后期处理步骤中更新索引和工作副本
如果途中有脏文件,则可以简单地中止合并。

t6423, t6436:期望ort后端的冲突标记标签得到改善

签名人:伊利亚·纽伦

冲突标记带有以下形式的附加注释: REF-OR-COMMIT:FILENAME 为了帮助区分内容的来源,:FILENAME部分在历史的两边都是相同的(因此,只有内容冲突的重命名才带有注释的那一部分),将其保留。

但是,在某些情况下,由于merge-recursive的every-codepath-needs-a-copy-of-all-special-case的代码格式,意外地取消了:FILENAME批注。

t6423:期望在ort后端中改善重命名/删除处理

签名人:伊利亚·纽伦

重命名文件时,如果存在内容冲突,则合并递归在索引中没有旧文件名的某些阶段和新文件名的某些阶段;而是将与旧文件名相对应的所有阶段复制到新文件名的相应位置,以便三个与所有新文件名相对应的高阶阶段。

通过这种方式执行操作,可以使用户更轻松地访问不同版本并解决冲突(无需手动't6404, t6423'git rm旧版本以及'man'git add新版本)。

重命名/删除应该以类似的方式进行处理-重命名文件应该有两个阶段,而不仅仅是一个阶段。
我们现在不希望破坏合并递归的稳定性,而是根据是否使用“ recursive”或“ ort”合并策略来更新相关测试以具有不同的期望。


使用Git 2.30(Q1 2021),为新的合并策略做准备。

请参见mancommit 848a856commit fd15863commit 23bef2ecommit c8c35f6commit c12d1f2commit 727c75b,{{3} },commit 489c85f(2020年10月26日)通过commit ef52778
(由commit f06481fElijah Newren (newren)中合并,2020年11月18日)

Junio C Hamano -- gitster --:期望ort中的目录/文件冲突处理得到改善

签名人:伊利亚·纽伦

commit 66c62ea建立在运行unpack_trees()然后进行“小的修饰”以获得结果的想法上。
不幸的是,unpack_trees()以“按需更新”模式运行,导致merge tests随之效仿,并最终进行了立即评估和按需修复设计。

诸如目录/文件冲突之类的内容在索引数据结构中无法很好地表示,并且需要特殊的额外代码来处理。
但是,当发现目录/文件冲突中也可能涉及重命名/删除冲突时,必须将特殊的目录/文件冲突处理代码复制到重命名/删除代码路径中。
...然后必须将其复制以进行修改/删除,以及重命名/重命名(1to2)冲突,...但是仍然错过了一些。
此外,当发现还存在文件/子模块冲突和子模块/目录冲突时,我们需要将特殊子模块处理代码复制到整个代码库中的所有特殊情况。

然后发现我们对目录/文件冲突的处理不是最理想的,因为它将创建未跟踪的文件来存储冲突文件的内容,如果有人要运行'{{3} }'merge-recursive.c或'merge-recursive.c'git merge --abort

鉴于索引中的目录/文件冲突,尝试添加或删除与这些文件相对应的索引条目也很困难或令人恐惧。
但是更改man来正确处理这些问题非常麻烦,因为代码中有太多站点具有相似但不相同的代码来处理目录/文件/子模块冲突,因此都需要更新。

我一直在努力通过单个代码路径在merge-ort中推动所有目录/文件/子模块的冲突处理,并避免创建未跟踪的文件来存储跟踪的内容(它确实在备用路径上记录内容,但要确保它们具有更高的路径-索引中的顺序阶段。

答案 4 :(得分:0)

由于上述答案并未显示所有策略详细信息。例如,某些答案缺少有关导入resolve选项和recursive的详细信息,其中ours具有许多子选项,如theirspatiencerenormalize,{ {1}},等等。

因此,我建议您访问官方的git文档,其中解释了所有可能的功能:

https://git-scm.com/docs/merge-strategies