要添加到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
编辑器,它是一个相当简单的纯文本编辑器,在启动时会打印输入文件中的字节数,并具有以下形式的命令:行号 操作。所以1s/$/ one/
的意思是“用 one
替换第1 行的结尾。w
命令将文件写回,q
退出编辑器。)
我们还没有必要创建额外的分支br2
、br3
和br4
,但我们还是继续这样做,创建它们以便它们指向提交b1a22ca
:
$ 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
此时,如果我们像 Git 那样水平地而不是垂直地绘制它,我们有一个看起来像这样的图表:
b1a22ca <-- master (HEAD), br2, br3, br4
\
b31f04a <-- br1
即分支名br1
表示它的tip commit是b31f04a
,而分支名master
和其他三个br
s都表示他们的tip commit是b1a22ca
。
现在让我们在 br2 上创建一个新的提交:
$ 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 checkout master && git merge br1
,Git 将为我们定位 merge base 提交。这是 both 分支 br1
和 master
上的“最近”提交。
请注意,此时,提交b31f04a
仅在br1
上,而提交805ea58
仅在br2
上,但提交b1a22ca
在每个分支上。 em> 这是 Git 中的关键:分支“包含”提交,任何给定的提交都可以同时在多个分支上。
找到合并基础后,Git 现在必须合并两组更改:
- 合并基
b1a22ca
到master
的尖端的变化:b1a22ca
;
- 从合并基础
b1a22ca
到br1
的尖端的变化:b31f04a
。
但是b1a22ca
是 b1a22ca
。这里不可能有任何变化!
在这种情况下,Git 默认做的是快进。快进根本不是合并!这相当于切换到另一个提交,在本例中为 b31f04a
,并将名称 master
向前拖动以指向该提交:
805ea58 <-- br2
/
b1a22ca <-- br3, br4
\
b31f04a <-- master (HEAD), br1
在此操作期间没有添加任何新提交:唯一改变的是您的当前提交现在是b31f04a
而不是b1a22ca
,并且分支标签master
指向b31f04a
。 (HEAD
这个词与 master
一起移动,因为 HEAD
被“附加到”master
。Git 在 git log
输出中将其显示为 HEAD -> master
。)
如果您愿意,可以运行 git merge --no-ff br1
。这迫使 Git 进行真正的合并。但是,当 Git 将 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
第二次合并冲突
但是,当我们进行 second 合并时,情况确实有所不同。这一次,master
(现在是b31f04a
)和br2
(805ea58
)之间共享的第一个提交是b1a22ca
。所以 Git 会运行:
$ 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
然后 Git 将运行:
$ git diff --find-renames b1a22ca 805ea58 # what happened on br2
diff --git a/myfile.txt b/myfile.txt
index cb3ff6d..8bfe146 100644
--- a/myfile.txt
+++ b/myfile.txt
@@ -1,4 +1,4 @@
01
-02
+02 two
03
04
Git 现在去结合这两个变化。但在 Git 眼里,这两个变化是重叠的:第一个接触到第 1 行,第二个接触到第 2 行,第 1 行和第 2 行相互接触:这是一个重叠。这两个更改相同,因此不能将它们折叠为更改两条线的单个更改。因此,更改是合并冲突,这就是您所看到的。
一旦分支 br3
和 br4
有适当的提示提交,其余的合并也会发生同样的事情。