git rebase详解

1,318 阅读5分钟

首先通过简单的提交节点图解感受一下rebase在干什么?

提交节点图解

两个分支master和feature,其中feature是在提交点B处从master上拉出的分支,master上有一个新提交M,feature上有两个新提交C和D。 image.png

此时切换到feature分支上,执行如下命令,相当于是想要把master分支合并到feature分支。

git checkout feature 
git rebase master // 这两条命令等价于git rebase master feature

下图为变基后的提交节点图,解释一下其工作原理:

image.png

这里要记住什么是待变基分支和基分支

  • feature:待变基分支、当前分支
  • master:基分支、目标分支

官方解释:当执行rebase操作时,git会从两个分支的共同祖先开始提取待变基分支上的修改,然后将待变基分支指向基分支的最新提交,最后将刚才提取的修改应用到基分支的最新提交的后面。

结合例子解释:当在feature分支上执行git rebase master时,git会从master和featuer的共同祖先B开始提取feature分支上的修改,也就是C和D两个提交,先提取到。然后将feature分支指向master分支的最新提交上,也就是M。最后把提取的C和D接到M后面,但这个过程是删除原来的C和D,生成新的C’和D’,他们的提交内容一样,但commit id不同,feature自然最后也是指向D’。

rebase的解释

rebase字面意思就是"变基",可以直接理解为改变基底。

feature分支是基于master分支的B拉出来的分支,feature的基底是B。而master在B之后有新的提交,就相当于此时要用master上新的提交来作为feature分支的新基底。实际操作为把B之后feature的提交存下来,然后删掉原来这些提交,再找到master的最新提交位置,把存下来的提交再接上去(新节点新commit id),如此feature分支的基底就相当于变成了M而不是原来的B了。

注意,如果master上在B以后没有新提交,那么就还是用原来的B作为基,rebase操作相当于无效,此时和git merge就基本没区别了,差异只在于git merge会多一条记录Merge操作的提交记录。

上面的例子可抽象为如下实际工作场景:张三从B拉了代码进行开发,目前提交了两次,开发到D了;李四也从B拉出来开发了并且开发完毕,他提交到了M,然后合到主干上了。此时张三想拉下最新代码,于是他在feature分支上执行了git rebase master,即把master分支给rebase过来,由于李四更早开发完并合了主干,如此就相当于张三是基于李四的最新提交M进行的开发了。

实际git提交示例

在使用git的日常开发过程中,经常遇到如下情况:

1、某个时间你通过git clone/git fetch拉取了远程仓库代码到本地仓库

2、开始本地调试并开发某个功能,经过几天的奋斗,终于功能调试好了

3、当你开心地准备通过git push分享你的成果时,遇到git push失败,提示本地代码非最新,需要先更新本地代码

image.png

说明如下:

1、o/master为远程分支,master:本地跟踪分支

2、c0、c1是从远程仓库取到本地的,故o/master指向c1

3、c2代表别人提交的代码,c3是你准备git push的本地提交

4、左边实线圆圈是本地仓库,右边虚线圆圈是远程仓库

别怕,git早就想到了,而且给出了两种不同的解决方案:

第一种:git pull --rebase

git fetch
git rebase o/master

git fetch会基于c1,从远程仓库中拉取c2到本地。

git rebase o/master, 这是一个变基操作,首先将master变基到o/master,这样就会在o/master分支上多了一个c3'(c3的副本),再将master指向c3', 具体效果如下图所示:

image.png

这样再git push即可,且push之后远程仓库结果如下:

image.png

第二种:git pull

git pull 一般不建议直接使用,因为该命令往往会产生意想不到的后果,让你后悔不已,我们尽量使用下面两个命令来代替:

git fetch
git merge o/master

git merge o/master, 这是一个合并操作,与变基操作不同,git只会新创建一个节点c4,然后使c2和c3同时作为c4的父节点,然后使master指向c4,具体效果如下:

image.png

这时再次git push即可,但是push之后远程仓库结果如下:

image.png

所以尽量使用git pull --rebase,这样可以使远程仓库提交历史更清爽。

推荐使用场景

  1. 拉公共分支最新代码的时候使用rebase,也就是git pull -r或git pull --rebase,但有个缺点就是rebase以后我就不知道我的当前分支最早是从哪个commitid拉出来的了,因为基底变了嘛。(如果使用merge,多出无意义的一条提交记录"Merge … to …"

2、往公共分支上合代码的时候,使用merge。如果使用rebase,那么其他开发人员想看主分支的历史,就不是原来的历史了,历史已经被你篡改了。比如张三和李四从共同的节点拉出来开发,张三先开发完提交了两次然后merge上去了,李四后来开发完rebase上去(注意:李四需要切换到主分支,然后执行git rebase,然后再git pull到远端),则李四的新提交变成了张三的新提交的新基底,本来李四的提交是最新的,结果最新的提交显示反而是张三的,就乱套了