Git解决分支冲突的两种方案:merge和rebase

在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化解冲突。