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. django反向解析传参

    这两天写代码总是有反向解析传参顺带复习了一下反向解析,以下是简单的反向解析 以下是我最近写的很多的反向解析传参  想要实现点击修改将这些从数据库读取的内容传到另一个页面就要通过id来查询,那么我们就需 ...

  2. VUE中总的逻辑关系和移动端mint-ui的应用接触

    1.mint-ui官网:https://mint-ui.github.io/#!/zh-cn 可以点击开始使用,里边有详细的讲解.安装mint-ui: 官网是: 但是应用没有装成功,不知为何,可能是我 ...

  3. 【python3接口自动化基础】json总结

    Json知识归纳: Post请求有两种方法传json参数 传json参数(自动转json了)---json=dict 传data参数(需json转换) ---data=json.dumps(dic) ...

  4. Java_Character类

      Character类用于对单字符进行操作. 常用的方法: System.out.println(Character.isDigit('1')); // true 判断是否是一个数字字符 Syste ...

  5. Mac新系统常用设置

    一.MAC OS整个系统的隐藏文件显示可见,在终端下输入以下命令defaults write com.apple.finder AppleShowAllFiles -bool true 二. 在MAC ...

  6. (初)Knockout 监控属性(Observables)

    1 创建带有监控属性的view model 1.1 Observables Knockout是在下面三个核心功能是建立起来的: 监控属性(Observables)和依赖跟踪(Dependency tr ...

  7. mysql查看每个数据库所占磁盘大小

    #查看每个数据库所占磁盘大小 SELECT TABLE_SCHEMA AS "库名", , ) AS "表所占空间(MB)", , ) AS "索引所 ...

  8. 20172328 2018-2019《Java软件结构与数据结构》第三周学习总结

    20172328 2018-2019<Java软件结构与数据结构>第三周学习总结 概述 Generalization 本周学习了第五章:队列.主要内容包含队列的处理过程.如何用对例如求解问 ...

  9. python 爬虫与数据可视化--python基础知识

    摘要:偶然机会接触到python语音,感觉语法简单.功能强大,刚好朋友分享了一个网课<python 爬虫与数据可视化>,于是在工作与闲暇时间学习起来,并做如下课程笔记整理,整体大概分为4个 ...

  10. struts2的java.lang.NoSuchMethodException错误

    不久前在学习struts时出现这个错误,在网上搜索了半天,发现答案不一.将其总结如下,以方便大家参考. 1. 你有没有试试看 其它的方法能不能用,要是都是这种情况的话,可能是你的Action类没有继承 ...