• git rebase和git merge的用法


    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/

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

    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)。
    image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2016/02/0_home_subaochen_git_blog_imgs_git_rebase_1-1.png
    图 1:hello-git的初始状态

    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所示:

    image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2016/02/1_home_subaochen_git_blog_imgs_git_merge_1-1.png
    图 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合并后只创建一个新的提交,即这个新的提交包含了所有的合并结果,合并的过程在合并后就丢失了。

    在实验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所示:
    image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2016/02/2_home_subaochen_git_blog_imgs_git_after_rebase_1-1.png
    图 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是最新的。
    image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2016/02/3_home_subaochen_git_blog_imgs_git_before_rebase_0-1.png
    图 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所示:
    image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2016/02/4_home_subaochen_git_blog_imgs_git_after_rebase_ff-1.png
    图 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获取图中的示例代码):
    image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2016/02/5_home_subaochen_git_blog_imgs_git_rebase_off_topic_1-1.png
    图 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分支上”,基本上,你可以把这句命令倒着念回去。执行后的结果会是图所示的那样。
    image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2016/02/6_home_subaochen_git_blog_imgs_git_rebase_off_topic_2-1.png
    图 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
    最终的结果正如我们期望的:
    image: http://softlab.sdut.edu.cn/blog/subaochen/wp-content/uploads/sites/4/2016/02/7_home_subaochen_git_blog_imgs_git_rebase_off_topic_3-1.png
    图 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

    虽然,两种合并分支方式的最终结果并没有什么差别,但是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#

    重复“准备工作”获得一个新鲜的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。

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

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

      • 《pro git》,分支一章讲的非常精彩,这篇日志的部分内容搬运自pro git外加自己的理解。
      • git help rebase也是不错的学习材料!
      • 本文的pdf版本和dot源文件可到https://github.com/subaochen/blog 下载
  • 相关阅读:
    FJUT3565 最大公约数之和(容斥)题解
    FJUT3568 中二病也要敲代码(线段树维护区间连续最值)题解
    BZOJ 2252 矩阵距离
    BZOJ 1047 理想的正方形
    BZOJ 1486 最小圈
    BZOJ 2083 Intelligence test
    BZOJ 1045 糖果传递
    BZOJ 3450 Easy
    BZOJ 4318 OSU!
    BZOJ 1954 The xor-longest Path
  • 原文地址:https://www.cnblogs.com/fengff/p/10736836.html
Copyright © 2020-2023  润新知