在Git中,原则上使用merge或者rebase都可以解决分支冲突的问题,比如:1
2
3 D (dev)
/
A---B---C (master)
当master和dev存在冲突时,在dev上进行git merge master
或者git rebase master
都可以解决冲突。这两种方式的作用机制不同,结果也不一样。
使用merge
当使用merge时,git merge master
会首先把master上的commit都搬进dev里,然后创建一个新的commit(M
)以记录如何化解冲突:1
2
3 D---M (dev)
/ /
A---B---C (master)
M
的特点是具有两个parent。注意这时的dev分支就包括了master上的所有信息,包括commit message。这可能并不是一件好事,比如如果有人在commit message里@了某人或者某issue,那当你把处理了冲突的分支push上去的时候就又@了一遍。
当解决了dev和master的冲突以后,把dev合并进master就可以fast-forward了:1
2
3 D---M (dev, master)
/ /
A---B---C
使用rebase
使用rebase
时,D
会直接被接到C
上变成D'
:1
2
3 D D' (dev)
/ /
A---B---C (master)
这时从master去合并dev分支也可以fast-forward,最后的结果看起来会更漂亮一些,因为通过修改历史的方式曾经的冲突都被隐藏了起来,不存在有两个parent的commit:1
2
3 D D' (dev, master)
/ /
A---B---C
如果考虑到最后Git会把D
这个commit垃圾回收,最终的状态是:1
A---B---C---D'(dev, master)
可以说是非常理想了。因此在比较简单的冲突的情形下,使用rebase是更好的方案,这种方案下master的历史非常清晰,易于管理。
复杂情况最好用merge
但是当分支冲突情况比较复杂时,rebase并不能妥善地解决冲突问题,而应该使用merge,比如下面这种情况,dev1和master有冲突,dev2基于dev1开发,与master也有冲突:1
2
3
4
5 F (dev2)
/
D (dev1)
/
A---B---C (master)
在这种情形下,假如将dev1分支rebase到master上,得到的状态是:1
2
3
4
5
6
7 F (dev2)
/
D
/
A---B---C (master)
\
D' (dev1)
其中D'
是rebase之后的D
。这时如果我们只关心dev1和master之间的冲突,那么问题已经解决了。然而比较让人头疼的是dev2和master之间的冲突问题并没有解决,如果要将dev2和master合并,就需要把dev1和master之间的冲突再重新处理一遍,这显然是低效的。
如果使用merge方案,dev1分支和master分支合并后的状态是:1
2
3
4
5 F (dev2)
/
D---M (dev1)
/ /
A---B---C (master)
这时直接将dev2和master合并起来还是会有冲突,但是dev2可以直接rebase到dev1上:1
2
3
4
5 F F' (dev2)
/ /
D---M (dev1)
/ /
A---B---C (master)
通过这种方式,dev2可以重用dev1处理与master冲突的commit。这时再将dev2合并进master就可以fast-forward:1
2
3
4
5 F F' (dev2, master)
/ /
D---M (dev1)
/ /
A---B---C
总结
Git中的rebase和merge命令都可以用于解决冲突。对于简单的情形,比如冲突分支只由一人开发是线性分支,出于简化master历史的考虑应该优先使用rebase。而当冲突比较复杂时,则应该回避修改历史,使用merge化解冲突。