抛出问题
- 多人对同一feature分支进行开发如何最有效、简洁?
这是最近我一直在思考的问题。这个场景有什么问题呢?先来看看通常的做法。一般一个人会对应一个分支来对feature进行开发,与IT不同的是。IC将分配不同的module分配给不同的前端设计师来进行设计,然后再由验证师来进行验证。说到这表面上还很和谐,并没有什么不妥。针对一些小的模块验证师只需要拉取设计师的分支,然后在自己的分支进行验证就可以了。
但这恰恰就是问题的本身,假如随着设计规模的增加,需要多人对一个设计进行验证。设计师的分支,和多个验证师各自的分支,更进一步则是多个设计师的分支。这样将会导致整个提交历史分支众多和merge的点众多,使得版本提交变得难以阅读、回溯。形成“钻石链”,而非线性的提交历史。
![f72985361bdcc096076c9c88dc16a33b.png](https://img-blog.csdnimg.cn/img_convert/f72985361bdcc096076c9c88dc16a33b.png)
接下来就是我查阅资料并结合自己的一些想法所诞生出来的一个work-flow(工作流),以及Git中一些知识点的分解归纳。
冲突详解
GIt合并中的细节
要想了解冲突,就必须得了解合并中到底发生了什么事。可以通过下面的图片和文字了解整个经过
- Step1:发起合并操作,将分支A的0.2版本和分支B的0.2版本进行合并操作(当然也可以是0.2和0.1版本)
- Step2:假如这两个分支的版本恰巧对同一个文件进行了修改(没有修改同一行代码)
- Step3:Git会找到分支A、B两个版本的共同祖代提交
- Step4:比对其中的差异,可以看见A、B做了各自正确的修改
- 在祖代版本中只有PAST是正确的(PAST:Intel)
- A、B分别提交了各自正确的修改
- Step5:A相较于祖代只对future进行了修改,B相较于祖代只对present进行了修改
- Step6:完成merge,得到最终的代码
![ce7d58fcc1d5fd495be79235b3043132.png](https://img-blog.csdnimg.cn/img_convert/ce7d58fcc1d5fd495be79235b3043132.png)
冲突类型
明白了合并中发生了什么事情,那么接下来就是了解冲突了。
从上一小节中可以看到在不对同一行代码进行修改的情况下,merge能够自动完成。那么当A、B两个分支对同一行代码进行了修改呢?显然Git不能够自动识别到底应该保留哪部分代码。此时就需要人手动解决,这就是Git中的——冲突。当然这只是最简单的编辑冲突,此外还有更加复杂的内容冲突。
编辑冲突
使用上述代码来模拟冲突场景,下图为初始版本的三个代码。
![5d5f0c46bf16b52dfc065c6ea35dad08.png](https://img-blog.csdnimg.cn/img_convert/5d5f0c46bf16b52dfc065c6ea35dad08.png)
分对A和B分支上的该文件的PRESENT作出修改,如下图
- branch_A:PRESENT:Intel; → PRESENT:my_cpu;
- branch_B:PRESENT:Intel; → PRESENT:xxx_cpu;
![20cb5a3b1a2b35389939b3e5453f4e3c.png](https://img-blog.csdnimg.cn/img_convert/20cb5a3b1a2b35389939b3e5453f4e3c.png)
现在将A、B这两次的修改进行合并,执行如下语句(处于A分支):
git merge A B
![6f2ba967e2353892877c9098d42697d9.png](https://img-blog.csdnimg.cn/img_convert/6f2ba967e2353892877c9098d42697d9.png)
可以从上图中,看出冲突出现了。在冲突文件中Git保留了两种修改,交由使用者来选择到底保留哪一种修改。需要注意以下几点:
current change指的是分支A上边的修改
上方按钮可以快速选择到底保留哪个版本的代码和进行修改比较,不用手动修改
将处于merging的状态,只有处理掉冲突后才能进行 切换分支等其他操作。如进行则报错,如下图
image-20201021140728808
这几只要选择一个你想要保留的版本即可,关于快速处理冲突的方式还有很多,以后再详谈。最好的方式就是别出现冲突,而不是是冲突了再去花费额外的时间来解决冲突。
内容冲突
这种冲突的情况更加复杂,但是只要使用者本身的codeing习惯足够好也能够很好的避免。这种冲突最麻烦的地方在于Git并不能将其识别出来
比如在验证中,如果环境中定义了某个类的方法,两人都对该方法做了些许修改。如果能够出现同行修改的编辑冲突还好,能够很快得知此处的内容冲突。但是如果刚好巧妙地避过了同行进行修改,在merge的时候其将会正常的进行,但是会得到一个有问题的代码。此时最好的解决办法merge完成后设置脚本自动进行静态代码检查。
多人同分支开发的另外一种work-flow(工作流)
其实这个work-flow很简单,就是将公共的设计分支或者验证分支当成master来使用,在pull的过程中使用rebase(变基)的方式来进行merg其他人的修改。这样能够最大程度的保持公共的设计分支或者验证分支处于“线性链”的状态,而不是“钻石链”。不会merge到master上之后显得整个commit graph很是臃肿,难以阅读。
什么是rebase(变基)?
rebase(变基)(别想歪了,这是个正经的直译名词),对于才开始使用Git的同学来说,一般听到这个名词都会冒出一个声音“啥玩意儿???”。一般源于对Git中的commit版本记录没有一个直观的认识。当然这也很简单,直接使用git graph来查看就行了。如下图
- graph可以直观的展示各个分支的提交和合并等等,以及版本的演进
- 在显示效果上,VSCode的Git Graph插件远好于Git自带的graph工具
![aebd9037d2689ef192c87b17972a0a1c.png](https://img-blog.csdnimg.cn/img_convert/aebd9037d2689ef192c87b17972a0a1c.png)
可以看见上一实例节的实例在合并之后,整体结构已经是明显的“钻石链”的结构了。从这个git graph上看还能接受,但你要知道的是在实际的项目中不但有为各自设计、验证创建的开发分支,还有bug分支等等各种奇奇怪怪的分支。当这些分支都挂到了master上之后,一眼望去将只能看见merge “xxx” into “master”的merge自动生成的commit注释。这在版本跳回、回溯的时候比不清晰的commit注释还让人捉急。所以这时候就需要使用rebase(变基)来净化历史,让其尽可能地线性。
![cf3b855e83e4d8a1dc0649736b692f5e.png](https://img-blog.csdnimg.cn/img_convert/cf3b855e83e4d8a1dc0649736b692f5e.png)
Work-Flow
这个工作流地具体使用模型如下图所示,其跟一般的git-flow的差异是:
其是在一个公共分支下进行开发
在pull的时候需要加上--base参数
![edf73ba5adcd2108d34c2d4a6f801e4b.png](https://img-blog.csdnimg.cn/img_convert/edf73ba5adcd2108d34c2d4a6f801e4b.png)
接下来,用上面的例子来进行测试,原始代码版本如下图
![5d5f0c46bf16b52dfc065c6ea35dad08.png](https://img-blog.csdnimg.cn/img_convert/5d5f0c46bf16b52dfc065c6ea35dad08.png)
初始commit graph
![23eca680012f30a8ae900c78264e413e.png](https://img-blog.csdnimg.cn/img_convert/23eca680012f30a8ae900c78264e413e.png)
可以看见graph中各个分支已经开始独立推进,一旦发生merge,就会很自然的形成“钻石链”。
这里的master代表的是公共分支
在master上使用git pull --rabase来拉取其他分支的修改
先拉取分支A的修改
整个过程顺利进行
git pull --rebase origin A
![072f4b31ea99028083d1a54a475da444.png](https://img-blog.csdnimg.cn/img_convert/072f4b31ea99028083d1a54a475da444.png)
下图为拉取完毕后的commit graph的样子
可以看见A的commit节点和master的commit节点已经同色且处于同一条line上了
![ff573e3123bb07e05e58bb47ac29277e.png](https://img-blog.csdnimg.cn/img_convert/ff573e3123bb07e05e58bb47ac29277e.png)
- 同样的方法拉取分支B的修改,并处理rebase(变基)的冲突
- 跟merge一样还是来到冲突的文件夹里,处理冲突
![b1fb8a764735411da1e53f52d2785cbe.png](https://img-blog.csdnimg.cn/img_convert/b1fb8a764735411da1e53f52d2785cbe.png)
- 冲突处理完毕,如下图
![676d87962511ccff7e282a7e9e554f87.png](https://img-blog.csdnimg.cn/img_convert/676d87962511ccff7e282a7e9e554f87.png)
- 完成冲突处理(这里跟 merge有所不同)
①首先,add处理完冲突后的文件
![1469a3f0266472a487919cc95e831ab6.png](https://img-blog.csdnimg.cn/img_convert/1469a3f0266472a487919cc95e831ab6.png)
②使用git rebase --continue完成变基操作
![bf4a50981d8a62231b49ebb4f05e9608.png](https://img-blog.csdnimg.cn/img_convert/bf4a50981d8a62231b49ebb4f05e9608.png)
pull --rebase 分支B后的graph
可以看见,分支A、分支B和master的commit节点已经都来到了一条line上,整体简洁、易读了很多
![011c35ec017061e7503779afe04853df.png](https://img-blog.csdnimg.cn/img_convert/011c35ec017061e7503779afe04853df.png)
图为最终的代码
![e35e0db3236692f5b82bda16cab24f8f.png](https://img-blog.csdnimg.cn/img_convert/e35e0db3236692f5b82bda16cab24f8f.png)
至此已经完成了这个work-flow的实验
总结
这个work-flow的使用是比较局限的,但也可提供一种commit graph的整理思路。不管本地的分支版本如何混乱,每个设计师/验证师都得保证自己在远程库提交出来的是一个简洁明了且内容正确的commit graph结构。不管是回溯、版本回退,甚至是之后的复用都有利的。
参考资料
- 《Git学习指南》【作者】René Preißel(普莱贝尔)/Bjørn Stachmann(斯拉赫曼)【译】凌杰 姜楠
![e2987748303d17b32127c36d4cd4b1df.png](https://img-blog.csdnimg.cn/img_convert/e2987748303d17b32127c36d4cd4b1df.png)