git rebase,顾名思义即变基,不过这是一条多功能命令,既可以重建提交历史,还可以整合不同分支间的变动。本文将对相干功能进行先容。
一、整合不同分支间的变动
该功能也即rebase的本意:变基。
1. 功能原理
当实行rebase操作时,git会从两个分支的共同祖先开始提取待变基分支上的修改,然后将待变基分支指向基分支的最新提交,最后将刚才提取的修改应用到基分支的最新提交的后面。
2. 使用方法
- git rebase [基节点]
- git rebase [基节点] [待变基节点]
复制代码 这里可以看到rebase后面的参数可以是两个,也可以是一个,当rebase为一个参数的时候实在是省略了第二个参数,第二个参数为HEAD指针当前指向的那个节点。
3. 使用场景:
在一个项目中,你们的代码经过多次开发终于上线了。为了便于阐明,我们先假设全部的代码提交都是在主分支 main 上进行的(主分支为什么不叫master?GitHub改版之后的默认分支名改成main了,这里与时俱进一下),而且已经有了C0,C1,C2三个节点的提交(这里的节点名C0用以代指相干commit操作完成后得到的那个HASH值,下同)。
此时你的leader想到了一个好的功能点,并专门指派你去将这个功能开发出来。与此同时,main 分支上仍有其它小伙伴进行更新提交。
于是,过了一段时间,你们的git分支大概被整成了如许:你在你的分支上一次提交了三次:C3,C4,C5。你们的主分支在此期间更新了C6,C7,C8三个节点
现在要进行功能合并:将你开发新功能的feature分支合并到主分支上。此时你可能会脱口而出:“merge一下不就好了吗?“
的确。如果你之前险些没用过这个命令的话,这是最自然而然的一种做法。但是今天我们要聊的是rebase,接下来先看一下使用rebase是怎么解决的:
(1)首先切换到feature分支:
(2)然后实行变基操作:
- git rebase main
- #此处我们使用了带有一个参数的命令
- #如果使用带有两个参数的命令则是:git rebase main feature
复制代码 操作后的结果是如许的:
原来的分支已经不存在,而是以main分支的最新节点为基,通过“嫁接”的方式迁移到main分支
根据前文所描述的操作原理:
- 首先,它会找出当前所在分支和变基分支的共同Parent,即C2;
- 然后,将当前分支中C2之后节点的快照暂存,即:C3,C4,C5;
- 最后,将刚刚暂存的快照应用到基分支main的最新提交的后面,便得到了上图。
必要注意的是这里的第三步一般会产生辩论,这时候就不是简朴的迁移了,而是先将待迁移节点(这里为C3,C4,C5)依次逐个与最新节点进行辩论合并.
比如这里第一次辩论是C3与最新节点C8辩论,解决辩论之后得到节点C3`,而得到的这个节点此时也成为了最新节点。之后继承拿C4,C5节点重复此动作依次得到最新的节点。
即便是这里不会发生辩论,迁移后的节点commit的哈希值也已经发生变化,所以相应的节点才叫C3`,而不是C3。
也正是出于这个原因,一般建议是在待合并分支上(此例中为feature)进行rebase操作,而不是在主分支上,最后切换到主分支merge已往即可。原因是有时候可能会通过HASH值去回溯节点,如果在主分支进行了此操作,则HSAH值会发生变化,不利于回溯。
实在在使用者的视角里这里的整个过程说白了就像是把你feature分支上的节点依次重新更新到主分支上一样
如果接纳merge呢?
- git checkout main
- git merge feature
复制代码 操作结果如下:
可以看到:在主分支main后面天生了一个新的节点C9(天生新节点也同时意味着这里会有一条日记:Merge XXX from XXX)。
底层的操作原理为:先将主分支的最新节点C8的快照,feature分支最新节点C5的快照与两个分支共同Parent节点C2的快照实行三路合并天生新的快照,并基于此快照天生一个连接主分支与feature分支的节点C9,最后调整main的指向。
4. rebase VS merge
4.1 异同
首先二者都具备整合分支间变动的能力,但二者的实现手段却大不类似。git merge总是在推进提交历史,并不会影响提交的原始状态,而git rebase整合变动的方式则是对提交历史进行重写,但从结果上看,最后git rebase形成的节点C5`与git merge形成的节点C9完全类似。
4.2 优缺点
长处:
rebase不产生新节点,当然也不会产生新的commit日记,但是merge过程中会产生一条险些“无用”的Merge日记。使用rebase操作的最大利益在于你可以让项目提交历史变得非常干净整洁。首先,它消除了git merge操作所需创建的没有必要的合并提交。其次,rebase会造就一个线性的项目提交历史——也就是说你可以从feature分支的顶部开始向下查找到分支的起始点,而不会遇到任何历史分叉。
rebase产生辩论并合并辩论发生在你操作git rebase时,而合并辩论这个操作是你本身进行的;但是你提交合并申请的时候一般环境下会有评审,由评审者解决辩论,开发者人多的环境下工作量可想而知。
缺点:
rebase操作产生辩论必要依次逐个进行解决,重写提交历史可能引起杂乱,对新手使用也不是很友爱。
4.3 小结
在日常的小规模项目开发中,这种差异险些可以忽略。但是在复杂的多人协作开发场景下,随着项目迭代的不断推进和工程复杂度的不断进步,rebase往往能助力天生相对清新的提交历史,进而方便追溯工程的演进历史和缺陷排查。
不过为了得到这种便于理解的提交历史,却必要付出两种代价:安全性和可追溯性。如果不能遵循rebase的黄金法则,重写项目提交历史会为协作工作流程带来潜在的灾难性后果。再次,rebase操作丢失了合并提交可以或许提供的上下文信息——所以你就无法知道功能分支是什么时候应用了上游分支的变动。
如果使用rebase进行合并,就会出现出比较清新的提交日记,也不会有那么多线,而是一条直线
5. 工作流实战
请注意此过程中的git分支变化图
现在要开发一款游戏,在主分支上有三次提交:
现在让你切一个feature分支去开发交易功能,你开发这个功能履历了三次提交,与此同时主分支上也增长了两次提交:
现在我们在你的feature分支上实行rebase操作:
这里会出现辩论,请注意这里的辩论会出现三次,原因我们前面已经阐述过了:你在feature分支上提交了三次,而变基操作实在可以理解为在现有的基分支的最新节点后面进行更新,所以会将你提交的这三个节点依次与master主分支上的最新节点的内容进行比较,然后解决辩论即可。
我们依次解决这三个辩论:
这里通过图形界面可以看出:提交记录很清新,feature分支通过嫁接的方式已经和master分支合二为一了。但是现在master的指针还没有进行移动,因此接下来我们必要将master的指针迁移到最新的节点:
到这里就完成了一次完整的git rebase操作。
作为对比,如果你使用merge操作将会得到:
会多出一次合并辩论提交,分支看起来没有rebase清新。
二、重建提交历史
1. 使用场景
gitbase 的另一个功能重要用来重建提交历史,什么意思呢?你在当地commit过程中有时候可能刚提交完,然后发现有个很小的地方必要修正一下,而当你修正完之后又得提交,但是这两次提交完成的任务可能一模一样,因为第二次修改的地方实在是微不足道,大概开发一个新功能的时候分多次提交,每次的变动都很小,这个时候就可以使用git rebase的另一个功能:重建提交历史。
2.使用方法
交互式 rebase 指的是使用带参数 --interactive 的 rebase 命令, 简写为 -i。如果你在命令后增长了这个选项, Git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交阐明,提交阐明有助于你理解这个提交进行了哪些更改。在现实使用时,所谓的 UI 窗口一般会在文本编辑器 —— 如 Vim —— 中打开一个文件。
当 rebase UI界面打开时, 你能做3件事:
- 调整提交记录的顺序
- 删除你不想要的提交
- 合并提交。
简而言之,它答应你把多个提交记录合并成一个。
例如:
C2-C5的提交都是为了完成一个功能,现在我想合并这些提交:
如许HEAD指针就会直线退却4个节点到C1,然后列出这个节点之后你想操作的节点,C2,C3,C4,C5,之后你就可以编辑操作这些节点:变动提交顺序,删除部分提交历史等,最后得到结果:
但是你可能也感受到了,这个命令的参数似乎有些抽象。
2.1 参数阐明
上文案例中后面的参数有些看不懂?下面简朴先容一下:
HEAD: 是个指针,当前这个指针指向main,main指向的节点是C5`;
~:可以理解为在本分支上直线退却一个节点。HEAD~就是在HEAD指向的节点处退却一个节点,HEAD~~就是在HEAD指向的节点处退却两个节点,也可以直接写成HEAD~2,二者是一样的。因此,在上例中,HEAD~4 的另一种写法是 HEAD~~~~。
^: 操作符 ^ 与 ~ 符一样,后面也可以跟一个数字。但是该操作符后面的数字与 ~ 后面的不同,并不是用来指定向上返回几代,而是指定合并提交记录的某个 parent 提交。还记得前面提到过的一个合并提交有两个 parent 提交吧,所以遇到如许的节点时该选择哪条路径就不是很清晰了。
Git 默认选择合并提交的“第一个” parent 提交,在操作符 ^ 后跟一个数字可以改变这一默认举动。
举个例子:
这里有一个合并提交记录,C3节点有两个parent。如果不加数字修改符直接切换到 main^,会回到第一个 parent 提交记录。(在我们的图示中,第一个 parent 提交记录是指合并提交记录正上方的那个提交记录。)因此,实行:
HEAD指针会指向C1节点:
这正符合我们的预期。但是当我们换成:
HEAD指针则会指向C2节点,也就是指向了第二个parent的节点:
使用 ^ 和 ~ 可以自由地在提交树中移动,非常给力。
我现在想让HEAD指向C3节点,我该如何操作?
- git checkout HEAD~
- git checkout HEAD^2
- git checkout HEAD~2
复制代码 乃至更简洁一点:
3.工作流实战
接着上面那个小案例,接下来我们开发一个订单体系并提交三次:
然后合并改成一次提交:
接下来你就拥有了修改HEAD指针之后节点的能力:
这里我们按照指示将三次提交合并成一次:
结果为:
相对应的代码内容已经合并:
以上便完成了一次git rebase 合并修改多次commit操作。
三、git pull --rebase
这个功能的使用场景为:多个开发者操作一个分支。
比如开发者A在master分支节点C0将远程代码clone到当地并在当地进行开发,同时B开发者也进行了这种操作,但是B提交的快,B提交完之后master分支产生了新节点C1,A开发者提交的时候由于B在这时候已经做了提交,因此会产生辩论。
如果这时候使用git pull去直接拉取并合并辩论会产生一次偶尔义的合并提交日记节点,而这时候使用git pull --rebase操作,则会将你当地master分支C0节点之后的内容“嫁接”到远程C1节点之后,期间可能会有辩论合并,但是不回产生新的偶尔义的合并节点及日记。
四、总结
回过头来想一下这个命令的这几种用途你就会发现,这些功能说来说去实在归根结底就一个功能:变基。
- 合并多个分支:将你的分支”嫁接“到其它分支上,是变基;
- 合并修改某一节点之后的commit节点:可以理解成将本分支上的某些节点进行变基;
- git pull --rebase 更是一种变基:将当地开发的某一节点之后的内容“嫁接”到最新的节点上。
这个名字起的还是相当贴切的。
文中的相干案例是使用git练习平台创建的,不熟悉git的可以使用这个平台练习一下。
以上便是本文的内容,如果觉得写的还不错还请一键三连,如果觉得有哪些地方写的有误接待评论区大概私信指出,感谢!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |