Git手册 - Git Rebase

2021-05-05
git
约 3390 字 预计阅读 7 分钟

git rebase是最重要的功能之一,可以自由的修改commit和commit的msg,可以合并,删除,修改之前的任何一次commit, 也可以用来合并分支,并且现在推荐所有的非发布型分支,都用rebase来进行合并,特别是个人的私有的工作分支

1. Rebase简介

1.1. rebase commit用途总结

  • 从别的分支上获取commit,然后在尾部开始应用工作分支的提交
  • 合并或者修改commit
  • 保持一个干净,线性的提交历史
  • 确保主分支的commit干净

如下图:

开发过程中,工作分支和主分支都有了新的提交

image-20210520090216729

这个时候在new_feature上, 如果想rebase master, 之后的commit就会变成这样

image-20210520094112291

1.2. rebase的特点

rebase之后的分支是线性的, 干净的

1.3. rebase的原理

是会建立一个零时分支, 将要rebase的分支作为主干, 将目前的工作分支的提交作为补丁,从最后开始打补丁

如图所示:

当在new_feature上想要rebase master的时候

  1. 将new_feature回到原始状态
  2. git 会建立一个临时的存储区,将new_feature上的commit全部放进去, 这时候new_feature已经清空
  3. 然后将引用移动到master的最新commit
  4. 从这里开始将临时存储的commit以补丁的形式在最末端开始replay

image-20210520094323121

image-20210520100346145

2. rebase用法一 合并

2.1. 将master的提交合并到工作分支

常规操作,操作当前分支

1
git rebase master

操作非当前分支, 此操作会自动checkout new_feature

1
git rebase master new_feature

合并前,找到最好的节点,显示合并的交叉commit信息

1
git merge-base master new_feature

2.2. Merge和Rebase的区别

  • 两者都是将改变从一个分支应用到另一个分支
  • 结果是一样的,过程不一样
  • Merge的特点
    • 添加一条merge的commit
    • 非破坏性的
    • 有完整的记录在什么时候发生了什么
    • 易于undo, 直接git reset --hard就可以
    • 日志会变得混乱, 并且非线性
  • Rebase的特点
    • 没有多余的commit信息
    • 产生破坏: SHA改变了, commit被重写了
    • 整个时间线就不完整了,无法得知什么时候发生了什么, 只有一个合并后的并被改写了commit的线性log
    • 不容易undo
    • log是线性的,并且非常干净

看下图:

image-20210520105942109

分别用两种方式合并以后

image-20210520110042762

2.3. 使用Rebase的黄金法则

2.3.1. 永远不要用Rebase改变发布分支

也就是说,不要在这些分支上rebase其它分支,一旦这些commit已经共享给别人了,并正在使用,就不要改变这些commit了,因为:

  • rebase是会改变commit的SHA和顺序的, rebase的操作就是先把分支上的commit全部清空并缓存,然后找到从要合并的分支的尽头重新打补丁(应用commits)
  • 其它使用此分支的合作者将会看到历史被改变了,这种改变非常有破坏性,会影响其它人, 它之前使用的commit会消失不见,取而代之的是一些新的commit(SHA改变了,时间顺序也变了)
  • 对于合作者需要将公共分支的内容同步到自己的工作分支的时候简直就是噩梦

要记住, rebase是对工作分支的破坏,工作分支上的commit都会被删除并修改再重新应用到目标分支的末端.

使用合并之前考虑以下情况能否接受:

  • commit的时间改变了
  • commit的SHA改变了

2.3.2. 如何选择才是正确的

  • Merge: 将阶段性完成的功能完整的分支Merge到Master上(因为merge是commit不变的,有清晰的历史时间点)
  • Merge: 准备好发布的分支,和公共分支用merge来合并别的分支的commits
  • Rebase: master分支有一些改变,如Minor级别的commit,可以将这些commit通过rebase添加到一个主题分支上,或者个人的开发分支上
  • Rebase: 只应用在开发分支上或者说个人的,私有的分支上
  • Rebase: 需要将一些commit从一个分支移动到另一个分支
  • Rebase: 单向使用, 不要在两个分支上相互rebase

3. rebase用法二 整理commit

包括

  • 合并commit
  • 修改commit
  • 删除commit

其中有的可以停下来,对一次commit进行代码修改,然后再继续rebase

操作方法是利用交互式rebase,也就是--interactive或者-i选项, 在交互式操作rebase的时候,rebase会整理一个todolist文件交给我们编辑

具体内容可参考下面的交互式rebase

4. 处理Rebase的Conflicts

和Merge一样,rebase会产生conflicts,当产生conflicts的时候,

rebase一旦发现conflicts,rebase会暂停,等待你完成冲突的解决

4.1. git rebase –continue

解决完冲突以后,可以使用下面的命令继续完成rebase操作

1
git rebase --continue

4.2. git rebase –skip

也可以使用--skip来跳过冲突

1
git rebase --skip

注意skip的过程是: 放弃这个commit带来的冲突,继续rebase,意味着本地的修改commit会消失

4.3. git rebase –abort

放弃rebase,回到rebase之前的状态

1
git rebase --abort

5. 用rebase onto改变分支的基点

1
2
3
git rebase --onto newbase upstream branch
# 比如
git rebase --onto master ecommerce new_feature

一个分支A从master checkout, 在A有一些commits之后, 分支B从分支A checkout, 现在想将分支B从最新的master来rebase

如下图

image-20210520154810640

看log图形

image-20210520155710441

原先camping是基于master的Add clean closet to do, expenses 是基于camping的Todo: Add camping activities

使用如下命令

1
git rebase --onto master camping expenses

分支expenses的基础发生了改变,看log图形

image-20210520160005804

如果想再rebase回来

1
git rebase --onto camping master expenses

image-20210520161112047

6. Undo Rebase

当我们rebase之后发现问题想要退回的时候,rebase就比merge要难一点,有两种方法

第一种

1
git reset --hard ORIG_HEAD

第二种

1
git rebase --onto hashstring master my_feature

用这种方式我们可以将分支的起点任意改变到master上的任何一次commit

例如:

现在3个分支在一个时间轴上,是线性的

image-20210520162214086

通过rebase --onto命令,将expenses的基点移动到了master上

1
git rebase --onto master camping expenses

image-20210520162431794

想要还原,我们可以使用下面的命令

1
git reset -hard ORIG_HEAD

也可以使用下面的命令达到同样的效果

1
git rebase --onto 7b3514 master expenses 

7. 交互式rebase

交互式rebase通常用来修改,合并,删除commit等操作,也可以用来合并分支,只需要在命令里加上-i选项

rebase会提供一个todo文件给我们编辑,看起来如下图

image-20210520163512499

从上到下,就是commit的提交历史从远到近

默认是pick,还有很多选项提供我们编辑,常用的如下

  • pick 使用这个commit
  • reword 使用commit,但是要修改commit message
  • edit 使用commit,但是停下rebase,我要在这个commit上做一些修改操作
  • squash 使用commit,但是将和上一个commit合并
  • fixup 类似squash,但是commit message被忽略,不予合并
  • drop 丢弃这个commit,和pick相反
  • exec 从这里开始使用shell命令,控制权交给我

注意:

如果将此文件清空,那么意味着这次rebase操作就完终止并放弃,任何东西都没有改变

删除某一行和drop的效果一样

可以重新排序,调整行之间的顺序,commit的顺序也会改变

命令示例:

1
2
3
4
5
git rebase -i master feature # 将master合并到feature, 基点调整为master的最后一次提交
git rebase -i master~1 # master分支的最后一次
git rebase -i master~5 # 
git rebase -i HEAD~3   # 当前分支的倒数第三次状态
git rebase -i 1e2w2hd1 # 指定某一次提交

8. Pull Rebase

pull有两种方式,merge和rebase,这和合并代码的方式一样,推荐使用rebase

考虑下面的情况

image-20210520224105287

服务器的公共分支发生了变化,本地的分支也发生了变化,如果我们先fetch,会变成这样

image-20210520224156930

如果是使用merge来合并远程分支,提交历史会变成这样

image-20210520224315066

如果使用rebase来进行pull

image-20210520224522832

8.1. 如何使用rebase来进行pull操作

pull关于rebase的选项

1
--rebase[=false|true|merges|preserve|interactive]

最简单的方式,通过option来指定

1
2
3
git pull --rebase
# or
git pull -r

还有比较特殊的选项

1
2
3
git pull --rebase=preserve
# rebase的时候加上一个merge commit
git pull --rebase=merge
TAG: git scm
文章作者 : Cocding