暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

晴耕 · 白话之Git合并那些事—合并策略(下)

晴耕小筑 2019-09-04
1809


这里,没有难懂的知识;

只有最浅显的文字,

最直观的图示,

最鲜活的例子;

晴耕 · 白话,让知识更简单!


(点击文末阅读原文,查看本文在晴耕小筑网站上的完整内容)


多人开发中的合并冲突是我们使用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策略


(欲查看本段内容击文末阅读原文


(完)


文章转载自晴耕小筑,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论