Git Rebase

Posted Time-Traveler

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Git Rebase相关的知识,希望对你有一定的参考价值。


初始化(windows上望见谅):

首先初始化一个项目,由于在github上已经建好了,这里直接clone下来,是个空repository,没有任何提交。


新增111.txt文件,并写入初始信息:

提交master:

git add .\\111.txt
git commit -m "init"
git push

从此签出dev分支,并推到服务器上

git checkout -b dev
git push origin dev

再签一个feature分支出来

 git checkout -b feature
 git push origin feature

此时分支情况如图所示:

Rebase 示例:

新的需求来了,需要开发一个新的功能,我们打算在feature分支上开发,这个需求很简单,就是在111.txt中加入一句 feature就完成开发了,但是这个开发过程却破天荒的花了你一周时间(这么好的老板哪里找)


命令如下:

git add .\\111.txt
git commit -m "feature"
git push    //git push --set-upstream origin feature 方便直接push

在开发的过程中,测试发现了一个非常严重的bug,为了不耽误你开发进度,你的同事不得不接过这个艰巨的任务,此时从dev拉了一个bugfix分支。

git checkout dev
git checkout -b bugfix
git push --set-upstream origin bugfix

新增一个文件222.txt,写入bugfix及修正了这个非常严重的bug,同事花了一天时间修好了bug,此时你还在绞尽脑汁的想新功能如何实现。

git add .\\222.txt
git commit -m "fixbug"
git push

此时分支情况如图所示:

既然bug修好了,那肯定要合并到dev啊,这里用rebase,不用merge,好了终于步入正题了。

git checkout bugfix
git rebase dev
git checkout dev
git merge bugfix
git push

此时再看分支图:

放到dev上去看看
转眼间一周时间过去了,你掉了一大把头发,终于把新功能写好了,如释重负,然后想着同事修好了bug,我也得把同事的代码合并进来吧,毕竟开发的时候dev还是有bug的。

git checkout feature
git rebase dev
git checkout dev 
git merge feature
git push

再来看看分支情况:


看看提交记录:

一条线,very nice!!!!


Rebase导致的问题:

dev分支算是完美了,那我们再切回到feature分支看看


咋一看貌似没什么问题,我直接push一下,毕竟前面只是rebase 了 dev,执行git push,走你

什么东西?让我pull,大家也都知道 git pull = git fetch + git merge,既然是merge 那岂不是又要多了一个merge的节点?再回头看看Your branch and ‘origin/feature’ have diverged是什么意思,网上大致查了一下,说是分叉,我rebase了一下还不让我提交了?怎么还分叉了?当然也查到了解决方法 强制提交 --force,–force-with-lease,git pull --rebase貌似也行。那为什么会这样呢?那就要知道rebase干了些什么了。

Git rebase 会回到两个分支(你所在的分支和你想要衍合进去的分支)的共同祖先,提取你所在分支每次提交时产生的差异(diff),把这些差异分别保存到临时文件里,然后从当前分支转换到你需要衍合入的分支,依序施用每一个差异补丁文件,文件在.git/objects/pack中,及撤销feature的提交,将dev中的提交应用到当前分支,最后在加上feature的提交,加上的这个commit跟撤销的commit内容是一样的,但是hash,commitid不同,本质上还是不同的commit

这就是为什么会分叉的原因了,相当于最开始是这样:

o ---- o ---- A ------- B  dev, origin/dev
               \\                       
                C  origin/feature 

rebase之后:

o ---- o ---- A ---------------------- B  dev, origin/dev
               \\                        \\
                C  origin/feature 		 C` feature 

origin/feature是最初提交的,也就是rebase之前,rebase后本地feature被更改,而git push默认是远程分支能做fast-forward保持一致的,现在有些不同了,这也就是为什么会被拒绝的原因了。此时如果是你一个在用这个feature分支,没有别人在feature分支上签出新分支,那么强制覆盖可以push上去。万一有人签了新分支,就不能强制覆盖,否则就只能祝你好运了。

Rebase的风险:

奇妙的衍合也不是完美无缺的,一句话可以总结这点:

永远不要Rebase(衍合)那些已经推送到公共仓库的更新

如果你遵循这条金科玉律,就不会出差错。否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

在rebase的时候,实际上抛弃了一些现存的 commit 而创造了一些类似但不同的新 commit。如果你把commit 推送到某处然后其他人下载并在其基础上工作,然后你用 git rebase 重写了这些commit 再推送一次,你的合作者就不得不重新合并他们的工作,这样当你再次从他们那里获取内容的时候事情就会变得一团糟。

下面我们用一个实际例子来说明为什么公开的衍合会带来问题。假设你从一个中央服务器克隆然后在它的基础上搞了一些开发:

现在,其他人进行了一些包含一次合并的工作(得到结果 C6),然后把它推送到了中央服务器。你获取了这些数据并把它们合并到你本地的开发进程里,让你的历史变成类似下图:

接下来,那个推送 C6 上来的人决定用衍合取代那次合并;他们用 git push --force 覆盖了服务器上的历史,得到 C4’。然后你再从服务器上获取更新:

这时候,你需要再次合并这些内容,尽管之前已经做过一次了。衍合会改变这些 commit 的 SHA-1 校验值,这样 Git 会把它们当作新的 commit,然而这时候在你的提交历史早就有了 C4 的内容:

你迟早都是要并入其他协作者提交的内容的,这样才能保持同步。当你做完这些,你的提交历史里会同时包含 C4 和 C4’,两者有着不同的 SHA-1 校验值,但却拥有一样的作者日期与提交说明,令人费解!更糟糕的是,当你把这样的历史推送到服务器,会再次把这些衍合的提交引入到中央服务器,进一步迷惑其他人。

如果把衍合当成一种在推送之前清理提交历史的手段,而且仅仅衍合那些永远不会公开的 commit,那就不会有任何问题。如果衍合那些已经公开的 commit,而与此同时其他人已经用这些 commit 进行了后续的开发工作,那你有得麻烦了。

参考:

1. https://blog.csdn.net/trochiluses/article/details/14451777 2. https://blog.csdn.net/bigmarco/article/details/7842642

以上是关于Git Rebase的主要内容,如果未能解决你的问题,请参考以下文章

【Git】rebase 用法小结

Git打开默认git编辑器而不是VIM或任何其他编辑器(Git Rebase)[重复]

Git rebase使用

git rebase VS git merge

合并代码还在用 git merge?我们都用 git rebase

git rebase简介(高级篇)