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
分支的提交对象,对于如下:
git rebase --onto
上例使用了git rebase --onto 变基目标分支master 变基过渡分支issue1 变基当前分支issue2
指令,此指令解释如下:
- 变基当前分支,即当前执行变基的分支issue2;
- 变基目标分支,即当前执行变基分支的变基指向分支master;
- 变基过渡分支,即当前执行变基分支过滤提交对象的目标分支issue1(当前分支从过渡分支切出);
- 取出issue2分支上进行的所有提交对象,以补丁(patches)形式在主线master分支上重新应用,创建新提交
对象,然后将分支指针移动到最后一个新创建的提交对象。
需要注意的是此时master分支指针并没有更新到新提交对象,我们需要手动进行合并:
合并后提交历史图:
此时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分支上有三次提交,然而并入主线只创建(复制)了两个提交对象,这是因为我们在处理cec214
提交对象的变基冲突时,把此提交对象的变更删除了,即未发现变更,Git未将此提交对象并入主线。
接下来,可以在master分支合并issue1分支。
交互式变基(rebase --interactive)
这一节要介绍的是交互式变基,指令为git rebase -i
或git rebase --interactive
,使用该指令可以修改提交历史,其后参数可以是某一特定提交对象ID或执行特定提交对象的指针,将输出该提交对象之后的所有提交对象(不包括该提交对象),如HEAD~
表明输出当前分支最新一次提交对象,HEAD~~
表明输出当前分支的最新的两次提交对象。
HEAD~~
如下,在issue1分支执行指令git rebase -i HEAD~~
,会进入编辑模式:
如图列出了HEAD~~指向的提交对象之后的所有提交对象,每一行之前默认是pick
标志,表示该提交对象正在使用,我们可以修改该标记值,随后Git根据标记值执行不同操作,标记值与操作对应关系如下:
- 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系列已经看完了,对于Git的使用清晰了很多。文章结合实例讲得很详细、深入,佩服博主的认真细致。
该系列文章对于只是日常工作使用Git的朋友,可能深入了一点,还是希望能结合工作中可能遇到的场景进行讲解,以及提供一些操作指导、建议。
再次感谢!
请问该博客使用的是什么技术栈(框架)?,简洁易读
谢谢支持哈,我博客通过LNMP + wordpress构建的,在xmag主题的基础上自己修改,优化了一些bug
感谢支持,限于精力和时间是少了一些对场景的介绍和使用建议,以后还是需要多注意~ o(* ̄▽ ̄*)o。
经常能在一些主题下搜到你的文章,写的真不错