说到版本控制,就不得不提到分支管理策略。就像学开车必须学学交通规则。分支管理策略是代码版本控制的基础组成部分。为团队定制一套合适的分支管理策略,就好比制定了一套合理的交通规则,可以让团队的代码的更加有序地演进,尽可能降低多分支带来的复杂度,并避免由于分支混乱引发的各种“车祸”。本文将简单讨论下我们在开发过程中尝试的各种分支管理策略,在面对各种复杂场景下呈现的优势与不足,以及我们的妥协和后续期望。
作为 Github 的重度用户,我首先考虑的当然是 Github-Flow 。
Github-Flow 是一种非常简单的分支管理方案。它的流程只有如下几步:
在 Gitlab 中同样可以使用 Github-Flow,唯一的区别是叫法从 Pull Request
变成了 Merge Request
。下图是一个被成功合并的 Merge Request:
Github-Flow 有如下几个让人着迷的优点:
然而,面对复杂的项目,Github-Flow 暴露出了如下的不足:
综上所述,Github-Flow 更适用于那些只以 master 分支为主分支,更注重迅速发布的简单项目。这使其非常适合用在维护 Github 上的这些 集市型
的的开源项目,而不适用于 大教堂型
的企业级项目。正如 Github 的 Scott Chancon 大神所说:
For teams that have to do formal releases on a longer term interval (a few weeks to a few months between releases), and be able to do hot-fixes and maintenance branches and other things that arise from shipping so infrequently, git-flow makes sense and I would highly advocate it’s use.
For teams that have set up a culture of shipping, who push to production every day, who are constantly testing and deploying, I would advocate picking something simpler like GitHub Flow.
Scott Chancon, Issues with git-flow
Git-Flow 是由 Vincent Driessen 在他的一篇文章 《 A successful Git branching model 》 中提出的分支管理策略。
与 Github-Flow 相比,Git-Flow 拥有更多的分支:
Git-Flow 提出的分支管理策略完整而实用,它甚至已经成为了一个通用开发流程标准。开发者们可以在多个团队和项目中遵守同一套流程。但 Git-Flow 也不是万金油。遇到复杂的项目,它也未必能完全适用:
容易出现冲突。Git-Flow 设计了多个分支各司其职,但多分支带来的苦恼是容易出现冲突。最常见的问题是,由于我们实现了 子模块 commit id 的自动更新 ,主分支与开发分支的子模块 commit id 经常变动,导致 develop 分支向 master 分支合并的时候出现大量冲突,阻塞发版进度。
多产品线的问题。
我们的主工程存在多条产品线:master 分支仅仅维护一个基础模板,而 jilin 、taishan 等分支才是用于产出真正产品的分支。每条产品线的各自有一套 Git-Flow 分支体系,并用前缀区分产品线。例如 jilin 的 develop 分支就叫 jilin-dev 。而子模块既可能和主工程一样多个产品分支,也可能是一个通用模块。对于通用模块,只需要维护一套 Git-Flow 分支体系。例如 common 子模块就只有标准的 master、dev 等分支。
对于多产品分支的主工程和子模块,当改动了某个分支的代码,你就要非常慎重的考虑这部分改动是否通用,是否需要并入其他产品线的分支。而 Git-Flow 并没有探讨多个产品线并存情况下的代码合并方案。
对于通用的子模块,拉 release 分支时又存在 锁
的问题。比如,负责 jilin 产品线的同事即将发版,于是把 common 子模块拉出了一个 release 分支。其他产品线的同事依然可以继续为 common 子模块的 develop 分支提交 feature 。但还没等 jilin 产品线完成发版。taishan 产品线的同事也准备发版了,此时 release 分支早已经被 jilin 的同事拉出来,而这个 release 分支却没有 taishan 产品线要发版需要的 feature 。这就阻碍了 taishan 产品线的发版。
为了化繁为简,我们做了些妥协:
$ fmanager cherry-pick <commit id> <分支列表>
例如,假如希望把 weihai 分支上的一个提交同步到其他分支,可以使用如下命令:
$ fmanager cherry-pick 023e937d master,jilin,taishan
通用的子模块发版时,始终拉出产品 release 分支。
例如,jilin 产品线需要发版了,于是从 common 模块的 master_dev 拉出了 jilin-release 分支:
拉出分支后,与 jilin 分支有关的临时改动可以在 jilin-release 中进行。同时 common 模块依然可以给负责其他产品线的同事提交新 feature 。此时 taishan 产品线的同事如果要发版,可以拉出 taishan-release 分支:
之后,如果 jilin 产品线的同事修改了一个通用的 bug,同样可以将这个提交 cherry-pick 到其他分支:
其实,与其说是 Github-Flow 和 Git-Flow 的问题,不如说是现阶段我们的产品架构的问题。用分支来实现产品线的差异化使得一个仓库出现了多个主分支,而这种复杂的模式已经超出了通用的分支管理流程所能解决的范畴。另外,现阶段子模块的不稳定也导致开发过程中不断需要跨产品线同步代码,给产品线的开发造成负担。
日后我们希望对工程的架构进行调整,通过配置文件来实现产品差异化,而不再创建产品分支。另外,子模块也会越来越稳定,可以低成本接入到各个产品线中,而不再需要频繁迭代。到了那一天,我们的项目就能够重新回归到严格的 Git-Flow ,将 化繁为简
做到极致。