前言
高效的持续交付体系,必定需要一个合适的代码分支策略。采用不同的代码分支策略,意味着实施不同的代码集成与发布流程,这会影响整个研发团队每日的协作方式,因此研发团队通常需要很认真地选择自己的策略。
现状
采用的分支策略
目前我们采用的 Git Flow 模型,其在 2011 年左右被大家当作了推荐的分支模型。
Git Flow 模型
主要包括:
- 主分支:master,稳定版本代码分支,对外可以随时编译发布的分支,不允许直接 Push 代码,只能请求合并(pull request),且只接受 hotfix、release 分支的代码合并。gitlab 上做权限限制(开发组长)。
- 热修复分支:hotfix,针对现场紧急问题、bug 修复的代码分支,修复完后合并到主分支、开发分支。
- 发版分支:release,版本发布分支,用于迭代版本发布。迭代完成后,合并 dev 代码到 release,在 release分支上编译发布版本,以及修改 bug(定时同步 bug 修改到 dev 分支)。测试完成后此版本可以作为发版使用,然后把稳定的代码 push 到 master 分支,并打上版本标签。
- 开发分支:dev,开发版本分支,针对迭代任务开发的分支,日常开发原则上都在此分支上面,迭代完成后合并到 release 分支。
- 特性开发分支:feature-***,开发人员可以针对模块自己创建本地分支,开发完成后合并到 dev 开发分支,然后删除本地分支,涉及多人协同开发的可以 push 到服务端。
目前团队特点
- 尚不具备主干开发能力(开发团队系统设计和开发能力非常强)
- 有预定的发布周期
- 需要严格执行发布周期(双周迭代)
目前落地方案
在代码分支管理的层面上,团队源代码分为五个主要分支:
- Master:主分支,稳定版本
- Hotfixes:补丁分支,稳定/预览版本或现场问题的应急处理
- Release:预览分支,Bata版/测试与bug修复
- Develop:开发分支,常规功能的新增与调整
- Feature:特性分支,同时可以有多个特性分支,代码合并后结束;
分支合并时间:
- 主分支:每个季度一个正式版本,于每个季度末合并发版;由预览分支、补丁分支合并;不允许直接 Push 代码,只能合并;
- 补丁(热修复)分支:随现场使用情况而定,可以打临时版本或补丁;由主分支替换而来,修复完后合并到主分支、开发分支;
- 预览分支:版本发布分支,用于迭代版本发布。每日测试打版验证,由开发分支合并而来;测试完成后此版本可以作为发版使用,然后把稳定的代码 push 到 master 分支,并打上版本标签。
- 开发分支:不对外发布,可以由其他分支合并而来;针对迭代任务开发的分支,日常开发原则上都在此分支上面,迭代完成后合并到 release 分支;
- 特性分支:不直接打版,可以由开发分支合并而来;新功能稳定后合并到开发分支;
目前存在的问题
- 分支关系复杂:GitFlow 包含的分支过多,以及许多繁琐的合并规则。重流程,使用起来并不是很容易,发布分支拉出后,直到合回主干,若有特性修改或 Hotfix 需要维护多处 CherryPick(选择部分变更集合并到其他分支) 合并;
- 集成时间滞后:特性分支在功能完成前,“不敢”随意合并回 Dev 分支,造成代码集成时间严重滞后;
- 代码集中冲突:每次功能完成后进行“大集成”,十分容易出现大范围代码冲突;
- 特性易合难分:特性一旦集成到 Dev 分支便难以再次分离, 单个特性问题可能导致整体发布延期。
- 无法自动化:各特性分支进入到稳定阶段,必然需要 merge 在一起,然后一起被编译、打包和测试。怎么让这个过程更加自动化呢?
方案选型
常见分支策略
主干开发,分支发布
图片来源:https://paulhammant.com/2013/12/04/what_is_your_branching_model/
在这种分支策略下,开发团队共享一条主干分支,所有的代码都直接提交到主干分支上,主干分支就相当于是一个代码的全量合集。在软件版本发布之前,会基于主干拉出一条以发布为目的的短分支。
分支开发,主干发布
图片来源:https://paulhammant.com/2013/12/04/what_is_your_branching_model/
当开发接到一个任务后,会基于主干拉出一条特性开发分支,在特性分支上完成功能开发验证之后,通过 Merge request 或者 Pull request 的方式发起合并请求,在评审通过后合入主干,并在主干完成功能的回归测试。开源社区流行的 GitHub 模式其实就是属于这种。根据特性和团队的实际情况,还可以进一步细分为两种情况:
- 每条特性分支以特性编号或需求编号命名,在这条分支上,只完成一个功能的开发;
- 以开发模块为单位,拉出一条长线的特性分支,并在这条分支上进行开发协作。
两者的区别就在于特性分支存活的周期,拉出时间越长,跟主干分支的差异就越大,分支合并回去的冲突也就越大。所以,对于长线模式来说,要么是模块拆分得比较清晰,不会有其他人动这块功能,要么就是保持同主干的频繁同步。随着需求拆分粒度的变小,短分支的方式其实更合适。
主干开发,主干发布
图片来源:https://paulhammant.com/2013/12/04/what_is_your_branching_model/
团队只有一条分支,开发人员的代码改动都直接集成到这条主干分支上,同时,软件的发布也基于这条主干分支进行。对于持续交付而言,最理想的情况就是,每一次提交都能经历一系列的自动化环境并部署到生产环境上面,而这种模式距离这个目标就更近了一点。可想而知,如果想要做到主干分支在任何时间都处于可发布状态,那么,这就对每一次提交的代码质量要求非常高。在一些追求工程卓越的公司里,你要提交一行代码,就必须经历“九九八十一难”,因为有一系列的自动化验收手段,还有极为严格的代码评审机制来保证你的提交不会把主干分支搞挂掉。
优缺点对比
选出最适合的策略
很难说有一种通用的分支策略可以满足我们所有场景的需求。但是,有些分支策略的原则更加适合于快速迭代发布的场景,也就更加适合 DevOps 的发展趋势。所以,这里我个人比较推荐的是「分支开发,主干发布」的模式,也就是团队共享一条开发主干,特性开发基于主干拉出特性分支,快速开发验收后合并发布,同时,在特性分支和发布分支分别建立不同的质量门禁和自动化验收能力。
这样做的好处在于:
- 保留“特性分支”的工作方式,便于团队协作;
- 简化 Gitflow 的复杂分支策略,上手容易;
- 灵活的特性分支组合集成,集成后亦可快速剥离;
- 实现“准持续集成”
- 略低于单主干,远高于 Gitflow 的集成频率 ;
- 选择性的特性持续集成(方便灵活,但其实并非优点)
不过,在执行的过程中,需要遵守以下原则:
- 团队共享一条主干分支;
- 强力的特性拆分的能力;
- 特性的粒度和分支存活的周期是关键要素。根据经验来看,分支存活的周期一般不要超过2周;
- 特性分支的命名需规范;
- 保证一个特性的关联改动需要提交到一条分支上,而不是到处都是,尽量做到原子性提交。
分支发布的策略图如下所示:
- 代码管理后台:GitLab
- 主分支:master,开发主分支,对外可以随时编译发布的分支,不允许直接Push代码,只能请求合并(pull request)。gitlab上做保护性限制。
- 热修复分支:hotfix/版本号命名,针对现场紧急问题、bug修复的代码分支,修复完后删除。
- 发布分支:release/版本号命名,版本发布分支,用于迭代版本发布。迭代完成后,合并代码到master,在release分支上编译发布版本,以及修改bug。测试完成后此版本可以作为发版使用,然后把稳定的代码合并到 master 分支,并打上版本标签。支持针对不同项目的特性发布。
- 特性分支:feature/特性命名,开发版本分支,针对迭代任务开发的分支,日常开发原则上都在此分支上面。
- 本地分支:local/特性命名,开发人员可以针对模块自己创建本地分支,开发完成后合并到 feature 特性分支,然后删除本地分支。
常见问题说明
单个特性分支怎么合入到发布分支?
为了保证集成分支的质量,在 gitlab 上集成分支通常都被保护起来(protected),不允许直接 push 到被保护的分支。不过,我们可以通过发起 Merge Request 的方式把特性分支合入到发布分支 。借助 Merge Request,我们可以完成 sonar 静态检查、代码 review 等质量管理的活动。
多个特性分支会给集成带来哪些问题?
- 不同分支可能会修改相同文件,集成时很可能出现代码冲突。
- A、B两个分支先后合入到集成分支,B合入后导致A分支对应的功能发生故障。
- A 合入到集成分支后可能需要一套测试环境;B 合入到集成分支后也可能再需要一套测试环境。多特性分支分别合入集成分支所需的测试环境也多。
参考资料:
- [1]:《持续交付36讲》
- [2]:《DevOps实战笔记》