跳至主要內容

再论Git Flow

Git

再论Git Flow

背景

团队目前使用的 Git 协作模式是:

  1. 对每个功能建立相应的 feat 分支
  2. 上研发、测试、UAT环境时,分别把相应的 feat 分支合并进入长驻 dev/test/uat
  3. 如有冲突,则在本地更新长驻分支 dev/test/uat,merge feat into current branch,之后 checkout 一个新分支,作为 conflict resolved 分支,推送并合并至远程长驻分支

这个模式简单好懂,且业界流行,最直观的好处是,可以满足以下需求:

  1. 某 feat 合并至 dev 后,并不想合并至 test
  2. 某 feat 合并至 test 后,并不想合并至 uat

本文暂且不讨论该交付理念的优劣,毕竟每个团队研发情况、交付理念都不一样。 本文关注的是,在满足上述需求的情况下,是否有更好的分支协作方式。

动机

为什么要寻求更好的方式?因为上述分支协作模式,会导致代码冲突的噩梦:

  1. feat -> dev,解决冲突
  2. feat -> test,又要解决冲突
  3. feat -> uat,还要解决冲突

正如此文章open in new window所说,“把时间浪费在解决不必要的冲突上”。

再者,功能已经通过测试了,准备上UAT环境时,居然还要解决一大堆曾经解决过的冲突,实在不想接受这种“惊喜”(或者说“惊吓”更合适)——合错代码了怎么办?并且,这么多分支,遗漏了怎么办?这些问题可以解决,但难免有为了解决一个问题,引入更多问题之嫌。

理想中的研发流程是,测试通过后,上 UAT 的体验是平滑的,是不用担心出错的。

为此,本文思考是否存在另一种分支协作的方式。

分析

首先分析一下,“某功能测试通过但不上 UAT”的可操作方法有哪些:

  1. 要上 UAT 的 feat 分支逐个依次合并至长驻分支,也即当前的做法
  2. 在原计划要上的功能的代码集合中,剔除掉相应 feat 的代码,再上 UAT
  3. 相应分支再次提交代码,或提交 revert commit,或屏蔽相应的功能及入口,变相达到目的

剔除代码

先来看第2种方法。filter by branch,这是最先想到且符合直觉的方式,可惜实际上 Git 并没有此功能。

想“剔除某 feat 分支的代码”,可操作方式如下,更多请参考此文章open in new window

  1. git rebase
  2. git cherry-pick

git rebase要求 commit 是连续的,这对于实际不可行,因为集成分支里各个 feat 分支的提交记录掺杂在一起。

git cherry-pick是可行的。不过其思路是挑捡想要的 commit,放到目标分支,本质上并不是剔除的逻辑。

再次提交

真正的剔除逻辑,存在于第3种方法中。提交一个 revert commit,就可以把之前的代码干掉了(如果想恢复代码,需要 revert "revert commit")。

觉得 revert 可能会对后续恢复代码造成困扰的话,也可以再提交代码,屏蔽相应功能及入口。这种方式适合于功能入口少,功能本身具有类似开关特性的场景。

比较优劣

要比较上述方案优劣,本文倾向于使用功利主义的最佳实践作为指导思想——认为痛苦存在更多共同点,因而为避免消极而努力。换言之,本文关注的是,哪种方案最令人痛苦,则优先淘汰它。

还有一个指导思想:麻烦、辛苦的事情放前面;前面可以多做,后面期望少做。

当前的方式,存在最难受的问题:解决过的冲突,需要重复地解决。涉及范围:全部分支。涉及人员:所有参与研发的人员,即使他们在别的 feat 分支提交代码。

cherry-pick 依然存在要重复解决冲突的问题,且涉及范围同样为全部分支,但涉及人员减少为单人,因为只需要一个做 cherry-pick 的工作,由其解决 cherry-pick 遇到的冲突(当然,很可能需要他人协助)。

再次提交,冲突的可能性将大大减少,涉及范围:相应的 feat 分支。涉及人员:相应的 feat 分支研发人员。

也即使用再次提交的方案,痛苦将降低至最小。这也是符合直觉的:谁出问题,谁负责。某功能不上线了,这也算是“问题”的一种,则相应的负责人去处理,尽可能不影响到其他人。

至于再次提交是使用 revert 还是屏蔽功能及入口,则具体情况具体分析。

实例

下面举例说明,如何应用上述分析结果。

分支模型

长驻分支: dev/test/uat,分别对应环境:研发/测试/UAT

一个月一次的迭代开始时,都建立相应的  release 分支,命名规则可以:

  • 按版本,如: release/v2.15
  • 按上线日期,如:release/04-26

功能提交

每个研发人员根据相应功能,从 uat checkout 相应的 feat 分支。

每次需要集成发布时,正常的分支合并操作如下:

  1. feat -> dev
  2. feat -> release
  3. release -> test
  4. release -> uat

则冲突大多数情况只发生在第前两步,解决之后,后续上测试环境、上 UAT 环境,基本无需担心冲突。

为什么一个 feat 要合并两次?

因为要保证 release 的功能是较为完整的, 至少经过开发人员在 dev 环境的自测。

并且这样也能适应不同的功能分批提测的研发节奏。

功能回撤

当 release 合并至 test 分支后,得到通知,某功能(分支涉及 feat/unwanted)不上 UAT。

则此时,feat/unwanted 相应的研发人员,为了进行功能回滚,操作如下:

  1. checkout rollback 分支
  2. 进行回滚提交,或 revert,或屏蔽功能入口
  3. 请求合并至 UAT(不合并至 release/分支):

后续要恢复功能,在 rollback 分支操作,再合并至 UAT 即可。

结论

通过分析与比较,本文推荐使用“再次提交”的方式,来满足某 feat 分支合进 test,不合进 uat 分支的需求。

这样做,将改动涉及范围减至最小,涉及人员降为单人,大大减少上 UAT 时合并代码的痛苦,达到平稳上线的目的。

上次编辑于:
贡献者: levy