初始化(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 进行了后续的开发工作,那你有得麻烦了。