这里,没有难懂的知识;
只有最浅显的文字,
最直观的图示,
最鲜活的例子;
晴耕 · 白话,让知识更简单!
(点击文末阅读原文,查看本文在晴耕小筑网站上的完整内容)
多人开发中的合并冲突是我们使用Git时常常会遇到的情况,小小合并门道大,讲述合并的那些事儿,晴耕 · 白话之“Git合并那些事”系列持续连载中……

为什么没有Theirs策略?
前面提到Ours策略,也许大家会认为一定还有一个Theirs策略。Recursive策略里不就有-Xours和-Xtheirs参数吗?有意思的是,如果我们翻Git的文档,也许会觉得奇怪,居然没有Theirs策略!
事实上,Git在以前的版本里是有Theirs策略的,但后来它被去掉了。其实道理也很简单,因为它太危险了。正如Ours策略会毫不犹豫地丢弃被合并分支上的修改,Theirs策略也会毫不犹豫的丢弃当前分支上的修改。这相当于自己之前在当前分支上所做的工作全部丢掉了!
如果你真的想丢弃自己的修改,完全可以用其他git命令来代替。比如,还是继续前面的例子。先回退到上次合并前的提交记录c8:
$ git reset --hard HEAD^
HEAD is now at 306f03a c8
然后新建一个分支,用来备份我们在当前分支上所做的工作:
$ git branch i-was-stupid
此时,我们在master分支上的提交历史是这样的:
$ git log --oneline --graph
* 306f03a (HEAD -> master, i-was-stupid) c8
* 0935c24 c6
|\
| * 26dfc71 (feature2) c3
| |\
* | \ 1725ff2 c5
|\ \ \
| | |/
| |/|
| * | 39b06d9 c1
* | | c096f34 c4
| |/
|/|
* | ff8acc9 c2
|/
* 8937d6a c0
然后,我们执行git reset,指定:回退到feature1上head指针所指向的提交记录c7:
$ git reset --hard feature1
HEAD is now at 9232cf1 c7
再观察master分支的提交历史,会发现之前不在feature1分支上出现的提交记录c3,c6,c8都不见了:
$ git log --oneline --graph
* 9232cf1 (HEAD -> master, feature1) c7
* 1725ff2 c5
|\
| * 39b06d9 c1
* | c096f34 c4
* | ff8acc9 c2
|/
* 8937d6a c0
master分支上余下的提交历史和feature1的提交历史是完全一样的:
$ git checkout feature1
Switched to branch 'feature1'
$ git log --oneline --graph
* 9232cf1 (HEAD -> feature1, master) c7
* 1725ff2 c5
|\
| * 39b06d9 c1
* | c096f34 c4
* | ff8acc9 c2
|/
* 8937d6a c0
Octopus策略
前面我们介绍的Recursive策略和Resolve策略都是针对两个分支的合并。假如我们要合并的分支超过两个,那该怎么办呢?这个时候,我们依然可以使用Recursive策略,对分支进行两两合并。但是,这样做每合并一次就会产生一个新的合并提交(merge commit)。过多的合并提交出现在提交历史里,会成为一种“杂音”,对提交历史造成不必要的“污染”,让它变得更加复杂,更难看懂。
这个时候,Octopus策略就派上用场了。Git在对两个以上的分支进行合并时,会自动选择Octopus策略。它的主要特点在于,只会生成一个合并提交,从而最大限度地减少了因为合并对提交历史造成的“污染”。因为按照这种合并策略得到的提交历史形似章鱼,所以名字还是起的很形象的。
下面我们就通过实际例子来感受一下。首先,我们新建一个本地Git库,叫做test-octopus-merge:
$ git init test-octopus-merge
Initialized empty Git repository in /root/test-octopus-merge/.git/
$ cd test-octopus-merge
在master分支上新建README文件:
$ vi README
$ cat README
Merge strategies include:
* Resolve
* Octopus
* ...
并建立提交记录c0:
$ git add .
$ git commit -m c0
[master (root-commit) 849aa5b] c0
1 file changed, 4 insertions(+)
create mode 100644 README
然后新建两个分支,feature1和feature2:
$ git checkout -b feature1
Switched to a new branch 'feature1'
$ git checkout -b feature2
Switched to a new branch 'feature2'
并且,分别在分支feature2上建立提交记录c1:
$ vi README
$ cat README
Merge strategies include:
* Recursive
* Resolve
* Octopus
* ...
$ git commit -am c1
[feature2 c60b6e8] c1
1 file changed, 1 insertion(+)
在feature1上建立提交记录c2:
$ git checkout feature1
Switched to branch 'feature1'
$ vi README
$ cat README
Merge strategies include:
* Resolve
* Ours
* Octopus
* ...
$ git commit -am c2
[feature1 79cc879] c2
1 file changed, 1 insertion(+)
在master上建立提交记录c3:
$ git checkout master
Switched to branch 'master'
$ vi README
$ cat README
Merge strategies include:
* Resolve
* Octopus
* Subtree
* ...
$ git commit -am c3
[master 3d795fb] c3
1 file changed, 1 insertion(+)
最后,执行git merge,把feature1和feature2一次性合并到master:
$ git merge -m c4 feature1 feature2
Trying simple merge with feature1
Simple merge did not work, trying automatic merge.
Auto-merging README
Trying simple merge with feature2
Simple merge did not work, trying automatic merge.
Auto-merging README
Merge made by the 'octopus' strategy.
README | 2 ++
1 file changed, 2 insertions(+)
从输出结果中可以看到,Git的确在合并时采用了Octopus策略。这个时候,再用git log查看提交历史,会发现分支feature1和feature2在向master合并时只生成了一个提交记录,即c4:
$ git log --graph --oneline --all
*-. d2780ca (HEAD -> master) c4
|\ \
| | * c60b6e8 (feature2) c1
| * | 79cc879 (feature1) c2
| |/
* | 3d795fb c3
|/
* 849aa5b c0
如图所示,c4就是c1,c2,c3在分支合并后产生的合并提交:

如果要是采用Recursive策略会怎么样呢,让我们先退回到提交记录c3:
$ git reset --hard HEAD^
HEAD is now at 3d795fb c3
然后,在master分支上逐个合并feature1和feature2:
$ git merge -m c4 feature1
Auto-merging README
Merge made by the 'recursive' strategy.
README | 1 +
1 file changed, 1 insertion(+)
$ git merge -m c5 feature2
Auto-merging README
Merge made by the 'recursive' strategy.
README | 1 +
1 file changed, 1 insertion(+)
从输出结果中可以看到,这次Git使用了Recursive策略。这个时候,我们观察提交历史就会发现,每合并一次都会产生一个新的提交记录:
$ git log --graph --oneline --all
* 5544665 (HEAD -> master) c5
|\
| * c60b6e8 (feature2) c1
* | dfef4b0 c4
|\ \
| * | 79cc879 (feature1) c2
| |/
* | 3d795fb c3
|/
* 849aa5b c0
其中,c4是在合并feature1时产生的,c5是在合并feature2时产生:

最后,关于Octopus策略还有一点需要说明:尽管它功能十分强大,但实际使用的机会却并不多。可以想像,如果一下子要合并五六个分支,那是一种什么样的感觉。平时我们遇到最多的,还是两个分支合并的情况。
Subtree策略
(欲查看本段内容,请点击文末阅读原文)
(完)




