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

resolve - This can only resolve two heads (i.e. the current branch and another branch you pulled from) using 3-way merge algorithm. It tries to carefully detect criss-cross merge ambiguities and is considered generally safe and fast. recursive - This can only resolve two heads using 3-way merge algorithm. When there are more than one common ancestors that can be used for 3-way merge, it creates a merged tree of the common ancestors and uses that as the reference tree for the 3-way merge. This has been reported to result in fewer merge conflicts without causing mis-merges by tests done on actual merge commits taken from Linux 2.6 kernel development history. Additionally this can detect and handle merges involving renames. This is the default merge strategy when pulling or merging one branch. octopus - This resolves more than two-head case, but refuses to do complex merge that needs manual resolution. It is primarily meant to be used for bundling topic branch heads together. This is the default merge strategy when pulling or merging more than one branches. ours - This resolves any number of heads, but the result of the merge is always the current branch head. It is meant to be used to supersede old development history of side branches. subtree - This is a modified recursive strategy. When merging trees A and B, if B corresponds to a subtree of A, B is first adjusted to match the tree structure of A, instead of reading the trees at the same level. This adjustment is also done to the common ancestor tree.

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


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

递归

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

章鱼

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

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

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

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

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

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

Ours

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

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

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

子树

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


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

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


“解析”和“递归”合并策略

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

摘自O'Reilly的《Git版本控制》(Amazon):

Originally, "resolve" was the default strategy for Git merges. In criss-cross merge situations, where there is more than one possible merge basis, the resolve strategy works like this: pick one of the possible merge bases, and hope for the best. This is actually not as bad as it sounds. It often turns out that the users have been working on different parts of the code. In that case, Git detects that it's remerging some changes that are already in place and skips the duplicate changes, avoiding the conflict. Or, if these are slight changes that do cause conflict, at least the conflict should be easy for the developer to handle..

我已经成功地合并了使用默认递归策略失败的“resolve”树。我是致命的:git写树失败写树错误,多亏了这篇博客文章(镜像),我尝试了“-s resolve”,这是有效的。我还是不太确定为什么……但我认为这是因为我在两棵树中都有重复的更改,并正确地解决“跳过”它们。


在Git 2.30(2021年第一季度)中,将会有一个新的合并策略:ORT(表面上递归的孪生兄弟)。

git merge -s ort

这篇文章来自Elijah Newren:

For now, I'm calling it "Ostensibly Recursive's Twin", or "ort" for short. > At first, people shouldn't be able to notice any difference between it and the current recursive strategy, other than the fact that I think I can make it a bit faster (especially for big repos). But it should allow me to fix some (admittedly corner case) bugs that are harder to handle in the current design, and I think that a merge that doesn't touch $GIT_WORK_TREE or $GIT_INDEX_FILE will allow for some fun new features. That's the hope anyway.

问题:

In the ideal world, we should: ask unpack_trees() to do "read-tree -m" without "-u"; do all the merge-recursive computations in-core and prepare the resulting index, while keeping the current index intact; compare the current in-core index and the resulting in-core index, and notice the paths that need to be added, updated or removed in the working tree, and ensure that there is no loss of information when the change is reflected to the working tree; E.g. the result wants to create a file where the working tree currently has a directory with non-expendable contents in it, the result wants to remove a file where the working tree file has local modification, etc.; And then finally carry out the working tree update to make it match what the resulting in-core index says it should look like.

结果:

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

merge-ort:新合并策略的基本API,空实现 署名:以利亚·纽伦

This is the beginning of a new merge strategy. While there are some API differences, and the implementation has some differences in behavior, it is essentially meant as an eventual drop-in replacement for merge-recursive.c. However, it is being built to exist side-by-side with merge-recursive so that we have plenty of time to find out how those differences pan out in the real world while people can still fall back to merge-recursive. (Also, I intend to avoid modifying merge-recursive during this process, to keep it stable.) The primary difference noticable here is that the updating of the working tree and index is not done simultaneously with the merge algorithm, but is a separate post-processing step. The new API is designed so that one can do repeated merges (e.g. during a rebase or cherry-pick) and only update the index and working tree one time at the end instead of updating it with every intermediate result. Also, one can perform a merge between two branches, neither of which match the index or the working tree, without clobbering the index or working tree.

And:

参见Elijah Newren (Newren)的commit 848a856, commit fd15863, commit 23bef2e, commit c8c35f6, commit c12d1f2, commit 727c75b, commit 489c85f, commit ef52778, commit f06481f(2020年10月26日)。 (由Junio C Hamano - gitster -在commit 66c62ea中合并,2020年11月18日)

T6423, t6436:注意改进了对脏文件的ort处理 署名:以利亚·纽伦

The "recursive" backend relies on unpack_trees() to check if unstaged changes would be overwritten by a merge, but unpack_trees() does not understand renames -- and once it returns, it has already written many updates to the working tree and index. As such, "recursive" had to do a special 4-way merge where it would need to also treat the working copy as an extra source of differences that we had to carefully avoid overwriting and resulting in moving files to new locations to avoid conflicts. The "ort" backend, by contrast, does the complete merge inmemory, and only updates the index and working copy as a post-processing step. If there are dirty files in the way, it can simply abort the merge.

T6423:期望改进的冲突标记标签在运动后端 署名:以利亚·纽伦

冲突标记带有表单的额外注释 REF-OR-COMMIT:文件名 以帮助区分内容来自哪里,如果:FILENAME部分对于历史记录的两边都是相同的,则将被省略(因此只有具有内容冲突的重命名才携带注释的那部分)。 然而,由于合并递归的每个代码路径都需要一份所有特殊case-code的副本格式,有些情况下:FILENAME注释被意外地省略了。

T6404, t6423:期望在ort后端改进重命名/删除处理 署名:以利亚·纽伦

When a file is renamed and has content conflicts, merge-recursive does not have some stages for the old filename and some stages for the new filename in the index; instead it copies all the stages corresponding to the old filename over to the corresponding locations for the new filename, so that there are three higher order stages all corresponding to the new filename. Doing things this way makes it easier for the user to access the different versions and to resolve the conflict (no need to manually 'git rm '(man) the old version as well as 'git add'(man) the new one). rename/deletes should be handled similarly -- there should be two stages for the renamed file rather than just one. We do not want to destabilize merge-recursive right now, so instead update relevant tests to have different expectations depending on whether the "recursive" or "ort" merge strategies are in use.


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

参见Elijah Newren (Newren)的commit 848a856, commit fd15863, commit 23bef2e, commit c8c35f6, commit c12d1f2, commit 727c75b, commit 489c85f, commit ef52778, commit f06481f(2020年10月26日)。 (由Junio C Hamano - gitster -在commit 66c62ea中合并,2020年11月18日)

合并测试:期望在ort中改进目录/文件冲突处理 署名:以利亚·纽伦

merge-recursive.c is built on the idea of running unpack_trees() and then "doing minor touch-ups" to get the result. Unfortunately, unpack_trees() was run in an update-as-it-goes mode, leading merge-recursive.c to follow suit and end up with an immediate evaluation and fix-it-up-as-you-go design. Some things like directory/file conflicts are not well representable in the index data structure, and required special extra code to handle. But then when it was discovered that rename/delete conflicts could also be involved in directory/file conflicts, the special directory/file conflict handling code had to be copied to the rename/delete codepath. ...and then it had to be copied for modify/delete, and for rename/rename(1to2) conflicts, ...and yet it still missed some. Further, when it was discovered that there were also file/submodule conflicts and submodule/directory conflicts, we needed to copy the special submodule handling code to all the special cases throughout the codebase. And then it was discovered that our handling of directory/file conflicts was suboptimal because it would create untracked files to store the contents of the conflicting file, which would not be cleaned up if someone were to run a 'git merge --abort'(man) or 'git rebase --abort'(man). It was also difficult or scary to try to add or remove the index entries corresponding to these files given the directory/file conflict in the index. But changing merge-recursive.c to handle these correctly was a royal pain because there were so many sites in the code with similar but not identical code for handling directory/file/submodule conflicts that would all need to be updated. I have worked hard to push all directory/file/submodule conflict handling in merge-ort through a single codepath, and avoid creating untracked files for storing tracked content (it does record things at alternate paths, but makes sure they have higher-order stages in the index).


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

参见Junio C Hamano (gitster)的commit 6d37ca2(2020年11月11日)。 参见Elijah Newren (Newren) commit 89422d2, commit ef2b369, commit 70912f6, commit 6681ce5, commit 9fefce6, commit bb470f4, commit ee4012d, commit a9945bb, commit 8adffaa, commit 6a02dd9, commit 291f29c, commit 98bf984, commit 34e557a, commit 885f006, commit d2bc199, commit 0c0d705, commit c801717, commit e4171b1, commit 231e2dd, commit 5b59c3d(2020年12月13日)。 (由Junio C Hamano—gitster—在commit f9d29da中合并,2021年1月6日)

合并:添加record_conflicted_index_entries()的实现 署名:以利亚·纽伦

After checkout(), the working tree has the appropriate contents, and the index matches the working copy. That means that all unmodified and cleanly merged files have correct index entries, but conflicted entries need to be updated. We do this by looping over the conflicted entries, marking the existing index entry for the path with CE_REMOVE, adding new higher order staged for the path at the end of the index (ignoring normal index sort order), and then at the end of the loop removing the CE_REMOVED-marked cache entries and sorting the index.


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

参见Elijah Newren (Newren)的commit 6fcccbd, commit f1665e6, commit 35e47e3, commit 2e91ddd, commit 53e88a0, commit af1e56c(2020年12月15日)和commit c2d267d, commit 965a7bc, commit f39d05c, commit e1a124e, commit 864075e(2020年12月14日)。 (由Junio C Hamano—gitster—在commit 2856089中合并,2021年1月25日)

例子:

合并:添加正常重命名处理的实现 署名:以利亚·纽伦

Implement handling of normal renames. This code replaces the following from merge-recurisve.c: the code relevant to RENAME_NORMAL in process_renames() the RENAME_NORMAL case of process_entry() Also, there is some shared code from merge-recursive.c for multiple different rename cases which we will no longer need for this case (or other rename cases): handle_rename_normal() setup_rename_conflict_info() The consolidation of four separate codepaths into one is made possible by a change in design: process_renames() tweaks the conflict_info entries within opt->priv->paths such that process_entry() can then handle all the non-rename conflict types (directory/file, modify/delete, etc.) orthogonally. This means we're much less likely to miss special implementation of some kind of combination of conflict types (see commits brought in by 66c62ea ("Merge branch 'en/merge-tests'", 2020-11-18, Git v2.30.0-rc0 -- merge listed in batch #6), especially commit ef52778 ("merge tests: expect improved directory/file conflict handling in ort", 2020-10-26, Git v2.30.0-rc0 -- merge listed in batch #6) for more details). That, together with letting worktree/index updating be handled orthogonally in the merge_switch_to_result() function, dramatically simplifies the code for various special rename cases. (To be fair, the code for handling normal renames wasn't all that complicated beforehand, but it's still much simpler now.)

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

参见Elijah Newren (Newren)的commit 4ef88fc, commit 4204cd5, commit 70f19c7, commit c73cda7, commit f591c47, commit 62fdec1, commit 991bbdc, commit 5a1a1e8, commit 23366d2, commit 0ccfa4e (01 Jan 2021)。 (由Junio C Hamano—gitster—在commit b65b9ff中合并,2021年2月5日)

合并:添加处理不同类型的文件在同一路径 署名:以利亚·纽伦

Add some handling that explicitly considers collisions of the following types: file/submodule file/symlink submodule/symlink> Leaving them as conflicts at the same path are hard for users to resolve, so move one or both of them aside so that they each get their own path. Note that in the case of recursive handling (i.e. call_depth > 0), we can just use the merge base of the two merge bases as the merge result much like we do with modify/delete conflicts, binary files, conflicting submodule values, and so on.