454

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

  • 解决- 这只能使用 3 路合并算法解决两个头(即当前分支和您从中提取的另一个分支)。它试图仔细检测交叉合并歧义,并且通常被认为是安全和快速的。

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

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

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

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

我什么时候应该指定不同于默认值的东西?什么场景最适合?

4

4 回答 4

324

我不熟悉resolve,但我用过其他的:

递归的

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

章鱼

当我有几棵需要合并的树时,我使用了章鱼。您可以在大型项目中看到这一点,其中许多分支都进行了独立开发,并且已经准备好整合到一个单一的头部。

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

举例来说,假设您有一个项目,该项目有一个 master,然后是三个要合并的分支(称为 a、b 和 c)。

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

一系列递归合并

但是,单个章鱼合并看起来像这样:

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

章鱼合并

我们的

我们的 == 我想引入另一个头部,但丢弃头部引入的所有变化。

这会保留分支的历史记录,而不会受到分支的任何影响。

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

子树

当您想将另一个项目合并到当前项目的子目录中时,子树很有用。当您有一个不想包含为子模块的库时很有用。

于 2008-12-14T20:25:49.760 回答
51

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

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

于 2008-12-15T00:56:24.037 回答
30

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

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

摘自 O'Reilly 的书《使用 Git 进行版本控制》 (亚马逊)(释义):

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

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

我已经使用默认递归策略失败的“resolve”成功合并了树。我遇到了fatal: git write-tree failed to write a tree错误,感谢这篇博文mirror),我尝试了“-s resolve”,它奏效了。我仍然不确定为什么......但我认为这是因为我在两棵树中都有重复的更改,并正确解决“跳过”它们。

于 2012-05-17T13:15:38.813 回答
4

随着 Git 2.30(2021 年第一季度),将会有一个的合并策略:ORT(“ Ostensibly Recursive's Twin ”)。

git merge -s ort

这来自以利亚纽伦的这个帖子:

现在,我称它为“表面上递归的双胞胎”,或简称为“ort”。> 起初,人们不应该注意到它与当前递归策略之间的任何区别,除了我认为我可以让它更快一点(尤其是对于大回购)。

但它应该允许我修复一些在当前设计中更难处理的(不可否认的极端情况)错误,我认为合并不会触及$GIT_WORK_TREE$GIT_INDEX_FILE允许一些有趣的新功能。
无论如何,这就是希望。

问题

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

  • 要求unpack_trees()做“ read-tree -m”没有“ -u”;

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

  • 比较当前的in-core index和生成的in-core index,注意工作树中需要添加、更新或删除的路径,保证变化反映到工作树时不丢失信息树;
    例如,结果想要创建一个文件,其中工作树当前有一个目录,其中包含不可消耗的内容,结果想要删除工作树文件具有本地修改的文件等;
    然后最后

  • 执行工作树更新以使其与生成的核心索引所显示的内容相匹配。

结果:

请参阅Elijah Newren ( ) 的提交 14c4586( 2020 年 11 月 2 日)、提交 fe1a21d(2020 年 10 月 29 日)和提交 47b1e89提交 17e5574 newren 2020 年 10 月 27 日
(由Junio C Hamano 合并 -- gitster--提交 a1f9595中,2020 年 11 月 18 日)

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

签字人:以利亚·纽伦

这是新合并策略的开始。

虽然存在一些 API 差异,并且实现在行为上也存在一些差异,但它本质上是作为merge-recursive.c.

但是,它被构建为与合并递归并存,因此我们有足够的时间来找出这些差异在现实世界中是如何出现的,而人们仍然可以退回到合并递归。
(另外,我打算避免在此过程中修改合并递归,以保持稳定。)

这里值得注意的主要区别是工作树和索引的更新不是与合并算法同时完成的,而是一个单独的后处理步骤。
新的 API 被设计成可以重复合并(例如在 rebase 或cherry-pick 期间),并且只在最后更新一次索引和工作树,而不是用每个中间结果更新它。

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

和:

请参阅Elijah Newren ( ) 的提交848a856、提交fd15863、提交23bef2e提交 c8c35f6提交 c12d1f2提交 727c75b提交 489c85f提交 ef52778提交 f06481f(2020 年 10 月 26 日(由Junio C Hamano 合并——提交 66c62ea中,2020 年 11 月 18 日)newren
gitster

t6423, t6436:注意改进了脏文件的 ort 处理

签字人:以利亚·纽伦

“递归”后端依赖于unpack_trees()检查未暂存的更改是否会被合并覆盖,但unpack_trees()不理解重命名 - 一旦它返回,它已经向工作树和索引写入了许多更新。
因此,“递归”必须进行特殊的 4 路合并,它还需要将工作副本视为额外的差异来源,我们必须小心避免覆盖并导致将文件移动到新位置以避免冲突。

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

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

签字人:以利亚·纽伦

冲突标记带有 REF-OR-COMMIT:FILENAME 形式的额外注释,以帮助区分内容的来源,:FILENAME如果历史的双方相同,则忽略该片段(因此只有带有内容冲突的重命名带有注释的那一部分)。

但是,在某些情况下:FILENAME,由于合并递归的每个代码路径需要所有特殊情况代码格式的副本,注释被意外遗漏了。

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

签字人:以利亚·纽伦

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

这样做使用户更容易访问不同的版本并解决冲突(无需手动' git rm' man旧版本以及' git add' man新版本)。

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


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

请参阅Elijah Newren ( ) 的提交848a856、提交fd15863、提交23bef2e提交 c8c35f6提交 c12d1f2提交 727c75b提交 489c85f提交 ef52778提交 f06481f(2020 年 10 月 26 日(由Junio C Hamano 合并——提交 66c62ea中,2020 年 11 月 18 日)newren
gitster

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

签字人:以利亚·纽伦

merge-recursive.c建立在跑步的想法上unpack_trees(),然后“做一些小的修饰”以获得结果。
不幸的是,unpack_trees()它以“即用即更新”模式运行,导致merge-recursive.c紧随其后并最终得到立即评估和“即用即修复”设计。

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

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

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

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


随着 Git 2.31(2021 年第一季度),“正确完成”的合并后端开始出现。
例子:

请参阅Junio C Hamano ( ) 的提交 6d37ca2(2020 年 11 月 11 日。 请参阅提交89422D2提交7681CE5提交9fefce6提交BB470F4提交EE4012D提交A9945BBCommit 8ADFFAA,提交6A02DD9,Commit 291F29C提交98BF984Commit 34E557A,提交D2BC199,提交0C0D705,提交0C0D705,提交D2BC199提交885595BBCommit 8ADFFCE6Commit 9fefce6Commit 9fefce6Commit 9fefce6Commit 9fefce6Commit 9fefce6Commit 。 c801717gitster
提交 e4171b1提交 231e2dd提交 5b59c3d(2020 年 12 月 13 日),作者Elijah Newren ( newren)
(由Junio C Hamano 合并 -- gitster--提交 f9d29da中,2021 年 1 月 6 日)

merge-ort: 添加实现record_conflicted_index_entries()

签字人:以利亚·纽伦

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

我们通过遍历冲突的条目来做到这一点,用 标记路径的现有索引条目,CE_REMOVE在索引末尾为路径添加新的高阶暂存(忽略正常的索引排序顺序),然​​后在循环结束时删除CE_REMOVED-marked缓存条目并对索引进行排序。


在 Git 2.31(2021 年第一季度)中,重命名检测被添加到“ORT”合并策略中。

请参阅提交 6fcccbd提交 f1665e6提交 35e47e3提交 2e91ddd提交 53e88a0提交 af1e56c(2020 年 12 月 15 日)和提交 c2d267d 提交965a7bc提交 f39d05c提交 e1a124e提交 864075e(14 月 14日(由Junio C Hamano 合并 -- --提交 2856089中,2021 年 1 月 25 日)newren
gitster

例子:

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

签字人:以利亚·纽伦

实现对正常重命名的处理。
此代码替换以下内容merge-recurisve.c

  • RENAME_NORMAL与in相关的代码process_renames()
  • RENAME_NORMAL情况process_entry()

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

  • handle_rename_normal()
  • setup_rename_conflict_info()

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

这意味着我们不太可能错过某种冲突类型组合的特殊实现(参见66c62ea引入的提交(“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(2021 年第一季度),使用 Git 2.31(2021 年第一季度),oRT 合并策略学习了对合并冲突的更多支持。

请参阅Elijah Newren ( ) 提交的 4ef88fc 、commit 4204cd5commit 70f19c7 、 commit c73cda7commit f591c47commit 62fdec1commit 991bbdccommit 5a1a1e8commit 23366d2commit 0ccfa4e (01 Jan 2021 )(由Junio C Hamano 合并——提交 b65b9ff中,2021 年 2 月 5 日)newren
gitster

merge-ort: 在同一路径添加对不同类型文件的处理

签字人:以利亚·纽伦

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

  • 文件/子模块
  • 文件/符号链接
  • submodule/symlink> 将它们作为冲突留在同一路径上对于用户来说很难解决,因此将它们中的一个或两个移到一边,以便他们各自获得自己的路径。

请注意,在递归处理(即
call_depth > 0)的情况下,我们可以只使用两个合并基的合并基作为合并结果,就像我们对修改/删除冲突、二进制文件、冲突子模块值等所做的那样。

于 2020-11-22T02:20:23.097 回答