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

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

    <李> < p > 解决 - 这只能解决两个头部(即当前分支和另一个分支,你从)使用3-way合并算法。它试图仔细检测交叉合并歧义,通常被认为是安全和快速的 <李> < p > 递归 - 使用三向归并算法只能解析两个头部。当有多个公共祖先可以用于3-way合并时,它会创建一个公共祖先的合并树,并将其用作3-way合并的参考树。据报道,通过对取自Linux 2.6内核开发历史的实际合并提交进行测试,这可以减少合并冲突,而不会导致错误合并。此外,它还可以检测和处理涉及重命名的合并。当拉取或合并一个分支时,这是默认的合并策略 <李> < p > 章鱼 - 这解决了超过两个头的情况,但拒绝做复杂的合并,需要手动解决。它主要用于将主题分支头捆绑在一起。当拉取或合并多个分支时,这是默认的合并策略。

    <李> < p > 我们的 - 这可以解析任意数量的头,但合并的结果始终是当前分支头。

    <李> < p > 子树 - 这是一种改进的递归策略。在合并树A和树B时,如果B对应于A的子树,则首先调整B以匹配A的树结构,而不是读取同一级别的树。

什么时候应该指定与默认值不同的内容?它们最适合什么场景?

146590 次浏览

我不太熟悉resolve这个词,但我用过其他几个词:

递归

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

章鱼

当我有几棵树需要合并时,我就会使用octopus。你可以在更大的项目中看到这一点,许多分支都有独立的开发,并且都准备好整合到一个单一的头部。

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

为了说明问题,假设您有一个项目,它有一个主项目,然后要合并三个分支(称它们为a、b和c)。

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

series of recursive merges

然而,一个章鱼合并是这样的:

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

octopus merge

我们的

我们的==我想加入另一个头部,但抛弃所有头部带来的变化。

这将保留分支的历史,而不保留分支的任何影响。

(阅读:它甚至没有看到这些分支之间的变化。分支只是合并,没有对文件做任何操作。如果你想在另一个分支中合并,并且每次出现“我们的文件版本或他们的版本”的问题时,你可以使用git merge -X ours)

子树

当您希望将另一个项目合并到当前项目的子目录中时,Subtree非常有用。当您有一个不希望作为子模块包含的库时非常有用。

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

章鱼 merge在合并两个以上分支时自动使用。解决在这里主要是由于历史原因,当你遇到递归合并策略的极端情况时。

“Resolve"与“Recursive"合并策略

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

摘自O'Reilly的书Git版本控制 (亚马逊)(意译):

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

在交叉合并的情况下,有不止一个可能的合并基,解析策略是这样工作的:选择一个可能的合并基,并期待最好的结果。这其实没有听起来那么糟。通常情况下,用户一直在处理代码的不同部分。在这种情况下,Git检测到它正在重新合并一些已经存在的更改,并跳过重复的更改,从而避免冲突。或者,如果这些微小的更改确实会导致冲突,那么至少对于开发人员来说,冲突应该很容易处理。

我成功地用“resolve”合并树;默认递归策略失败。我得到fatal: git write-tree failed to write a tree错误,多亏了这篇博文 (镜子),我尝试了“-s resolve”,这是有效的。我还是不太确定为什么……但我认为这是因为我在两棵树中都有重复的变化,并解决了“跳过”;他们正确。

在Git 2.30(2021年第一季度)中,将会有一个合并策略:支持 ("表面上递归的双胞胎")。

git merge -s ort

这来自这个线程 from Elijah Newren:

目前,我称它为“表面递归的孪生”,或“;为短。比; 一开始,人们应该不会注意到它和当前递归策略之间的任何区别,除了我认为我可以让它更快一点(特别是对于大的回购) 但是它应该允许我修复一些(无可否认的极端情况)在当前设计中更难处理的错误,并且我认为不涉及$GIT_WORK_TREE$GIT_INDEX_FILE的合并将允许一些有趣的新功能。
无论如何,这是希望

问题:

在理想的世界里,我们应该:

  • ask unpack_trees() to do "没有“-u";

  • 在核心中执行所有归并递归计算并准备 结果索引,同时保持当前索引不变

  • 比较当前的in-core索引和最终的in-core索引,并注意工作树中需要添加、更新或删除的路径,并确保在更改反映到工作树时没有丢失信息 例如,结果想要创建一个文件,其中工作树当前有一个包含非消耗性内容的目录,结果想要删除一个文件,其中工作树文件有本地修改,等等 最后

  • 执行工作树更新,使其与生成的核心索引所显示的应该是什么样子。

结果:

参见Elijah Newren (newren)中的提交14 c4586(2020年11月02日),提交fe1a21d(2020年10月29日)和提交47 b1e89提交17 e5574(2020年10月27日)。
(由Junio C Hamano—gitster提交a1f9595中合并,18 Nov 2020)

merge-ort:新合并策略的空实现的基本API

署名:以利亚·纽伦

这是一个新的合并策略的开始。

虽然有一些API差异,实现在行为上也有一些差异,但它本质上是作为merge-recursive.c的最终替代品。

然而,它被构建为与合并递归并存,这样我们就有足够的时间来找出这些差异在现实世界中是如何发生的,而人们仍然可以回到合并递归。
(此外,我打算在此过程中避免修改merge-递归,以保持它的稳定。 这里需要注意的主要区别是,工作树和索引的更新不是与合并算法同时完成的,而是一个单独的后处理步骤。
新的API被设计成可以重复合并(例如在重基或樱桃选择期间),并且只在最后一次更新索引和工作树,而不是在每个中间结果中更新它

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

和:

< p >看到提交848年a856 提交fd15863提交23 bef2e 提交c8c35f6, 提交c12d1f2, 提交727年c75b, 提交489年c85f, 提交ef52778, 提交f06481f 提交848年a8560(2020年10月26日)。
(由Junio C Hamano—gitster提交66年c62ea中合并,18 Nov 2020)

t6423, t6436:注意改进的运动处理脏文件

署名:以利亚·纽伦

< p >“recursive"后端依赖unpack_trees()来检查未分阶段的更改是否会被合并覆盖,但是unpack_trees()不理解重命名——一旦它返回,它已经对工作树和索引写了很多更新。
因此,“递归”;必须做一个特殊的4-way合并,它也需要把工作副本作为一个额外的差异来源,我们必须小心地避免覆盖和导致文件移动到新的位置,以避免冲突 < p > “ort"相比之下,后端在内存中完成完整的合并,并仅更新索引和工作副本作为后处理步骤
如果有脏文件挡在路上,它可以简单地中止合并

t6423:期望在ort后端改进冲突标记标签

署名:以利亚·纽伦

冲突标记携带表单的额外注释 REF-OR-COMMIT:文件名 以帮助区分内容的来源,如果:FILENAME段对历史的两边都是相同的,则省略它(因此只有内容冲突的重命名才携带注释的这部分)

然而,有些情况下,:FILENAME注释被意外地省略了,这是由于合并递归的每个代码路径都需要一个所有特殊case-code的副本格式。

t6404, t6423:期望改进的重命名/删除处理在ort后端

署名:以利亚·纽伦

当一个文件被重命名并且有内容冲突时,合并递归在索引中没有旧文件名的一些阶段和新文件名的一些阶段;相反,它将旧文件名对应的所有阶段复制到新文件名的相应位置,这样就有三个更高阶的阶段都对应于新文件名。

这样做使用户更容易访问不同的版本并解决冲突(不需要手动'git rm '(< a href = " https://git-scm.com/docs/git-rm " rel = " noreferrer " > < / >)旧版本以及'git add'(< a href = " https://git-scm.com/docs/git-add " rel = " noreferrer " > < / >)新版本)。

rename/deletes应该类似地处理——重命名的文件应该有两个阶段,而不是只有一个阶段。
我们现在不想破坏合并递归的稳定性,因此更新相关测试以获得不同的期望,这取决于"recursive"或“;ort"正在使用合并策略。


Git 2.30(2021年第一季度),为新的合并策略做准备。

< p >看到提交848年a856 提交fd15863提交23 bef2e 提交c8c35f6, 提交c12d1f2, 提交727年c75b, 提交489年c85f, 提交ef52778, 提交f06481f 提交848年a8560(2020年10月26日)。
(由Junio C Hamano—gitster提交66年c62ea中合并,18 Nov 2020)

merge tests:期望在ort中改进目录/文件冲突处理

署名:以利亚·纽伦

merge-recursive.c是建立在运行unpack_trees()然后“做小的修改”的思想之上的;得到结果。
不幸的是,unpack_trees()是在更新-as-it-goes模式下运行的,导致merge-recursive.c跟进并以立即求值和修复-up-as-you-go设计结束 像目录/文件冲突这样的事情在索引数据结构中不能很好地表示,并且需要特殊的额外代码来处理。
但是,当发现重命名/删除冲突也可能涉及目录/文件冲突时,必须将特殊的目录/文件冲突处理代码复制到重命名/删除代码路径中。
...然后它必须复制修改/删除,重命名/重命名(1to2)冲突,…但它还是漏掉了一些。
此外,当发现还存在文件/子模块冲突和子模块/目录冲突时,我们需要将特殊子模块处理代码复制到整个代码库中的所有特殊情况

然后发现我们对目录/文件冲突的处理是次优的,因为它会创建未跟踪的文件来存储冲突文件的内容,如果有人运行'git merge --abort'(man)或'git rebase --abort'(man),这些文件就不会被清理。

考虑到索引中的目录/文件冲突,尝试添加或删除与这些文件对应的索引项也很困难或可怕。
但是改变merge-recursive.c来正确地处理这些是非常痛苦的,因为在代码中有很多站点具有类似但不相同的代码来处理目录/文件/子模块冲突,这些都需要更新

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


在Git 2.31(2021年Q1)中,合并后端“做得对”;开始出现。
例子:< / p >

参见提交6 d37ca2 (11 Nov 2020) by juno C Hamano (gitster)
看到提交89422 d2, 提交ef2b369 提交70912 f6提交6681年ce5 提交9 fefce6, 提交bb470f4, 提交ee4012d, 提交a9945bb, 提交8 adffaa, 提交89422 d20, 提交89422 d21, 提交89422 d22, 提交89422 d23, 提交89422 d24, 提交89422 d25, 提交89422 d26, 提交89422 d27, 提交89422 d28, 提交89422 d29, 提交ef2b3690 提交ef2b3691(2020年12月13日)。
(由Junio C Hamano—gitster提交f9d29da中合并,06 Jan 2021)

merge-ort:添加record_conflicted_index_entries()的实现

署名:以利亚·纽伦

checkout()之后,工作树有适当的内容,索引匹配工作副本。
这意味着所有未修改和干净合并的文件都有正确的索引项,但冲突的条目需要更新

为此,我们循环遍历冲突项,用CE_REMOVE标记路径的现有索引项,在索引的末尾为该路径添加新的高阶分段(忽略正常的索引排序顺序),然后在循环结束时删除CE_REMOVED-marked缓存项并对索引排序。


在Git 2.31 (Q1 2021)中,重命名检测添加到"ORT"合并策略。

参见提交6 fcccbd2的提交6 fcccbd提交f1665e6提交35 e47e3提交2 e91ddd提交53 e88a0提交af1e56c (15 Dec 2020)和提交c2d267d提交965年a7bc提交f39d05c提交6 fcccbd0, 提交6 fcccbd1 (14 Dec 2020)。
(由Junio C Hamano—gitster提交2856089中合并,25 Jan 2021)

例子:

merge-ort:添加正常重命名处理的实现

署名:以利亚·纽伦

实现正常重命名的处理。
此代码替换了merge-recurisve.c中的以下内容
  • process_renames()中与RENAME_NORMAL相关的代码
  • process_entry()RENAME_NORMAL大小写

此外,还有一些来自merge-recursive.c的共享代码,用于多种不同的重命名情况,我们将不再需要这种情况(或其他重命名情况):

  • handle_rename_normal()
  • setup_rename_conflict_info()

将四个独立的代码路径合并为一个代码路径可以通过改变设计实现:process_renames()调整opt->priv->paths中的conflict_info项,以便process_entry()可以正交地处理所有非重命名冲突类型(目录/文件、修改/删除等)。

这意味着我们不太可能错过某种冲突类型组合的特殊实现(参见66年c62ea带来的提交("Merge branch 'en/ Merge -tests'", 2020-11-18, Git v2.30.0-rc0—合并列在批# 6中),特别是提交ef52778 ("合并测试:期望改进的目录/文件冲突处理在ort", 2020-10-26, Git v2.30.0-rc0—合并列在批# 6中)了解更多详细信息)。

这样,再加上让工作树/索引更新在merge_switch_to_result()函数中以正交方式处理,极大地简化了各种特殊重命名情况下的代码。

(公平地说,之前处理普通重命名的代码并没有那么复杂,但现在还是简单多了。)

并且,在Git 2.31 (Q1 2021)中,oRT合并策略学习了对合并冲突的更多支持。

< p >看到提交4 ef88fc 提交4204年cd5提交70年f19c7 提交c73cda7, 提交f591c47, 提交62年fdec1, 提交991年bbdc, 提交5 a1a1e8, 提交23366 d2, 提交4 ef88fc0 提交4 ef88fc1(2021年1月01)。
(由Junio C Hamano—gitster in 提交b65b9ff合并,05 Feb 2021)

merge-ort:为相同路径下的不同类型的文件添加处理

署名:以利亚·纽伦

添加一些显式考虑以下类型冲突的处理:

  • 文件/子模块
  • 文件/符号链接
  • 子模块/ symlink>将它们作为冲突留在同一路径上对用户来说很难解决,所以将它们中的一个或两个移到一边,以便它们各自拥有自己的路径。
注意,在递归处理的情况下(即
. 0) call_depth > 0),我们可以只使用两个合并基的合并基作为合并结果,就像我们对修改/删除冲突、二进制文件、冲突的子模块值等所做的那样