前言
在 Git 中整合来自不同分支的修改主要有两种方法:merge
以及 rebase
。
其中merge
是一个三方(分支master最新快照,分支dev最新快照,分支master和dev的共同祖先快照)合并的方式来整合修改,并且整合之后会生成新的提交快照。
merge
之前的三方(c4
,c5
及共同祖先c2
):
merge
之后产生新的提交快照(c6
)
merge
之后,iss53
分支的c3
和c5
提交快照消失了。
为了避免提交快照因为合并而移除的问题,发明了rebase
方法来整合不同分支的修改。
更多
merge
的用法请参考分支的新建与合并。
变基的原理
假设日常开发中出现了如下图所示的场景:分支experiment
和分支master
分别在共同祖先C2
上生成了提交快照C4
和C3
。
首先找到这两个分支(即当前分支 experiment
、变基操作的目标基底分支master
) 的最近共同祖先 C2
,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底 C3
, 最后以此将之前另存为临时文件的修改依序应用。
此时,只需要在目标基底分支master
上和合并rebase
过的experiment
分支即可。
$ git checkout master
$ git merge experiment
这样,两个分支的提交快照都保留了下来,效果如下:
更复杂的变基
在对两个分支进行变基时,所生成的“重放”并不一定要在目标分支(下文的server
分支)上应用,你也可以指定另外的一个分支(下文的master
分支)进行应用。 你创建了一个主题分支server
,为服务端添加了一些功能,提交了C3
和C4
。 然后从C3
上创建了主题分支client
,为客户端添加了一些功能,提交了C8
和C9
。 最后,你回到server
分支,又提交了C10
。
整个项目的提交状态如下:
master
分支和server
分支的共同祖先是C2
,server
分支和client
分支的共同祖先的C3
。
假设你希望将client
中的修改合并到主分支并发布,但暂时并不想合并server
中的修改, 因为它们还需要经过更全面的测试。这时,你就可以使用 git rebase 命令的 --onto
选项, 选中在client
分支里但不在server
分支里的修改(即C8
和 C9
,也就是server
和client
共同祖先C3
之后的修改),将它们在master
分支上重放:
git rebase --onto master server client
--onto
参数指定的分支是最后要变基的基底分支,这里是master
,而server
用于确定client
分支要基于的共同祖先C3
之后的提交快照。
以上命令的意思是:“取出client
分支,找出它从server
分支分歧之后的补丁, 然后把这些补丁在master
分支上重放一遍,让 client 看起来像直接基于master
修改一样”。这理解起来有一点复杂,不过效果非常酷。
使用--onto
参数,client
分支需要重播的提交是C3
之后的提交,而不是C2
之后的提交。这样,用于确定共同祖先的分支(server
)和最终要变基的基底分支(master
)是独立的。
最终rebase
后的效果如下:
同样,此时要在目标基底分支master
合并client
分支来整合C8
和C9
提交:
$ git checkout master
$ git merge client
接下来你决定将server
分支中的修改也整合进来。 使用 git rebase <basebranch> <topicbranch>
命令可以直接将主题分支 (即本例中的server
)变基到目标分支(即master
)上。 这样做能省去你先切换到server
分支,再对其执行变基命令的多个步骤。
$ git rebase master server
然后就可以快进合并主分支master
了:
$ git checkout master
$ git merge server
至此,client
和server
分支中的修改都已经整合到主分支里了, 你可以删除这两个分支,最终提交历史会变成上图中的样子:
$ git branch -d client
$ git branch -d server
使用变基的原则
如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。
简单的说就是只在自己的分支变基,然后在公共分支上合并变基后的提交记录。
本地变基后推送到远程
在本地分支变基(可能要解决冲突)后,本地分支和远程分支会变得不一样,特别是有冲突的时候,推送上去会提示要merge
还是rebase
。如果远程的分支只有自己修改,可以使用git push --force
让本地分支直接覆盖远程分支,使远程分支最新。如果远程分支可能有其他人修改,最好使用git push --force-with-lease
,这个命令在检测到远程分支修改时,不会强制覆盖。此时需要先fetch
远程分支,然后整合分支修改,然后再使用git push --force-with-lease
推送代码。