Git由浅入深之细说变基(rebase)

上一篇,详细介绍了Git分支管理,最后一节介绍了Git变基及其与合并的区别,限于篇幅,并未对变基展开介绍,实在是因为关于Git变基需要阐述的内容颇多,而且并不是新手能彻底掌握的,于是计划单列一篇,由浅入深,详细剖析,若有失误之处,望看官包容,指正。

变基(rebase)

rebase,有垫底,基底的意思,Git rebase我们称它为Git变基,即在当前分支外另一点上重新应用当前分支的提交历史,变基是Git整合变更的一种方式,另一种方式是什么呢(当然是合并了)?变基的基础使用已经在上一篇中提到,此处就不重复了,下面会阐述更深入或者说对于初学者更不常用的Git变基知识点。

多主题分支变基(git rebase –onto)

无论曾经,还是未来,只要你一直使用Git,早晚会遇到这样一种情况:从主干切出了某一分支issue1,进行了一些提交后,有另一个需求,我们在issue1分支切出新分支issue2,进行了提交,这期间其他成员对master主干分支进行了更新,结构图如下:

变基前结构图

现在,issue2分支开发完,我们需要将其变更并入主线,但是issue1分支的变更还是继续保持独立,如果直接进行简单变基,cec214,d4eb57也将被并入主线,这不是我们想要的结果,我们只希望将issue2分支上进行的变更和提交并入主线,即5f3776,ac5c08,这时需要使用git rebase --onto指令:

变基

提交历史图:

变基后分支提交历史

分支结构图:

变基后结构图

可以看出,执行--onto变基后,在主线上复制了issue2分支的所有提交对象,此处所谓复制,是在主线创建新提交对象,而其提交内容及备注复制自issue2分支的提交对象,对于如下:

--onto变基提交对象复制

git rebase –onto

上例使用了git rebase --onto 变基目标分支master 变基过渡分支issue1 变基当前分支issue2指令,此指令解释如下:

  • 变基当前分支,即当前执行变基的分支issue2;
  • 变基目标分支,即当前执行变基分支的变基指向分支master;
  • 变基过渡分支,即当前执行变基分支过滤提交对象的目标分支issue1(当前分支从过渡分支切出);
  • 取出issue2分支上进行的所有提交对象,以补丁(patches)形式在主线master分支上重新应用,创建新提交
    对象,然后将分支指针移动到最后一个新创建的提交对象。

需要注意的是此时master分支指针并没有更新到新提交对象,我们需要手动进行合并:

--onto变基后合并

合并后提交历史图:

--onto变基合并后提交历史图

此时master分支和issue2分支指针均指向主线最新提交对象749a39,执行合并指令时输出的第一行信息指明当前主线分支从624c26推进到749a39,第二行Fast-forward说明是快速推进合并方式。

变基与冲突

接下来,如果issue1分支开发测试完成,可以继续对该分支进行变基,合并:

变基冲突

如图中红线表明,变基合并文件时发生冲突,因为两个分支对同一文件同一部分进行了变更,需要我们手动处理冲突,打开该文件,处理掉冲突,然后继续执行变更:

处理变基冲突

注:注意此处红线标注处No changes -,为什么会说没有变更呢?因为在处理变基冲突时,我把此次提交的变更删除了,该文件没有变化,后文你会发现这样处理的意图。

如上图标红处显示REBASE 1/3,说明变基时存在多个提及对象需整合变更,需要多次处理冲突,这时,我们就需要使用git rebase --skip指令:

处理变基冲突

执行完git rebase --skip后,如图红线圈处REBASE 2/3``,说明第一个提交对象整合完成,再看红线下划线标注处第一行Applying: second edition of test.md“`指明当前继续变基时处理的提交对象信息,第二行指明冲突所在文件,首先处理冲突,然后继续执行变基:

继续处理变基冲突

如图,需要注意的有两点:

  • 在执行git rebase --continue指令时,需要使用git add指令标记冲突已解决(和SVN一样有这个过程);
  • 继续变基完成时,我们看到输出了两行Applying: 提交对象备注信息,这里两行是重点。

查看提交历史:

issue1分支变基后提交历史

目前,分支结构如下:

issue1分支变基后结构

issue1分支并入主线,其提交历史也全部并入主线,提交对象复制关系如下:

变基提交对象复制关系

我们发现,在issue1分支上有三次提交,然而并入主线只创建(复制)了两个提交对象,这是因为我们在处理cec214提交对象的变基冲突时,把此提交对象的变更删除了,即未发现变更,Git未将此提交对象并入主线。

接下来,可以在master分支合并issue1分支。

交互式变基(rebase –interactive)

这一节要介绍的是交互式变基,指令为git rebase -igit rebase --interactive,使用该指令可以修改提交历史,其后参数可以是某一特定提交对象ID或执行特定提交对象的指针,将输出该提交对象之后的所有提交对象(不包括该提交对象),如HEAD~表明输出当前分支最新一次提交对象,HEAD~~表明输出当前分支的最新的两次提交对象。

HEAD~~

如下,在issue1分支执行指令git rebase -i HEAD~~,会进入编辑模式:

git rebase -i HEAD~~

如图列出了HEAD~~指向的提交对象之后的所有提交对象,每一行之前默认是pick标志,表示该提交对象正在使用,我们可以修改该标记值,随后Git根据标记值执行不同操作,标记值与操作对应关系如下:

rebase -i提交对象状态关键字

  • pick(p),表明正在使用;
  • reword(r),表明仍然使用该提交对象,但是需修改提交信息;
  • edit(e),使用该提交对象,但是不合并提交对象;
  • squash(s),使用该提交对象,但是将此提交与上一次提交对象合并;
  • fixup(f),同squash值,但是丢失此次提交的日志信息;
  • exec(x),后接特定脚本,保存后将执行该脚本;
  • drop(d),移除该提交对象

reword

当我们把第二个提交前面的pick修改成reword,保存退出编辑模式,输出如下:

交互式变基修改提交对象

我们可以继续编辑提交对象信息,然后保存退出编辑模式,这里我修改了提交对象备注信息,如下图红圈处:

交互式变基修改提交对象后

exec

如果继续执行git rebase -i HEAD~~指令,我们再使用exec模式:

交互式变基执行脚本

保存退出编辑模式后,输出:

交互式变基执行脚本

-i 提交对象ID

当然除了使用HEAD指针做参数,git rebase -i后也可以接具体提交对象ID,如0f7de9,将输出该提交对象之后的所有提交对象(不包括该提交对象):

交互式变基

如上图,输出了0f7de9提交对象之后的所有提交对象,此指令等同于git rebase -i HEAD~

-i origin/master

比较特殊的一个参数是origin/master,使用git rebase -i origin/master,可以获取最后一次从origin远端仓库拉取(pull)或推送(push)之后的所有提交。

变基的影响

总结下来,Git变基的作用也是整合变更,首先在待合并分支执行变基,最后还是归于分支合并,但是在这个过程与直接合并分支还是有差别,正如本文的例子,可以看出变基会保留分支的提交历史,但是是通过将其并入主线保存的,之后关于该分支开发的具体历史及关系,已经被遮盖了,即历史已被休整,而我们通过直接合并分支方式整合变更时,分支的提交记录依然可以以分支的形式独立存在,历史未被修改。

选择变基还是合并

上一节,得出结论:

变基会修整历史,然后将分支历史并入主线,可以理解成美化过的历史,而合并则可以不修改历史,让分支历史依然独立存在,可以看作原始的历史。

所以选择变基还是合并,看具体需求,你只是想要一个清晰,明了的历史,并不关系历史的具体来源,你可以首选变基,但是如果你想比较清楚地了解项目不同阶段的原始历史,你可以选择直接合并。

一个原则

说到最后,还有一个不得不提的原则:

永远不要对已经推到主干分支服务器或者团队其他成员的提交进行变基,我们选择变基还是合并的范围应该在自己当前工作范围内。

原创文章,转载请注明: 转载自 熊建刚的博客

本文链接地址: Git由浅入深之细说变基(rebase)

熊 建刚

热爱前端,但不局限于前端,喜欢尝试各种新技术,爱好读书。

4 thoughts on “Git由浅入深之细说变基(rebase)

  1. 谢谢博主的文章,整篇Git系列已经看完了,对于Git的使用清晰了很多。文章结合实例讲得很详细、深入,佩服博主的认真细致。

    该系列文章对于只是日常工作使用Git的朋友,可能深入了一点,还是希望能结合工作中可能遇到的场景进行讲解,以及提供一些操作指导、建议。

    再次感谢!

    1. 感谢支持,限于精力和时间是少了一些对场景的介绍和使用建议,以后还是需要多注意~ o(* ̄▽ ̄*)o。

发表评论

电子邮件地址不会被公开。 必填项已用*标注