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)。
3 merge方法合并分支
git merge用法简单,要把topic分支合并到master分支,首先确保当前处于master分支:
$ git branch
* master
topic
然后直接执行merge指令即可:
git merge的过程是首先找到master分支和topic分支的起点C2,然后把C2提交之后的所有提交,即C3、C4、C5合并起来形成一个新的提交C6。
执行命令提交合并的结果:
此时,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
4.2 切换到topic分支
$ git checkout topic
分支 topic 设置为跟踪来自 origin 的远程分支 topic。
切换到一个新分支 ‘topic’
4.3 rebase onto master分支
$ git rebase master
首先,重置头指针以便在上面重放您的工作…
正应用:C4
正应用:C5
图 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
最终的结果正如我们期望的:
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”
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 参考资料
- git rebase vs git merge详解
https://medium.com/@porteneuve/getting-solid-at-git-rebase-vs-merge-4fa1a48c53aa#.std3ddz0g 请参考另外一篇文 ...
- git rebase VS git merge? 更优雅的 git 合并方式值得拥有
写在前面 如果你不能很好的应用 Git,那么这里为你提供一个非常棒的 Git 在线练习工具 Git Online ,你可以更直观的看到你所使用的命令会产生什么效果 另外,你在使用 Git 合并分支时只 ...
- git rebase VS git merge
git rebase VS git merge 写在前面 如果你不能很好的应用 Git,那么这里为你提供一个非常棒的 Git 在线练习工具 Git Online(回复公众号「工具」,获取更多内容) , ...
- git rebase 与git merge 小结
git merge是用来合并两个分支的. $ git merge b 将b分支合并到当前分支 同样 $ git rebase b ,也是把 b分支合并到当前分支 ---------------- ...
- git rebase 和 git merge 总结
git merge 和 git rebase 都是用于合并分支,但二者是存在区别的. 在使用时,记住以下两点: 当你从 remote 去 pull 的时候,永远使用 rebase(除了一个例外) 当你 ...
- git rebase、git merge、git cherry-pick 使用详解
1.git cherry-pick 是合入其他分支的某一次或者几次提交(cherry-pick是挑选的意思):把其他分支的某些功能合入当前分支 2.git merge 把其他分支合入当前分支,一般用作 ...
- 你在开发过程中使用Git Rebase还是Git Merge?
摘要:在git里面经常的一个争论是到底用rebase还是用merge? 1. 痛苦吗?代码历史中的迷失羔羊 我们先来看一个真实的代码提交历史图形化截图: 图片源自 https://storage.kr ...
- git rebase和git merge的区别
前言: 平时工作中发现一般同事在同步远程代码的时候都是用git pull,其实git pull包含有两个操作,一个是fetch远程的代码,一个是将本地当前的代码和远程代码进行merge,即git ...
- git rebase与 git合并(error: failed to push some refs to)解决方法
1.遇到的问题 本地有一个git仓库,在github上新建了一个空的仓库,但是更新了REWADME.md的信息,即在github上多了一个提交. 关联远程仓库,操作顺序如下: git remote a ...
随机推荐
- 【转】协同开发中SVN使用规范试用
转自:http://www.cnblogs.com/BraveCheng/archive/2012/07/02/2573617.html 协同开发中SVN使用规范试用 目标,要求 本次svn提交规范主 ...
- Java框架之spring框架的优点,为什么要学习spring框架
为什么要学习Spring的框架a: 方便解耦,简化开发 Spring就是一个大工厂,可以将所有对象创建和依赖关系维护,交给Spring管理 b:AOP编程的支持 Spring提供面向切 ...
- An overview of network penetration testing
1. an SQLi vulnerability will allow you to do the following query the database using select statem ...
- HTML常用基础标签
HTML常用基础标签 带有语义的标签 <em> </em> 强调 <strong> </strong> 比em强调级别高 <abbr> &l ...
- css中的宽度
浏览器通过CSS对元素的盒子模型的描述进行页面渲染的.因此,元素的宽度受到父元素.css描述的影响. 通常,元素的宽度是指盒子模型中content-box所占用的宽度.也就是说,默认box-sizin ...
- WPF 杂记
1,跨屏最大化 单屏幕的时候我们可以设置 WindowState 来使应用最大化 当接多个屏幕的时候,就需要下面这个设置: private void FullScreen() { this.Windo ...
- 视频转码成mp4格式,添加关键帧,添加元数据,把元数据放在第一帧,可拖动
作者测试是在windows下使用,所以下载的页面地址是: http://ffmpeg.zeranoe.com/builds/点击页面上的Download FFmpeg git-738ebb4 64-b ...
- Python3中的真值测试
1. 真值测试 所谓真值测试,是指当一种类型对象出现在if或者while条件语句中时,对象值表现为True或者False.弄清楚各种情况下的真值对我们编写程序有重要的意义. 对于一个对象a,其真值定义 ...
- 在vue2中隐藏elementUI的tab栏
<template> <el-tabs v-model="activeName" @tab-click="handleClick" ref=& ...
- python面试题之什么是PEP8规范
1 缩进.4个空格的缩进(编辑器都可以完成此功能),不使用Tap,更不能混合使用Tap和空格. 2 每行最大长度79,换行可以使用反斜杠,最好使用圆括号.换行点要在操作符的后边敲回车. 3 类和top ...