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. Alpha 事后诸葛亮(团队)

    前言 事后诸葛亮?作业名真的不好听,下一届还要沿用吗? 队名:小白吃 通向hjj博客的任意门 思考总结 设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? ...

  2. yum安装软件报错Segmentation fault处理

    yum安装软件报错Segmentation fault处理 在使用yum 更新软件时提示:Segmentation fault 中文错误提示: 段错误 [root@CMS-BAK:/usr/local ...

  3. 【Hibernate】Unable to locate appropriate constructor on class原因分析

    通常我们喜欢将hql查询结果封装到POJO对象syntax:select new POJO(id,name) from POJO ; 这种封装需要POJO类提供对应构造器,POJO(id,name)构 ...

  4. SpringBoot配置

    多模块Maven项目 .gitignore文件 .idea *.iml targetout log tmp test 父模块pom文件 <?xml version="1.0" ...

  5. HTML 中的预留字符(如标签的小于号 < )必须被替换为字符实体( &lt; )。 不间断空格(&nbsp;)

    1. 参考 HTML 字符实体 Python处理HTML转义字符 比方说一个从网页中抓到的字符串 html = '<abc>' 用Python可以这样处理: import HTMLPars ...

  6. 你好!酷痞 Coolpy

    欢迎你进入酷痞的物联网世界.这里有着自由的空气和自然的气息.接下来我将告诉你如果一步步建立一个自己专属的物联网平台. 由于目前的酷痞的官方域名还没有通过备案所以现在用临时域名解说本说明. 最终酷痞的官 ...

  7. Linux下如何查看系统启动时间和运行时间以及安装时间

    1.uptime命令输出:16:11:40 up 59 days, 4:21, 2 users, load average: 0.00, 0.01, 0.00 2.查看/proc/uptime文件计算 ...

  8. 理解WidowManager

    --摘自<Android进阶解密> *WindowManger的关联类* 1.Window是一个抽象类,具体的实现类为PhoneWindow,它对View进行管理.WindowManage ...

  9. Codeforces 730 J.Bottles (01背包)

    <题目链接> 题目大意: 有n个瓶子,各有水量和容量.现在要将这写瓶子里的水存入最少的瓶子里.问你最少需要的瓶子数?在保证瓶子数最少的情况下,要求转移的水量最少. 解题分析:首先,最少的瓶 ...

  10. 数据挖掘算法——Apriori算法

    Apriori算法  首先,Apriori算法是关联规则挖掘中很基础也很经典的一个算法. 转载来自:链接:https://www.jianshu.com/p/26d61b83492e 所以做如下补充: ...