“ git merge”如何详细工作?


93

我想知道'git merge'之后的精确算法(或附近的算法)。至少对以下子问题的答案将有所帮助:

  • git如何检测特定的非冲突更改的上下文?
  • git如何找出这些确切的行之间存在冲突?
  • git自动合并哪些内容?
  • 当没有合并分支的通用基础时,git如何执行?
  • 当有多个合并分支的通用基础时,git如何执行?
  • 一次合并多个分支会发生什么?
  • 合并策略之间有什么区别?

但是整个算法的描述会更好。


8
我想您可以用这些答案填满整本书……
Daniel Hilgarth 2013年

2
或者,您也可以直接去阅读代码,这将花费大约“描述整个算法”的时间
Nevik Rehnel

3
@DanielHilgarth我很高兴发现,如果某处已经有这样的书。欢迎参考。
深渊。

5
@NevikRehnel是的,我可以。但是,如果有人已经知道此代码背后的理论,它就会变得容易得多。
深渊。

1.什么是“特定的非冲突性变更的背景”?第2点和第3点相同,但取反,让我们合并这两个问题?
西罗Santilli郝海东冠状病六四事件法轮功

Answers:


65

您可能最好去寻找3路合并算法的描述。一个高级描述将是这样的:

  1. 查找合适的合并基础B-文件版本是两个新版本(XY)的始祖版本,通常是最新的合并基础(尽管在某些情况下必须追溯到更早的版本,这是其中之一)gits default的功能recursive合并)
  2. 执行的diffXBYB
  3. 遍历两个差异中标识的变更块。如果双方在同一地点提出相同的更改,则接受其中一个;如果一个人引入了变化,而另一个人离开了该区域,则在决赛中引入该变化;如果两者都引起了现场变化,但又不匹配,则将冲突标记为手动解决。

完整的算法对此进行了更详细的介绍,甚至有一些文档(https://github.com/git/git/blob/master/Documentation/technical/trivial-merge.txt以及其中的一些git help XXX页面) ,其中XXX是一个merge-basemerge-filemergemerge-one-file以及可能的其他一些)。如果还不够深入,那么总会有源代码...


11

当有多个合并分支的通用基础时,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个最佳共同祖先(不是任何其他祖先的共同祖先),CD。Git将它们合并到一个新的虚拟分支中V,然后V用作基础。

(A)----(B)----(C)--------(F)
        |      |          |
        |      |      +---+
        |      |      |
        |      +----------+
        |      |      |   |
        |      +--(V) |   |
        |          |  |   |
        |      +---+  |   |
        |      |      |   |
        |      +------+   |
        |      |          |
        +-----(D)--------(E)

我想如果有更多最好的共同祖先,Git会继续与V下一个合并。

文章说,如果在生成虚拟分支时存在合并冲突,则Git只会将冲突标记留在原处并继续。

一次合并多个分支会发生什么?

正如@Nevik Rehnel解释的那样,这取决于策略,这一man git-merge MERGE STRATEGIES节对此进行了很好的解释。

例如,仅octopusours/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作为单行添加
  • 认为没有基础文件的3向合并称为2向合并,这只是一个差异

1
这个问题有一个新的SO链接,所以我浏览了这个答案(非常好),并且注意到最近的Git更改已经过了最后一部分。从Git 2.9版(提交e379fdf34fee96cd205be83ff4e71699bdc32b18)开始,如果没有合并基础,除非您添加,否则Git现在拒绝合并--allow-unrelated-histories
torek

1
下面是从一个@Ciro贴在后续的文章:blog.plasticscm.com/2012/01/...
adam0101

除非自上次尝试以来行为没有改变,否则:--allow-unrelated-histories如果要合并的分支之间没有公共文件路径,则可以省略。
杰里米·

小修正:有ours合并策略,但没有theirs合并策略。recursive+theirs策略只能解决两个分支。git-scm.com/docs/git-merge#_merge_strategies
nekketsuuu

9

我也很感兴趣 我不知道答案,但是...

总是发现一个有效的复杂系统是从一个有效的简单系统演变而来的

我认为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=======>>>>>>> version2diff3也有||||||| oldversion),展示他们的共同遗产。

我为oldversion使用了一个空文件,为version1version2使用了几乎相同的文件,仅向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,上下文无关文法或关系代数中最长的常见子序列算法。如果您想深入浅出,我想可以,但是这需要一些坚定的研究。



0

git如何检测特定的非冲突更改的上下文?
git如何找出这些确切的行中有冲突?

如果同一行在合并的两边都发生了更改,那就是冲突;如果没有,则接受一侧的更改(如果存在)。

git自动合并哪些内容?

不冲突的更改(请参见上文)

当有多个合并分支的通用基础时,git如何执行?

根据Git merge-base的定义,只有一个(最新的共同祖先)。

一次合并多个分支会发生什么?

这取决于合并策略(仅octopus和和ours/theirs策略支持合并两个以上的分支)。

合并策略之间有什么区别?

git merge联机帮助页对此进行了说明。


2
“同一条线”是什么意思?如果我在其他两个之间插入新的非空行并合并-哪些行是相同的?如果我删除一个分支中的某些行,那么另一分支中的“相同”行是什么?
深渊。

1
用文本回答这个问题有些棘手。Git使用[diffs](en.wikipedia.org/wiki/Diff)表示两个文件(或文件的两个修订版)之间的差异。它可以通过比较上下文来检测是否添加或删除了行(默认情况下为三行)。然后,“同一行”是指上下文,同时牢记添加和删除。
Nevik Rehnel

1
您建议更改“同一行”将表明存在冲突。自动合并引擎真的基于行吗?还是基于大块头?只有一个共同的祖先吗?如果是这样,为什么git-merge-recursive存在?
爱德华·汤姆森

1
@EdwardThomson:是的,分辨率是基于行的(可以将大块分解为较小的块,直到只剩下一行)。默认的合并策略使用最新的共同祖先作为参考,但是如果您要使用其他东西,可以使用其他合并策略。而且我不知道git-merge-recursive应该怎么做(没有手册页,谷歌什么也没有产生)。有关更多信息,请参见git mergegit merge-base手册页。
Nevik Rehnel

1
您指出的git-merge手册页和git-merge-base手册页讨论了多个公共祖先和递归合并。我认为您的答案没有讨论就不完整。
爱德华·汤姆森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.