http://softlab.sdut.edu.cn/blog/subaochen/2016/01/git-rebase%E5%92%8Cgit-merge%E7%9A%84%E7%94%A8%E6%B3%95%E5%8C%BA%E5%88%AB/

1 前言

git rebase和git merge常令人迷惑,都是合并分支,什么时候用rebase,什么时候用merge呢?下面通过两个实验彻底搞清楚这两个命令的区别。

2 准备工作

hello-git是一个已经有一些提交(C0-C5)的示例项目,我们下面的两个实验都基于hello-git,分别通过merge和rebase两种方法将topoic分支合并到master分支。首先做一点点准备工作:
$ git clone https://github.com/subaochen/hello-git # 获取演示文件
$ git fetch origin topic # 获取topic分支。默认git clone下来的是master分支
此时hello-git的状态如图1所示,也就是说,master分支指向C3,topic分支指向C5,当前分支是master(HEAD指向master)。
图 1:hello-git的初始状态

3 merge方法合并分支

git merge用法简单,要把topic分支合并到master分支,首先确保当前处于master分支:
$ git branch
* master
topic
然后直接执行merge指令即可:
$ git merge topic
git merge的过程是首先找到master分支和topic分支的起点C2,然后把C2提交之后的所有提交,即C3、C4、C5合并起来形成一个新的提交C6。
执行命令提交合并的结果:
$ git commit

将创建一个新的提交C6,如图2所示:

图 2:git merge后的状态
此时,master分支指向新的提交C6,并且C6有两个父节点,即C6是C3、C4、C5合并后的结果。
如果通过git log命令可以看到如下的结果:
$ git log –oneline –decorate –graph –all
* 1679337 (HEAD -> master) C6:Merge branch ‘topic’
|\
| * 4b874d6 (origin/topic, topic) C5
| * 6e2550c C4
* | 96bba70 (origin/master, origin/HEAD) C3
|/
* 5aadaa6 C2
* 273c00a C1
* d50589b C0
* fb68e6f Initial commit
重点:git merge合并后只创建一个新的提交,即这个新的提交包含了所有的合并结果,合并的过程在合并后就丢失了。

4 rebase方法合并分支

在实验rebase方法合并分支之前,首先删除上一步的项目目录hello-git,即重新clone一个新的项目出来:
$ rm -rf hello-git
$ git clone https://github.com/subaochen/hello-git
rebase的基本思路是,将topic分支的每一个提交(C4、C5)相对于C2的变更(diff,即补丁)临时保存起来,然后逐一在C3上面重放(replay,即逐一在C3上面打补丁),每打一个补丁即创建一个新的提交,直到所有topic分支的补丁全部打完,这样就完成了在master分支上合并topic分支的任务。rebase的本意也是这样:将当前分支(topic)重新基于(rebase)指定的分支构建。下面是rebase方法合并topic分支到master分支的基本流程:

4.1 获取topic分支

$ git fetch topic
来自 https://github.com/subaochen/hello-git
* branch topic -> FETCH_HEAD
此时hello-git的状态和图1一样。

4.2 切换到topic分支

$ git checkout topic
分支 topic 设置为跟踪来自 origin 的远程分支 topic。
切换到一个新分支 ‘topic’

4.3 rebase onto master分支

$ git rebase master
首先,重置头指针以便在上面重放您的工作…
正应用:C4
正应用:C5
此时hello-git的状态如图3所示:
图 3:将C4、C5的diff在C3上面重放后的状态
通过git log命令可以看出此时HEAD指向topic(当前分支)和C5,但是master分支仍然指向C3。
$ git log –oneline –decorate –graph –all
* 2de5ca6 (HEAD -> topic) C5
* 9eed5cb C4
* 96bba70 (origin/master, origin/HEAD, master) C3
| * 4b874d6 (origin/topic) C5
| * 6e2550c C4
|/
* 5aadaa6 C2
* 273c00a C1
* d50589b C0
* fb68e6f Initial commit
注意:如果topic分支是最新的,即topic分支是从master分支的最新节点开始,并且此后master分支没有新的进展,如图4所示的情形,则get rebase master的结果只是显示:topic是最新的。

图 4:topic分支是最新时的情形

4.4 fast forward merge

我们的目标是将topic分支合并到master分支,因此现在切换到master分支执行:
$ git checkout master
$ git merge topic
更新 96bba70..2de5ca6
Fast-forward
C4 | 1 +
C5 | 1 +
2 files changed, 2 insertions(+)
create mode 100644 C4
create mode 100644 C5
由于在第三步的rebase操作的时候已经将topic分支合并到了master分支,这里的git merge topic仅仅是移动HEAD指针而已,因此被称为快进(fast forward)模式。此时HEAD指向了C5,如图5所示:
图 5:在master分支fast forword merge之后的状态
如果使用git log命令查看,此时HEAD已经指向了C5。
* 2de5ca6 (HEAD -> master, topic) C5
* 9eed5cb C4
* 96bba70 (origin/master, origin/HEAD) C3
| * 4b874d6 (origin/topic) C5
| * 6e2550c C4
|/
* 5aadaa6 C2
* 273c00a C1
* d50589b C0
* fb68e6f Initial commit

4.5 rebase的进一步解释

注意到以下两个命令的效果是一样的:
$ git rebase master
$ git rebase
master topic # 首先执行git rebase master,然后执行git checkout topic。但是实际上git
rebase master也可以达到同样的效果。另外,这条命令不管当前在哪个分支上都有效,而且语义更加明确,推荐。

4.6 神奇的rebase –onto

假设有如图6的情形(请git clone https://github.com/subaochen/hello-git-rebase获取图中的示例代码):
图 6:在server分支上存在client分支的情形
通过git log –oneline –decorate –graph –all观察的结果是:
* 5c38282 (server) C9
* 0b8cc9e C4
| * d2ba3bd (client) C8
| * aecaedb C7
|/
* 1e3855d C3
| * 894eb0e (HEAD -> master) C6
| * a1cdb1e C5
|/
* 20b3774 (origin/master, origin/HEAD) C2
* e99b06f C1
* 255c292 Initial commit
现在假设client分支的开发稳定了,我们希望把client分支合并到master分支上。但是此时server分支的开发尚在继续,怎么办呢?git rebase –onto提供了神奇的效果:
$ git rebase –onto master server client
首先,回退分支以便在上面重放您的工作…
应用:C7
应用:C8
这个命令的意思是说:“检查client分支,获得client和server的分歧点(这里是C3)以来的所有补丁(这里是C7、C8)并打在master分支上”,基本上,你可以把这句命令倒着念回去。执行后的结果会是图所示的那样。
图 7:client分支合并到master后的情形
然后再在master分支执行fast forword merge即可:
$ git checkout master
$ git merge client
更新 894eb0e..d64c83b
Fast-forward
C7 | 1 +
C8 | 1 +
2 files changed, 2 insertions(+)
create mode 100644 C7
create mode 100644 C8
最终的结果正如我们期望的:
图 8:rebase –onto的最终结果
git log –oneline –decorate –graph –all的执行结果也佐证了这一点:
$ git log –oneline –decorate –graph –all
* d64c83b (HEAD -> master, client) C8
* 729c0bb C7
* 894eb0e (origin/master, origin/HEAD) C6
* a1cdb1e C5
| * 5c38282 (origin/server, server) C9
| * 0b8cc9e C4
| | * d2ba3bd (origin/client) C8
| | * aecaedb C7
| |/
| * 1e3855d C3
|/
* 20b3774 C2
* e99b06f C1
* 255c292 Initial commit

5 区别使用

虽然,两种合并分支方式的最终结果并没有什么差别,但是rebase模式的最大优点是可以保留分支的变动情况,或者说,topic分支的历史在master分支上被完整的复现了出来。merge模式的合并则不然,merge在合并点像和面一样将两个分支融合在一,只是记录了合并的结果,在master分支上看不出topic分支的变动情况。因此,在大多数情况下,建议使用rebase来合并分支。如果是采用pull更新远程分支,建议使用如下的命令:
$ git pull –rebase upstream branch
为了方便起见,建议设置别名:
$ git alias pull “pull –rebase”
通常,在一些开源项目中要求使用rebase合并分支。如果你要为一个开源项目开发一个新特性,通常会创建一个分支,然后在这个分支上开发新特性,开发完成后再合并到master分支上来。如果采用rebase方式合并分支,则开源项目的维护人只需要执进行fast
forward merge即可。比如:https://developer.jboss.org/wiki/HackingOnWildFly#

6 rebase合并失败会怎样?

重复“准备工作”获得一个新鲜的hello-git,我们故意制造一个冲突看看:在master分支中执行:
$ echo “C4 from master” >> C4
然后按照rebase的合并方法试图将topic分支合并到master的时候会提示:
首先,回退分支以便在上面重放您的工作…
应用:C4
使用索引来重建一个(三方合并的)基础目录树…
回落到基础版本上打补丁及进行三方合并…
自动合并 C4
冲突(添加/添加):合并冲突于 C4
error: 无法合并变更。
打补丁失败于 0001 C4
失败的补丁文件副本位于:.git/rebase-apply/patch
当您解决了此问题后,执行 “git rebase –continue”。
如果您想跳过此补丁,则执行 “git rebase –skip”。
要恢复原分支并停止变基,执行 “git rebase –abort”。
非常人性化,这告诉我们rebase在给C4打补丁时发现冲突因此停了下来(C5还没有处理)并且给出三个解决方案:
  • 解决冲突后执行git rebase –continue继续rebase的流程。
  • 如果跳过这个补丁(通常这是不允许的)则可以执行git rebase –skip。
  • 如果后悔了,可以通过执行git rebase –abort回复rebase之前的状态。
通常的做法是,打开C4这个文件解决冲突后执行git add C4,然后git rebase –continue。

7 rebase就木有缺点吗?

这部分还需要加强学习和理解!
TBD,see revert a faulty merge howto

8 总结

如果更新还没有push到远程仓库,那么使用rebase,尽量保留更新的历史;如果更新已经push到了远程仓库,那么永远不要rebase这部分代码!

9 参考资料

    • 《pro git》,分支一章讲的非常精彩,这篇日志的部分内容搬运自pro git外加自己的理解。
    • git help rebase也是不错的学习材料!
    • 本文的pdf版本和dot源文件可到https://github.com/subaochen/blog 下载

git rebase和git merge的用法的更多相关文章

  1. git rebase vs git merge详解

    https://medium.com/@porteneuve/getting-solid-at-git-rebase-vs-merge-4fa1a48c53aa#.std3ddz0g 请参考另外一篇文 ...

  2. git rebase VS git merge? 更优雅的 git 合并方式值得拥有

    写在前面 如果你不能很好的应用 Git,那么这里为你提供一个非常棒的 Git 在线练习工具 Git Online ,你可以更直观的看到你所使用的命令会产生什么效果 另外,你在使用 Git 合并分支时只 ...

  3. git rebase VS git merge

    git rebase VS git merge 写在前面 如果你不能很好的应用 Git,那么这里为你提供一个非常棒的 Git 在线练习工具 Git Online(回复公众号「工具」,获取更多内容) , ...

  4. git rebase 与git merge 小结

    git merge是用来合并两个分支的. $ git merge b   将b分支合并到当前分支 同样  $ git rebase b ,也是把 b分支合并到当前分支 ---------------- ...

  5. git rebase 和 git merge 总结

    git merge 和 git rebase 都是用于合并分支,但二者是存在区别的. 在使用时,记住以下两点: 当你从 remote 去 pull 的时候,永远使用 rebase(除了一个例外) 当你 ...

  6. git rebase、git merge、git cherry-pick 使用详解

    1.git cherry-pick 是合入其他分支的某一次或者几次提交(cherry-pick是挑选的意思):把其他分支的某些功能合入当前分支 2.git merge 把其他分支合入当前分支,一般用作 ...

  7. 你在开发过程中使用Git Rebase还是Git Merge?

    摘要:在git里面经常的一个争论是到底用rebase还是用merge? 1. 痛苦吗?代码历史中的迷失羔羊 我们先来看一个真实的代码提交历史图形化截图: 图片源自 https://storage.kr ...

  8. git rebase和git merge的区别

    前言:    平时工作中发现一般同事在同步远程代码的时候都是用git pull,其实git pull包含有两个操作,一个是fetch远程的代码,一个是将本地当前的代码和远程代码进行merge,即git ...

  9. git rebase与 git合并(error: failed to push some refs to)解决方法

    1.遇到的问题 本地有一个git仓库,在github上新建了一个空的仓库,但是更新了REWADME.md的信息,即在github上多了一个提交. 关联远程仓库,操作顺序如下: git remote a ...

随机推荐

  1. windows下使用git和github建立远程仓库

    转自(http://www.bubuko.com/infodetail-430228.html) 从昨天开始就在看git的使用,因为在Windows下很多命令行操作都比较坑爹,但是今天再走了无数弯路之 ...

  2. $ Django 调API的几种方式

    API调用方式 下面是python中会用到的库.urllib2httplib2pycurlrequestsurllib2 #request import requests, json github_u ...

  3. List的分组,求和,过滤操作

    package ---; import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; /* ...

  4. 网络抓包教程之tcpdump

    现在的移动端应用几乎都会通过网络请求来和服务器交互,通过抓包来诊断和网络相关的bug是程序员的重要技能之一.抓包的手段有很多:针对http和https可以使用Charles设置代理来做,对于更广泛的协 ...

  5. Python开发【第十篇】:模块

    模块,用一砣代码实现了某个功能的代码集合. 类似于函数式编程和面向过程编程,函数式编程则完成一个功能,其他代码用来调用即可,提供了代码的重用性和代码间的耦合.而对于一个复杂的功能来,可能需要多个函数才 ...

  6. C++面试

    C++ Primer.STL源码剖析.设计模式 C++ 析构函数可以是虚函数吗?为什么 构造函数可以是虚函数吗?为什么 如何防止类被继承 手写String类(实现类里面常用函) 什么是野指针如何避免这 ...

  7. Win下必备神器之Cmder

    诚言,对于开发码字者,Mac和Linux果断要比Windows更贴心;但只要折腾下,Windows下也是有不少利器的.之前就有在Windows下效率必备软件一文中对此做了下记载:其虽没oh-my-zs ...

  8. SQL反模式学习笔记7 多态关联

    目标:引用多个父表 反模式:使用多用途外键.这种设计也叫做多态关联,或者杂乱关联. 多态关联和EAV有着相似的特征:元数据对象的名字是存储在字符串中的. 在多态关联中,父表的名字是存储在Issue_T ...

  9. day16匿名函数

    匿名函数,好像也就是 lambda 表达式 先来看一段函数,返回 def func(n): return n * 3 print(func(5))15 用lambda表达式写: func = lamb ...

  10. 在使用mysql8.0的时候遇到的密码链接问题

    问题概述 SQLSTATE[HY000] [2054] The server requested authentication method unknown to the client SQLSTAT ...