1

自从 Git 出现之后,分支管理就深入人心。但是随着我们团队在合并 master 分支时,开始优先采用 squash merge,事情还是有了变化。我也开始采用另一种不同于传统开发模式的分支合并方法。在此我简单撰文阐述一下。

这个模式还是争议很大的,文末我也列举了很多不适用的情况,还请读者不吝提出质疑。


为什么使用 squash merge?

首先,我们可能需要解释一下,为什么我们采用 squash merge 而不是传统的 merge 合并代码。直接原因很简单:为了保持 master 分支的纯净和简洁。Master 分支所应该表现的,就是我们因应各种明确的需求、优化、bug 修复所进行的代码变化。随着 master 版本的逐步前进,我们可以窥探到代码生长的过程。

在团队开发中,一般来说大家遵从的 Git 使用方法是这样子的:

  1. master 分支作为发布分支,原则上发不到生产环境的代码都应该基于 master 分支
  2. 每个人管理至少一个开发分支,开发分支与具体的需求绑定;新的需求开新的分支;开发分支开发完成后,合并到 master 分支

但是,作为代码分支,其过程可简可繁,针对 master 分支,我们重点关注的是为了完成一个需求,代码做了哪些变更,但是在开发者开发过程中,这个分支不可能只有一个提交点,特别是发现了 bug 之后,肯定也会调整逻辑再 commit 一次。对于这些很快就发现并在上线之前就修复掉的 commits,我们并不需要它出现在 master 分支中。实际上我们的解决方法是:

  1. 控制 master 分支权限,所有分支均需要通过 MR / PR 才允许合并
  2. 通过 MR / PR 后,默认使用 Squash Merge 模式进行合并

Squash 合并模式,又称为 “压缩合并”,会将分支的所有提交点(commit)合并成一个,然后再合并到 master 分支上。当然,这种模式也是有前提的:

  1. 每一个 MR 尽可能原子,就是为了完成一件事情而提交一个 MR,不夹带与 commit 描述无关的私货
  2. Git 系统支持将 MR / PR 单进行关联,这样一来即便 master 分支损失了一些细节,依然可以打开具体的 MR / PR 单来获取信息。

Develop / test 分支的管理

上面我只讲了 master 和个人需求分支的管理方式。但是在团队开发中,往往有很多开发者、很多需求,大家一起共用一个开发 / 测试环境,为了能够共用,那么各位开发者往往会再创建一个 develop 分支,大家把自己的开发进度,如果自测通过,那么就合并到 develop 分支上,这样以确保同一个环境都能够包含多人都包含的 feature。

然而,这种方法,其实也遇到了我们推崇 squash merge 时所要解决的同样的问题:我们是不是需要关注那么多的过程信息?显然,即便未合并到 master 分支,那么对于一个共用分支来说,也是不需要的。很大程度上,所谓的 develop 分支仅仅是一个用来包容所有已开发 / 测试代码的路径而已,网上推代码的开发者们压根就不关注其变更历史。为此,在第五人的开发过程中,我提出了另一种分支模式,我称为 rebase & squash 模式。

这个模式的具体操作方式,我们以下面的例子,对比传统模式来说明一下吧:

Rebase & Squash 模式的操作模式

创建和开发分支

首先,我们手头有一个 master 分支。因为我们采取了 squash merge 的模式,因此这个分支非常纯净,就是一条单纯的曲线

假设张三和李四分别需要开发一个分支,那么他们自然就是从最新的 master 分支中 checkout 新的 feature 分支,进行开发

02.png

合并分支

与此同时,还有其他同学也在开发分支,并且合并到了 master,因此大家的分支都在往后生长。

到了某个时间节点,张三扯着嗓子吼一声:“开发环境我用一下噢!”这个时候李四也说:“我也要用,帮我发一下”。张三看到 master 分支已经发生变化了,于是张三从 master 分支 checkout 了一个 develop 分支,并且把自己和李四的代码分支都合并到分支上。

03.png

rebase & squash 模式下也是一样的,但不同的是,传统模式下,develop 分支创建之后,会一直存在于远端,继续合并;而 rebase & squash 模式下,这个分支只会临时地存在于远端,当完成了流水线的编译、发布之后,就将分支从远端删除。我们将这种临时分支表上虚线框以示区别。

04.png

公共开发分支演化

张三合并了分支,发布到环境上调试。哎,发现有 bug,这就需要修改代码了。OK,张三在自己的 feature 分支上改了代码,然后再合并到 develop 分支:

05.png

rebase & squash 模式下,之前的临时分支已经删除了,那么只是重复一下上一次的操作,再创建临时 develop 分支,用完即弃就行了。

06.png

这个时候,两种模式下 develop 分支的复杂度差异已经可以看出端倪了。

引入新开发分支

这个时候,王五也加入来参与开发了,ta 自然是创建了一个新的分支。等到王五开发完毕,也准备用到统一环境的时候,王五也喊了一声:“开发环境有谁在用吗?”张三李四说:“合并到 develop 分支再发。”王五看了一下 develop 分支,因为李四的开发,develop 分支相比上一个小节,又往前了一个 commit;此外,还因为 master 分支往前也走了一步, 所以 master 分支的变动也被某位同学合并到了 develop 分支,以确保 develop 分支不能落后于线上版本。

不过这并不影响王五的操作方案,于是王五把自己的分支往 develop 一合,再提交了一个。

07.png

rebase & squash 模式下,还是老三样,直接从 master 拉出临时分支,然后王五扯嗓子喊一声:“开发环境谁在用?我要合哪些分支?”这个之后张三李四说:“我们建了一个共享文档,你就按照文档上的分支合就行。”于是王五把自己的分支和张三李四的分支都合并、编译、发布,然后删除临时分支。

08.png

Rebase

在传统模式下,个人的分支,一旦被别人 merge 了,或者是 merge 到别的分支了,那么这个个人分支就不能乱动,不能轻易进行压缩、rebase 等破坏分支链一致性的操作,否则进行了修改之后的分支,会被视为新的分支。比如张三修了 bug 之后,觉得自己的分支太复杂了,好多叫做 “dev”、“bugfix” 的 commit message,哎合并一下算了。但是合并了之后,重新 merge 到 develop 分支的时候,张三发现多出来一条旁路了:

09.png

rebase & squash 模式下,个人的分支,被视为个人在工作层面的 “私有财产”,这里的 “私有” 指的是,分支的所有权属于个人,个人可以对这个分支作任何操作,如果觉得分支过于难看的话,完全可以自作主张进行合并,也可以为了跟上 master 分支的进度,进行 rebase 操作,这在这个模式下是极为常见的操作:

10.png

MR / PR

我们假设,张三完成了开发、测试,并且提交了 MR / PR。通过了之后,通过系统成功压缩合并到了 master 分支。那么在传统模式下,尽管张三删除了远端名为 feature/zhangsan 分支,但由于 Git 的分支特点,张三原来的那个开发分支的支架,依然存在于 develop 分支上:

11.png

那么基于 develop 应该不落后于 master 分支的原则, 需要再执行一次合并:

12.png

rebase & squash 模式下,事情就没那么复杂了,张三的分支合并到了 master,其他的分支你们随意,能保持最新的话,就 rebase,不急的话也没问题。张三的开发分支,也不会在其他的分支中留下痕迹。

13.png

新一轮的开发

这个时候,李四也要进行调试了,我们对比一下两种模式下,分支的变化:

传统模式:

14.png

对于一个成熟的开发团队而言,开发分支是动态的,不停滚动前进的。如果采用传统模式,很难遇到某一个时间点,develop 分支上的 MR 都合并入 master,从而可以删除并开启全新的分支。因此,develop 分支的混乱交叉,会在远端长时间地存在,并且实质上,这些分支的历史信息,一点意义都没有。

rebase & squash 模式:

15.png

采用这种模式,我们将关注的重点聚焦于各特性分支,而不是分支的合并上。分支也能够一直保持最新和整洁。


冲突处理

有经验的同学估计很快就能发现一个关键问题点:如果分支发生冲突了怎么办?

传统模式下,分支的冲突处理可以轻易地通过 merge 来解决,但是在 rebase & squash 模式下,rebase 天生就带来重复解决冲突的副作用。

首先最理想的情况是,冲突点尽快合入 master 分支,然后相关的分支重新 rebase master。但实际操作中,冲突点可能无法快速解决,这个时候,这个模式也是有解决方法的。

引入冲突解决分支

我们回到张三李四的场景。我们假设发生了冲突:

16.png

首先,解决冲突的时候,冲突的当事双方必然需要进行协商,然后选定解决冲突的方案,这即便是传统的分支模式也不例外。此时,我们应该找到冲突点,然后基于冲突点,执行 merge 并解决冲突,生成一个基准分支

17.png

然后,将这个基准分支,基于 master 进行 rebase 和 squash 操作,合并为一个提交点(或者想要保留之前的 commit,其实也行):

18.png

然后,相关当事人基于这个新的基准分支,将自己的分支进行 rebase 操作:

19.png

回归正途

有了基准分支之后,当事分支将自己的基准分支改为这个新的基准分支。基准分支也可以随时跟着 rebase master:

20.png

基准分支发生变化,特性分支也可以选择跟随,也可以选择不跟随,其实影响不大。

21.png

而如果引入了新的开发者王五的时候,王五并没有什么冲突的压力,ta 依然可以按照原本的模式,正常从 master 分支拉出来干活,并且合并别人的分支:

22.png

基准分支的删除

基准分支是为了解决冲突而产生的,不希望长期存在。当由基准分支派生出来的任意一个特性分支通过了 MR / PR,并且压缩合并入 master 的时候,我们就可以考虑基准分支的删除操作了。这个删除可以由刚刚合入代码的开发同学来负责。我们先看看分支合并以后的状态(灰色分支表示不存在了):

23.png

这个时候,另一个特性分支,只需要 rebase master,就可以回归了,毕竟冲突点早就合入 master 了,世界重归宁静。


适应症

在食品领域,抛开剂量谈毒性就是耍流氓;在软件工程,抛开场景谈应用也是耍流氓。我提的这种模式,并不是要革传统分支模式的命,在统一 squash merge 模式的大背景下,两种模式最终的走向都是保持主干分支的简洁可读,同时解决团队开发的问题。

就我个人的实践经验来说,rebase & squash 模式比较适合以下场景:

  • 项目团队规模比较小,针对同一个模块的开发者基本不超过三四人
  • 代码冲突不是常态,平均一个星期需要解决的冲突在一两次以下
  • 框架成熟,大部份需求开发规模相对较小;或者是即便规模大,但基本上也都是一到两个同学负责这一需求
  • 需求多,同时迭代速度快,经常大量需求同时转测
  • 项目需求相互之间关联度低,或者是主要是前后依赖关系,很少有交叉依赖

反过来,我们也可以列举出不适合这种分支模式的情况:

  • 项目团队规模比较大,针对同一个木亏的开发者经常达到十几甚至几十人——这种大团队自然有大团队的开发模式,并且有专职的项目工程师。这种情况下,我们应该遵从项目工程师的管理模式进行开发
  • 经常需要解决代码冲突
  • 项目处于早期开发模式,这个时候框架、技术方案之类的都不成熟,那么代码的变更虽然混乱且频繁,但有时候为了便于复盘,我们有必要进行比较完善的保留
  • 需求数量相对比较少,但每一个需求的规模都比较大,每次变更都会涉及大量代码的测试、提交、合并,并且非常重视版本
  • 需求关系复杂,互相耦合的需求同时开发是常态

不过其实,我觉得两种模式并不是你死我活的关系。即便是针对传统模式或者是其他的分支管理模式,本文提出的这种模式也是可以在子模块的小团队中采用的。比如说,小团队开发阶段采用 rebase & squash,但最终合并到大项目的关键分支中则采用项目模式。


写给 scrum master 们

虽然我提出了这种模式,不过我个人是从未强制其他人 follow。原因嘛其实也挺明显,且不说这个模式解决冲突时的麻烦,据我了解大部份开发者并不特别在意保持与 master 分支的更新,而主要关注自己开发中的分支。

这个模式的个人喜好特征非常明显。我个人就一直使用这种混合模式对自己的代码进行管理,同时参照团队模式进行合并。其实本质上,就是如何选取基准分支的问题——master 分支也可以是相对的,在不同的场景下,我们开发中可以视另一个分支为我们的基准分支,那么 rebase 其实也就是另一种 squash merge 而已。

另外一种场景是推荐给 scrum master 同学,特别是需要清晰掌握各开发分支状态的 scrum master。这种模式可以非常方便 scrum master 清晰掌握项目中各分支的进展。但是在这种模式下,scrum master 也应该承担起分支合并冲突处理以及基准分支的管理职责。

Anyway,这是我个人在开发过程中摸索出的一种新玩法,分享出来,是请开发者批判性地学习这种模式啦。


本文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

原作者: amc,原文发布于腾讯云开发者社区,也是本人的博客。欢迎转载,但请注明出处。

原文标题:《一种邪道的 Git 整洁之法——rebase & squash》

发布日期:2024-11-25

原文链接:https://cloud.tencent.com/developer/article/2470889

CC BY-NC-SA 4.0 DEED.png


amc
927 声望228 粉丝

电子和互联网深耕多年,拥有丰富的嵌入式和服务器开发经验。现负责腾讯心悦俱乐部后台开发