衍合

把一个分支整合到另一个分支的办法有两种:merge(合并) 和 rebase(衍合)。

衍合基础

假设在接下来的一次软件发布中,你决定把客户端的修改先合并到主线中,而暂缓并入服务端软件的修改(因为还需要进一步测试)。你可以仅提取对客户端的改变(C8 和 C9),然后通过使用 git rebase 的 –onto 选项来把它们在 master 分支上重演:

$ git rebase –onto master server client
这基本上等于在说“检出 client 分支,找出 client 分支和 server 分支的共同祖先之后的变化,然后把它们在 master 上重演一遍”。是不是有点复杂?不过它的结果,如图 3-32 所示,非常酷:

现在可以快进 master 分支了(见图 3-33):
$ git checkout master
$ git merge client

现在你决定把 server 分支的变化也包含进来。可以直接把 server 分支衍合到 master 而不用手工转到 server 分支再衍合。git rebase [主分支] [特性分支] 命令会先检出特性分支 server,然后在主分支 master 上重演:

$ git rebase master server
于是 server 的进度应用到 master 的基础上,如图 3-34:


然后快进主分支 master:

$ git checkout master
$ git merge server
现在 client 和 server 分支的变化都被整合了,不妨删掉它们,把你的提交历史变成图 3-35 的样子:


$ git branch -d client
$ git branch -d server

##衍合的风险

呃,奇妙的衍合也不是完美无缺的,一句话可以总结这点:

永远不要衍合那些已经推送到公共仓库的更新。

如果你遵循这条金科玉律,就不会出差错。否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

在衍合的时候,实际上抛弃了一些现存的 commit 而创造了一些类似但不同的新 commit。如果你把commit 推送到某处然后其他人下载并在其基础上工作,然后你用 git rebase 重写了这些commit 再推送一次,你的合作者就不得不重新合并他们的工作,这样当你再次从他们那里获取内容的时候事情就会变得一团糟。

下面我们用一个实际例子来说明为什么公开的衍合会带来问题。
假设你从一个中央服务器克隆然后在它的基础上搞了一些开发,提交历史类似图 3-36:

现在,其他人进行了一些包含一次合并的工作(得到结果 C6),然后把它推送到了中央服务器。你获取了这些数据并把它们合并到你本地的开发进程里,让你的历史变成类似图 3-37 这样:

接下来,那个推送 C6 上来的人决定用衍合取代那次合并;他们用 git push –force 覆盖了服务器上的历史,得到 C4’。然后你再从服务器上获取更新:

这时候,你需要再次合并这些内容,尽管之前已经做过一次了。衍合会改变这些 commit 的 SHA-1 校验值,这样 Git 会把它们当作新的 commit,然而这时候在你的提交历史早就有了 C4 的内容(见图 3-39):

你迟早都是要并入其他协作者提交的内容的,这样才能保持同步。当你做完这些,你的提交历史里会同时包含 C4 和 C4’,两者有着不同的 SHA-1 校验值,但却拥有一样的作者日期与提交说明,令人费解!更糟糕的是,当你把这样的历史推送到服务器,会再次把这些衍合的提交引入到中央服务器,进一步迷惑其他人。

如果把衍合当成一种在推送之前清理提交历史的手段,而且仅仅衍合那些永远不会公开的 commit,那就不会有任何问题。如果衍合那些已经公开的 commit,而与此同时其他人已经用这些 commit 进行了后续的开发工作,那你有得麻烦了。