合并git并行分支中的冲突

时间:2017-12-27 16:06:32

标签: git merge parallel-processing branch

我试图在git中实现一个场景。 我从一个带有四行的文本文件开始,然后制作了4个分支,每个分支在文本文件中更改了一行,它们并行工作,每个分支都有原始文件的副本,如图所示。 enter image description here 当我合并分支时,第一个合并总是这样成功:

Updating 0b18c93..274ba8c
Fast-forward
 t.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

但是我在后来的合并中遇到了合并冲突。

Automatic merge failed; fix conflicts and then commit the result.

是否有方法可以实现此方案而不会发生合并冲突?

5 个答案:

答案 0 :(得分:5)

要添加到jthill's answer(这是正确的,我已经赞成它):您需要意识到您的第一次合并根本不是合并。这就是Git说的原因:

Fast-forward

合并过程,正如jthill所说:

  

找到(在某些情况下Git的合并将实际构建)公共基数,并将该基数的差异与每个分支提示进行比较。重叠范围的任何差异都构成冲突。

但是对于第一个git merge命令,公共基础分支提示之一。

第一个是免费的

让我们看看它是怎么回事:

$ mkdir tt; cd tt; git init
Initialized empty Git repository in ...
$ cat << END > myfile.txt
01
02
03
04
END
$ git add myfile.txt
$ git commit -m initial
[master (root-commit) b1a22ca] initial
 1 file changed, 4 insertions(+)
 create mode 100644 myfile.txt
$ git checkout -b br1
Switched to a new branch 'br1'
$ ed myfile.txt
12
1s/$/ one/
w
16
q
$ git add myfile.txt
$ git commit -m 'change line 1'
[br1 b31f04a] change line 1
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --all --decorate --oneline --graph
* b31f04a (HEAD -> br1) change line 1
* b1a22ca (master) initial

(如果您不熟悉ed编辑器,它是一个相当小的纯文本编辑器,它在打开时打印输入文件中的字节数,并具有以下形式的命令: em> line-number(s) operation 。所以1s/$/ one/表示“用 one替换第1行的结尾。w命令写入文件返回,q退出编辑器。)

我们还没有创建额外的分支br2br3br4,但是让我们继续这样做,然后创建它们以便它们指向提交{ {1}}:

b1a22ca

此时,我们有一个看起来像这样的图表,如果我们水平绘制它而不是像Git那样垂直绘制:

$ git checkout master
Switched to branch 'master'
$ git branch br2 && git branch br3 && git branch br4
$ git log --all --decorate --oneline --graph
* b31f04a (br1) change line 1
* b1a22ca (HEAD -> master, br4, br3, br2) initial

也就是说,分支名称b1a22ca <-- master (HEAD), br2, br3, br4 \ b31f04a <-- br1 表示其提示提交为br1,而分支名称b31f04a和其他三个master都表示他们的提示提交是br

现在让我们继续在br2上创建一个新的提交:

b1a22ca

并了解$ git checkout br2 Switched to branch 'br2' $ ed myfile.txt 12 1,$p 01 02 03 04 2s/$/ two/ w 16 q $ git add myfile.txt $ git commit -m 'change line 2' [br2 805ea58] change line 2 1 file changed, 1 insertion(+), 1 deletion(-) 如何绘制它:

git log --all --decorate --oneline --graph

在我首选的水平图表绘图中 - 新的提交是在右边,而不是在上面 - 我们有:

$ git log --all --decorate --oneline --graph
* 805ea58 (HEAD -> br2) change line 2
| * b31f04a (br1) change line 1
|/  
* b1a22ca (master, br4, br3) initial

如果我们现在运行 805ea58 <-- br2 (HEAD) / b1a22ca <-- master, br3, br4 \ b31f04a <-- br1 ,Git将为我们找到合并基础提交。这是两个分支git checkout master && git merge br1 br1上的“最近”提交。

请注意,此时,提交master仅在b31f04a上,提交br1仅在805ea58上,但提交{{} 1}}在每个分支上。 这是Git中的一个关键事项:分支“包含”提交,任何给定的提交可以同时在许多分支上。

找到合并基础后,Git现在必须结合两组更改:

  • 从合并基础br2b1a22ca的提示的更改:b1a22ca;
  • 从合并基础masterb1a22ca的提示的更改:b1a22ca

br1 b31f04a。这里不可能有任何变化!

对于这种情况,Git默认做的是执行快进。快进不是合并!它只相当于切换到另一个提交,在本例中为b1a22ca,并将名称b1a22ca向前拖动到指向该提交:

b31f04a

在此操作期间,不添加新的提交:唯一改变的是您的当前提交现在是master而不是{{1} },并且分支标签 805ea58 <-- br2 / b1a22ca <-- br3, br4 \ b31f04a <-- master (HEAD), br1 指向b31f04a。 (单词b1a22camaster一起移动,因为b31f04a已“附加到”HEAD。Git在master输出中将其显示为HEAD 。)

如果您愿意,可以改为运行master。这迫使Git进行真正的合并。但是,当Git将HEAD -> master与自身进行比较时,仍然没有找到任何更改,因此没有合并冲突。如果你这样做,你会得到一个新的提交,有两个父母。你没有,所以我只是快速前进:

git log

第二次合并是冲突的

但是,当我们进入第二次合并时,情况确实不同。这次,git merge --no-ff br1(现在b1a22ca)和$ git checkout master Switched to branch 'master' $ git merge br1 Updating b1a22ca..b31f04a Fast-forward myfile.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) $ git log --all --decorate --oneline --graph * 805ea58 (br2) change line 2 | * b31f04a (HEAD -> master, br1) change line 1 |/ * b1a22ca (br4, br3) initial master)之间共享的第一个提交是b31f04a。所以Git会运行:

br2

然后Git将运行:

805ea58

Git现在转到合并这两个变化。但是在Git的眼中,两个变化重叠:第一个接触第1行,第二个接触第2行,第1行和第2行相互接触:这是重叠。这两个更改不是相同,因此它们不能折叠为更改两行的单个更改。因此,更改是合并冲突,这是您所看到的。

一旦分支b1a22ca$ git diff --find-renames b1a22ca b31f04a # what happened on master diff --git a/myfile.txt b/myfile.txt index cb3ff6d..8bc2250 100644 --- a/myfile.txt +++ b/myfile.txt @@ -1,4 +1,4 @@ -01 +01 one 02 03 04 有适当的提示提交,其余合并也会发生同样的事情。

答案 1 :(得分:5)

根据您在评论中提出的问题,这里有一些补充说明。

首先你有一个起始状态,每行只有数字;我们会调用该提交A。这就是master指向的地方。

A <--(master)
            ^HEAD

然后你有四个分支。每个分支都会更改不同的代码行。我们将HEAD保留在master以准备合并。

A <-(master)
|\         ^HEAD
| B <-(branch1)
|\
| C <-(branch2)
|\
| D <-(branch3)
 \
  E <-(branch4)

所以你做第一次合并

git merge branch1

现在git将寻找&#34;你正在合并的&#34;之间的合并基础。 (branch1 = B)和HEADmaster = A)。合并基础通常(在这种情况下)只是从双方可以到达的最近提交(即来自masterbranch1)。当然A可以从master到达,A也可以从branch1到达,因为AB的父级。

所以我们确定了3个版本。 &#34;我们&#34; (我们将更改合并到的内容)是A。 &#34;他们&#34; (正在合并更改的地方)是B。 &#34;基础&#34; (&#34;我们的&#34;和#34;他们的&#34;)之间的合并基础是A,恰好与&#34;我们的&#34;相同。

如果我们完全合并,下一步就是比较我们的&#34;到&#34;基地&#34;并比较他们的#34;到&#34;基地&#34;创建两个补丁。然后我们组合这些补丁。只要这些补丁不会影响相同的代码,我们就可以将它们简单地结合起来(&#34;我们将大块A从xxx改为yyy;他们将大块B从zzz改为www;所以合并的补丁确实如此这些都是&#34;)。这大概是非冲突合并的工作方式。在这种情况下,它非常简单;因为&#34;我们的&#34;和&#34; base&#34;是一回事,&#34;我们没有改变任何事情&#34 ;;所以合并的补丁等于&#34; base&#34;之间的补丁。和他们的&#34;。

事实上,默认情况下git在这里采用了一个快捷方式。一旦它意识到&#34;我们的&#34;他们是&#34;他们的祖先&#34;,它知道它可以做一个&#34;快进&#34;而不是真正的合并。它可以将master移动到branch1所在的位置,而不会合并任何内容。 (您可以通过向--no-ff命令提供merge选项来防止这种情况。)

但重点是,即使没有快进,这也不会发生冲突,因为根据定义的冲突意味着&#34;我们&#34;改变了相同的代码块#34;他们&#34;改变,在这种情况下&#34;我们&#34;并没有改变任何事情。

所以现在快进后你已经

A -- B <-(branch1)(master)
|\                       ^HEAD
| C <-(branch2)
|\
| D <-(branch3)
 \
  E <-(branch4)

请注意master 不再是 CDE的祖先;第一个合并将master移动到其他分支无法访问的提交。真正的合并也是如此;在这种情况下,master将转移到新的&#34;合并提交&#34;。由于快进,它只是移动到B,但效果是一样的。

所以现在你说

git merge branch2

当我们寻找&#34;我们的&#34;之间的合并基础时(master = B)和&#34;他们的&#34; (branch2 = C)我们会找到A。所以基数是A,但这次&#34;我们的变化&#34;是&#34;第1行是从&#39; 01&#39;到&#39; 01一个&#39;&#34;。 &#34;他们的变化&#34; &#34;第2行从&#39; 02&#39;到了&#39; 02两个&#39;&#34;。

由于不满足快进条件,我们必须使用这两个补丁进行完全合并。你可能会认为&#34;我可以在没有冲突的情况下组合这些补丁&#34;导致&#34;第1行从&#39; 01&#39;到了&#39; 01一个&#39; 01和第2行改变了frmo&#39; 02&#39;到了&#39; 02两个&#39;&#34;。如果补丁严格逐行比较,那就是真的;但他们不是。

他们没有理由。 git的目的是维护程序源代码的版本。可能性是第1行的代码与第2行的代码有关。如果变化没有相应的距离(不,我不知道距离是什么[1]),它们是被认为会影响同一个大块头。

因此,不要假设这些更改是独立正确的并且不会互相干扰,git会将其标记为冲突,并要求您决定最终结果应该是什么。即使你不得不不必要地解决100个这样的冲突,成本仍然低于git假设它知道该做什么和错误的一个实例。

第3次和第4次合并的分析将是相同的;在每种情况下,您有两个影响同一块代码的补丁,因此需要手动干预。

现在,如果您碰巧是源控制某些特殊类型的文件,您对此非常有信心(实际上,您可能必须绝对确定),对一行的更改与更改无关到下一行,然后你可以编写自己的合并驱动程序。它不是微不足道的,请记住,当添加行时,删除行时,当文件的两个版本偏离您在您的文件中显示的偏差时,您必须知道该怎么做例如,等等。

接受有时你必须解决冲突的可能性更大。如果你想要写作&#34;玩具&#34;模拟非冲突合并的测试,最简单的方法是让每个分支更改不同的文件;但是在一个文件中,变化不能太紧密,否则冲突就会发生。

[1] 评论更新 - 根据torek的说法,即使只有一条中间线也没有人改变就足够了。我并不认为这是正确的,但作为一项规则,如果托雷克告诉我一些我认为错误的事情,我会进行另一次测试;根据那个测试,我会同意他的观点。事实上,似乎你可以合并branch1branch3而不会发生冲突,例如。

答案 2 :(得分:4)

  

是否有方法可以实现此方案而不会发生合并冲突?

tl;博士:没有。

合并发现(在某些情况下Git的合并将实际构建)公共基础并将该基础的差异与每个分支提示进行比较。重叠范围变化的任何差异都构成冲突。

没有人能找到一种自动解决这些重叠的方法。我们已经有大量的现有历史可供使用,因此您可以尝试在linux或vim或libreoffice或git本身的合并中提出的任何方法,或者你有什么,这不是第一次大家曾经看过一个问题的人错过了什么,但我对“无法做到”的答案打了很多。

不良自动注射的成本非常高,这是疼痛/增益指标的“痛苦”。收益只是方便:许多冲突与你的冲突一样,人类很容易正确解决。使用工具获得好处,简单的案例需要几秒钟。所以Git非常谨慎。

答案 3 :(得分:1)

我发现有用的是分支1&amp; 2被合并到master,checkout到分支3并从master拉出并解决冲突然后推送到master,然后分支4可以从master中拉出所有已解决的冲突并合并然后push to master。

答案 4 :(得分:0)

  

是否有方法可以实现此方案而不会发生合并冲突?

否(如其他答案中所述),但可以使用正确的工具自动解决这些问题。

以下内容基于Torek's answer中的所有命令。

$ git merge br2
Auto-merging myfile.txt
CONFLICT (content): Merge conflict in myfile.txt
Automatic merge failed; fix conflicts and then commit the result.
$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both modified:   myfile.txt

no changes added to commit (use "git add" and/or "git commit -a")
$ git ls-files -u
100644 cb3ff6d73dab0ac586b87f6c5f222e37b85dd32d 1       myfile.txt
100644 8bc2250b70fd06d951fd6ae1ea7ebf0b421ce2e3 2       myfile.txt
100644 8bfe14645e97d71dd4036b5a51d73e29020cba55 3       myfile.txt
$

因此合并br2会导致冲突(您可以检查与ls-files相关的版本。)

Git的内部合并处理在彼此相邻的两条线被修改时失败,而如果使用KDiff3进行合并而不是它(这通常是你想要的但是风险更高)这是不正确的线,因此有一个权衡)。

使用mergeto命令使用KDiff3很简单;

$ git config merge.tool kdiff3
$ git mergetool
Merging:
myfile.txt

Normal merge conflict for 'myfile.txt':
  {local}: modified file
  {remote}: modified file
$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

        modified:   myfile.txt

Untracked files:
$ ..." to include in what will be committed)

        myfile.txt.orig

$ rm myfile.txt.orig
$ git diff --cached
diff --git a/myfile.txt b/myfile.txt
index 8bc2250..73e9fe0 100644
--- a/myfile.txt
+++ b/myfile.txt
@@ -1,4 +1,4 @@
 01 one
-02
+02 two
 03
 04
$ 

KDiff3是一个graphical 3-way merge程序,但在这种情况下,当它自动解决问题时,它没有显示窗口。如果存在必须手动解决的冲突,则会有。