git rebase 的使用

 

rebase

在 Git 中整合来自不一样分支的修改主要有两种方法:merge 以及 rebase。 在本节中咱们将学习什么是“rebase”,怎样使用“rebase”,并将展现该操做的惊艳之处,以及指出在何种状况下你应避免使用它。python

rebase的基本操做

 

整合分支最容易的方法是 merge 命令。 它会把两个分支的最新快照(C3 和 C4)以及两者最近的共同祖先(C2)进行三方merge,merge的结果是生成一个新的快照(并提交)。git

还有一种方法:你能够提取在 C4 中引入的补丁和修改,而后在 C3 的基础上应用一次。 在 Git 中,这种操做就叫作 rebase。 你可使用 rebase 命令将提交到某一分支上的全部修改都移至另外一分支上,就好像“从新播放”同样。django

在上面这个例子中,运行:服务器

 

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

 

它的原理是首先找到这两个分支(即当前分支 experiment、rebase操做的目标基底分支 master)的最近共同祖先 C2,而后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,而后将当前分支指向目标基底 C3, 最后以此将以前另存为临时文件的修改依序应用。并发

如今回到 master 分支,进行一次快进merge。app

$ git checkout master
$ git merge experiment

 

此时,C4' 指向的快照就和上面使用 merge 命令的例子中 C5 指向的快照如出一辙了。 这两种整合方法的最终结果没有任何区别,可是rebase使得提交历史更加整洁。 你在查看一个通过rebase的分支的历史记录时会发现,尽管实际的开发工做是并行的,但它们看上去就像是串行的同样,提交历史是一条直线没有分叉。ide

通常咱们这样作的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个其余人维护的项目贡献代码时。 在这种状况下,你首先在本身的分支里进行开发,当开发完成时你须要先将你的代码rebase到 origin/master 上,而后再向主项目提交修改。 这样的话,该项目的维护者就再也不须要进行整合工做,只须要快进merge即可。工具

请注意,不管是经过rebase,仍是经过三方merge,整合的最终结果所指向的快照始终是同样的,只不过提交历史不一样罢了。 rebase是将一系列提交按照原有次序依次应用到另外一分支上,而merge是把最终结果合在一块儿。学习

 

 

更有趣的rebase例子

在对两个分支进行rebase时,所生成的“重放”并不必定要在目标分支上应用,你也能够指定另外的一个分支进行应用。 你建立了一个特性分支 server,为服务端添加了一些功能,提交了 C3 和 C4。 而后从 C3 上建立了特性分支 client,为客户端添加了一些功能,提交了 C8 和 C9。 最后,你回到 server 分支,又提交了 C10测试

假设你但愿将 client 中的修改合并到主分支并发布,但暂时并不想merge server 中的修改,由于它们还须要通过更全面的测试。 这时,你就可使用 git rebase 命令的 --onto 选项,选中在 client 分支里但不在 server 分支里的修改(即 C8 和 C9),将它们在 master 分支上重放:

 
$ git rebase --onto master server client

以上命令的意思是:“取出 client 分支,找出处于 client 分支和 server 分支的共同祖先以后的修改,而后把它们在 master 分支上重放一遍”。 这理解起来有一点复杂,不过效果很是酷。

 

如今能够快进merge master 分支了。

$ git checkout master
$ git merge client

 

 

 

接下来你决定将 server 分支中的修改也整合进来。 使用 git rebase [basebranch] [topicbranch] 命令能够直接将特性分支(即本例中的 server)rebase到目标分支(即 master)上。这样作能省去你先切换到 server 分支,再对其执行rebase命令的多个步骤。

$ git rebase master server

 

 

而后就能够快进merge主分支 master 了:

$ git checkout master
$ git merge server

 


至此,client 和 server 分支中的修改都已经整合到主分支里了,你能够删除这两个分支,最终提交历史会变成图 最终的提交历史 中的样子:

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

 

 

rebase的风险

呃,奇妙的rebase也并不是天衣无缝,要用它得遵照一条准则:

Do not rebase commits that exist outside your repository.

若是你遵循这条金科玉律,就不会出差错。

rebase操做的实质是丢弃一些现有的提交,而后相应地新建一些内容同样但实际上不一样的提交。 若是你已经将提交推送至某个仓库,而其余人也已经从该仓库拉取提交并进行了后续工做,此时,若是你用 git rebase 命令从新整理了提交并再次推送,你的同伴所以将不得再也不次将他们手头的工做与你的提交进行整合,若是接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。

让咱们来看一个在公开的仓库上执行rebase操做所带来的问题。 假设你从一个中央服务器克隆而后在它的基础上进行了一些开发。 你的提交历史如图所示:

而后,某人又向中央服务器提交了一些修改,其中还包括一次merge。 你抓取了这些在远程分支上的修改,并将其merge到你本地的开发分支,而后你的提交历史就会变成这样:

接下来,这我的又决定把merge操做回滚,改用rebase;继而又用 git push --force 命令覆盖了服务器上的提交历史。 以后你从服务器抓取更新,会发现多出来一些新的提交。

结果就是大家两人的处境都十分尴尬。 若是你执行 git pull 命令,你将merge来自两条提交历史的内容,生成一个新的merge提交,最终仓库会如图所示:

此时若是你执行 git log 命令,你会发现有两个提交的做者、日期、日志竟然是同样的,这会使人感到混乱。 此外,若是你将这一堆又推送到服务器上,你其实是将那些已经被rebase抛弃的提交又找了回来,这会使人感到更加混乱。 很明显对方并不想在提交历史中看到 C4 和 C6,由于以前就是他把这两个提交经过rebase丢弃的。

若是你 真的 遭遇了相似的处境,Git 还有一些高级魔法能够帮到你。 参考 https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%8F%98%E5%9F%BA

 

 

rebase vs. merge

至此,你已在实战中学习了rebase和merge的用法,你必定会想问,到底哪一种方式更好。 在回答这个问题以前,让咱们退后一步,想讨论一下提交历史到底意味着什么。

 

有一种观点认为,仓库的提交历史便是 记录实际发生过什么。 它是针对历史的文档,自己就有价值,不能乱改。 从这个角度看来,改变提交历史是一种亵渎,你使用_谎话_掩盖了实际发生过的事情。 若是由merge产生的提交历史是一团糟怎么办? 既然事实就是如此,那么这些痕迹就应该被保留下来,让后人可以查阅。

 

另外一种观点则正好相反,他们认为提交历史是 项目过程当中发生的事。 没人会出版一本书的初版草稿,软件维护手册也是须要反复修订才能方便使用。 持这一观点的人会使用 rebase 及 filter-branch 等工具来编写故事,怎么方便后来的读者就怎么写。

 

如今,让咱们回到以前的问题上来,到底merge仍是rebase好?但愿你能明白,这并无一个简单的答案。 Git 是一个很是强大的工具,它容许你对提交历史作许多事情,但每一个团队、每一个项目对此的需求并不相同。 既然你已经分别学习了二者的用法,相信你可以根据实际状况做出明智的选择。

 

总的原则是,只对还没有推送或分享给别人的本地修改执行rebase操做清理历史,从不对已推送至别处的提交执行rebase操做,这样,你才能享受到两种方式带来的便利。

相关文章
相关标签/搜索