我想知道'git merge'之后的精确算法(或附近的算法)。至少对以下子问题的答案将有所帮助:
- git如何检测特定的非冲突更改的上下文?
- git如何找出这些确切的行之间存在冲突?
- git自动合并哪些内容?
- 当没有合并分支的通用基础时,git如何执行?
- 当有多个合并分支的通用基础时,git如何执行?
- 一次合并多个分支会发生什么?
- 合并策略之间有什么区别?
但是整个算法的描述会更好。
我想知道'git merge'之后的精确算法(或附近的算法)。至少对以下子问题的答案将有所帮助:
但是整个算法的描述会更好。
Answers:
您可能最好去寻找3路合并算法的描述。一个高级描述将是这样的:
B
-文件版本是两个新版本(X
和Y
)的始祖版本,通常是最新的合并基础(尽管在某些情况下必须追溯到更早的版本,这是其中之一)git
s default的功能recursive
合并)X
与B
和Y
与B
。完整的算法对此进行了更详细的介绍,甚至有一些文档(https://github.com/git/git/blob/master/Documentation/technical/trivial-merge.txt以及其中的一些git help XXX
页面) ,其中XXX是一个merge-base
,merge-file
,merge
,merge-one-file
以及可能的其他一些)。如果还不够深入,那么总会有源代码...
当有多个合并分支的通用基础时,git如何执行?
本文非常有帮助:http : //codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html(这是第2部分))。
递归使用diff3递归生成一个虚拟分支,该分支将用作祖先。
例如:
(A)----(B)----(C)-----(F)
| | |
| | +---+
| | |
| +-------+
| | |
| +---+ |
| | |
+-----(D)-----(E)
然后:
git checkout E
git merge F
有2个最佳共同祖先(不是任何其他祖先的共同祖先),C
和D
。Git将它们合并到一个新的虚拟分支中V
,然后V
用作基础。
(A)----(B)----(C)--------(F)
| | |
| | +---+
| | |
| +----------+
| | | |
| +--(V) | |
| | | |
| +---+ | |
| | | |
| +------+ |
| | |
+-----(D)--------(E)
我想如果有更多最好的共同祖先,Git会继续与V
下一个合并。
文章说,如果在生成虚拟分支时存在合并冲突,则Git只会将冲突标记留在原处并继续。
一次合并多个分支会发生什么?
正如@Nevik Rehnel解释的那样,这取决于策略,这一man git-merge
MERGE STRATEGIES
节对此进行了很好的解释。
例如,仅octopus
和ours
/theirs
支持一次合并多个分支recursive
。
octopus
拒绝合并(如果存在冲突),并且ours
是微不足道的合并,因此不会有冲突。
这些命令生成一个新的提交将有两个以上的父级。
我做了一个 merge -X octopus
在Git 1.8.5上进行了一次无冲突的测试,以了解其进展。
初始状态:
+--B
|
A--+--C
|
+--D
行动:
git checkout B
git merge -Xoctopus C D
新状态:
+--B--+
| |
A--+--C--+--E
| |
+--D--+
不出所料 E
有3个父母。
TODO:章鱼如何在单个文件上进行修改。递归二乘二3路合并?
当没有合并分支的通用基础时,git如何执行?
@Torek提到自2.9起,合并失败而没有 --allow-unrelated-histories
。
我在Git 1.8.5上凭经验进行了尝试:
git init
printf 'a\nc\n' > a
git add .
git commit -m a
git checkout --orphan b
printf 'a\nb\nc\n' > a
git add .
git commit -m b
git merge master
a
包含:
a
<<<<<<< ours
b
=======
>>>>>>> theirs
c
然后:
git checkout --conflict=diff3 -- .
a
包含:
<<<<<<< ours
a
b
c
||||||| base
=======
a
c
>>>>>>> theirs
解释:
a\nc\n
作为单行添加e379fdf34fee96cd205be83ff4e71699bdc32b18
)开始,如果没有合并基础,除非您添加,否则Git现在拒绝合并--allow-unrelated-histories
。
--allow-unrelated-histories
如果要合并的分支之间没有公共文件路径,则可以省略。
我也很感兴趣 我不知道答案,但是...
总是发现一个有效的复杂系统是从一个有效的简单系统演变而来的
我认为git的合并非常复杂,将很难理解-但是解决该问题的一种方法是从其前身开始,并专注于您关注的核心。也就是说,给定两个没有共同祖先的文件,git merge如何计算出如何合并它们,以及冲突在哪里?
让我们尝试找到一些先驱。来自git help merge-file
:
git merge-file is designed to be a minimal clone of RCS merge; that is,
it implements all of RCS merge's functionality which is needed by
git(1).
维基百科:http://en.wikipedia.org/wiki/Git_%28software%29 - > http://en.wikipedia.org/wiki/Three-way_merge#Three-way_merge - > HTTP://en.wikipedia .org / wiki / Diff3- > http://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf
最后一个链接是diff3
详细描述该算法的论文的pdf 。这是google pdf查看器版本。它只有12页长,并且算法只有几页-而是完整的数学处理。这似乎有点过于正式,但是如果您想了解git的合并,则需要首先了解较简单的版本。我还没有检查过,但是名称类似diff3
,您可能还需要了解diff(使用最长的通用子序列算法)。但是,diff3
如果您有Google ,可能会有更直观的解释。
现在,我只是做了一个比较diff3
和的实验git merge-file
。他们采取同样的三个输入文件VERSION1 oldversion版本2和标记冲突同样的方式,用<<<<<<< version1
,=======
,>>>>>>> version2
(diff3
也有||||||| oldversion
),展示他们的共同遗产。
我为oldversion使用了一个空文件,为version1和version2使用了几乎相同的文件,仅向version2添加了一行。
结果:git merge-file
将单个更改的行标识为冲突;但diff3
将整个两个文件视为冲突。因此,与diff3一样,git的合并甚至在这种最简单的情况下也更加复杂。
这是实际结果(我使用@twalberg的答案作为文字)。请注意所需的选项(请参见相应的联机帮助页)。
$ git merge-file -p fun1.txt fun0.txt fun2.txt
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:
Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B. Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
<<<<<<< fun1.txt
=======
THIS IS A BIT DIFFERENT
>>>>>>> fun2.txt
The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
$ diff3 -m fun1.txt fun0.txt fun2.txt
<<<<<<< fun1.txt
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:
Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B. Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
||||||| fun0.txt
=======
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:
Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B. Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
THIS IS A BIT DIFFERENT
The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
>>>>>>> fun2.txt
如果您对此真的感兴趣,那就有点麻烦了。对我来说,它看起来像正则表达式一样深,是diff,上下文无关文法或关系代数中最长的常见子序列算法。如果您想深入浅出,我想可以,但是这需要一些坚定的研究。
这是原始的实现
http://git.kaarsemaker.net/git/blob/857f26d2f41e16170e48076758d974820af685ff/git-merge-recursive.py
基本上,您会为两个提交创建一个公共祖先列表,然后以递归方式合并它们,或者快速转发它们,或者创建虚拟提交以用于在文件上进行三向合并。
git如何检测特定的非冲突更改的上下文?
git如何找出这些确切的行中有冲突?
如果同一行在合并的两边都发生了更改,那就是冲突;如果没有,则接受一侧的更改(如果存在)。
git自动合并哪些内容?
不冲突的更改(请参见上文)
当有多个合并分支的通用基础时,git如何执行?
根据Git merge-base的定义,只有一个(最新的共同祖先)。
一次合并多个分支会发生什么?
这取决于合并策略(仅octopus
和和ours
/theirs
策略支持合并两个以上的分支)。
合并策略之间有什么区别?
git merge
联机帮助页对此进行了说明。
git-merge-recursive
存在?
git-merge-recursive
应该怎么做(没有手册页,谷歌什么也没有产生)。有关更多信息,请参见git merge
和git merge-base
手册页。
git-merge
手册页和git-merge-base
手册页讨论了多个公共祖先和递归合并。我认为您的答案没有讨论就不完整。