支的目的是隔离,但多一个分支也意味着维护成本的增加。我们可以分别从开发和发布分支的多寡,做个简单组合,即:
主干开发,主干发布。
分支开发,主干发布。
主干开发,分支发布。
分支开发,分支发布。
设想两个不同的场景:
如果一个软件,只有一个开发者,只需要一个发布版本,那他需要什么样的分支模式?
如果一个软件,有 10 位开发者,需要支持多个版本,那他们又需要什么样的分支模式?
一个好的分支模式,可以大大提高软件的开发、集成和发布效率。选择什么样的分支策略,是每一个开发团队开始工作时面临的第一个问题。那么,选择什么样的分支模式才适合我们呢?在回答这个题之前,我们先了解一下几种常见的分支模式。
主流的分支模式
常见的分支模式有 TBD(即主干开发模式)、Git-Flow 模式、Github-Flow 模式及 Gitlab-Flow 模式。
TBD(主干开发模式)
即所有开发者,仅在一个开发分支(即主干)上进行协作开发的模式,在这种模式下,不允许新建任何长期存在的开发分支,有且仅保留主干分支进行开发协作。
因为没有长期分离的其他开发分支,任何代码变更持续地更新到主干上,在一定程度上避免了 merge 代码带来的困扰。同时,在这种开发模式下,建议采用发布分支的策略,根据软件版本的发布节奏拉出发布分支。在 TBD 模式下,所有的修改都是在主干上,哪怕是缺陷的修改也是,修改完缺陷后,再 cherry pick 到发布分支上。
其特点总结一下就是:
有且仅有一个开发分支,即主干分支。
所有改动都发生在主干分支。
发布可以从主干拉发布分支。
主干上进行的修复需要根据缺陷的修复策略,确定是否 cherry pick 到对应版本的发布分支。
因为团队共享一个开发分支,并且在开发分支上进行集成验证,而每次代码提交都会触发集成验证,这就要求每次代码的变更在主干上都能快速地验证,以确定是否接受下一次代码变更(每次代码变更都应该基于前一个稳定的版本进行),为了保证主干一直处在可工作状态,这就需要:
每一次的变更要小,这样在验证的过程中才能控制范围。
快速完成验证,这就要求有相对完善的自动化检查验证机制。
所以,主干开发模式可以说是持续集成的关键推动者。主干开发模式非常利于持续集成,并且根据稳定和主干基线,做到随时发布,以达到持续交付。但这些是建立在团队成熟的协作能力和相对成熟的工程配套的基础上,快速地对主干的变更提交完成编译、检查及验证;同时,因为采取发布分支的实践方式,在产品版本、分支、部署场景的对应关系需要梳理清楚,避免发布分支混乱,及缺陷修改在各分支上的修复策略。
因为主干开发要求每次变更提交都要小,并且要快速验证完,保证主干是处在可发布状态。对于一些处在开发过程中的特性,如每次变更提交,并非意味着完整特性的完成,为了隔离“特性半成品”对主干的影响,一般会采用特性开关(Feature Toggle)的方式进行隔离。即频繁的代码变更提交,可以先做集成及验证,但是在发布的角度,通过(Feature Toggle)先隐藏相关特性,只有当特性都完成之后,才打开开关,特性完全透出。
但是,特性开关的引入也并不是没有成本,因为特性开关是配置,本质上跟我们常常用到的宏定义(#if #else)没啥区别,从本质上,它也是一种代码的分支。特性开关的使用,在一定程度上让你的代码变得更脆弱。所以,特性开关的使用,是建立在良好的代码设计基础上。
为了弥补诸如特性开关这样针对某个特性开发的需要,而且现在软件开发中,越来越多的团队共同协作在一起完成某一个特性这样的场景,一种针对特性开发的分支模式就应运而生,这就是特性分支开发模式,最有代表性的就是 Git-Flow。
Git-Flow
Git-Flow 是为了解决多个不同特性之间并行开发需要的一种工作方式。当开始一个特性的开发工作的时候,从主干上拉出一个特性分支,所有的关于该特性的开发工作都发生在这个特性分支上,当完成该特性的工作之后,再把特性分支合并回代码主路径上,并准备发布。
Git-Flow 有以下几种分支:
feature 分支:开发者进行功能开发的分支。
develop 分支:对开发的功能进行集成的分支。
release 分支:负责版本发布的分支。
hotfix 分支:对线上缺陷进行修改工作的分支。
master:保存最新已发布版本基线的分支。
每个特性都有属于自己的开发分支,即 feature 分支,当一个开发者需要在两个特性上进行工作的时候,他需要做的是通过 check out 命令在两个分支之间进行切换。这样做的目的是防止开发过程中,两个特性开发工作的相互干扰。
特性开发过程中,需要针对该特性进行单独验证,当该特性并验证通过之后,merge 到一个叫做 develop 分支(大部分时间与 master 分支相近)的集成分支中,对整个软件进行验证。develop 分支永远保存都是最近的未发布版本,当 develop 分支的代码被验证可发布之后,单独从 develop 分支拉出 release 分支进行发布。
当拉出 release 分支进行发布过程中,如果发现缺陷,缺陷的修复发生在 release 分支上,所做的缺陷修改再持续同步到 develop 分支上。当 release 分支被发布完,其代码的最终版本会再次分别同步给 develop 分支和 master 主干上。我们可以发现, master 上永远保存的是可工作版本的基线。develop 分支保证的是开发集成中最新的版本。
Git-Flow 引入了一种叫做 hotfix 的分支,专门用于线上缺陷的修复。当缺陷修复完,再集成到 develop 分支,及同步到 master。其实,我们可以理解 hotfix 是一种特殊的 feature 分支,只是它的变更提交在集成到 develop 分支的同时需要同步到 master。
是不是觉得这个模式很专(fu)业(zha)的样子,那我们通过开发者做一个特性开发,按 happy path 捋捋:
开发者接到一个开发需求,从 develop 分支拉一个 feature 分支。
大家完成自己本地的开发工作,完成本地验证,提交代码到 feature 分支。
基于 feature 分支进行验证,并持续合并新的开发代码。
完成特性的开发,并且 feature 分支验证无误,将 feature 分支的代码合并到 develop 分支。
在 develop 分支进行集成验证(此处,可能和其他合并进来的特性分支一起进行验证),集成验证完毕,feature 分支会被删除。
当 develop 分支是一个成熟的发布版本时,如完成了彻底的测试及问题的修复,拉出 release 分支进行发布。
完成发布之后,将 release 分支合并入 develop 和 master(master 保存的永远都是已发布的最新代码),并删除 release 分支。
Hotfix 的流程如下:
如果发布之后,发现了缺陷,基于 master 拉出一个 hotfix 分支。
在 hotfix 对问题进行修改及验证。
问题的修复合并到 develop 和 master 上。
删除 hotfix 分支。
Git-Flow 的分支模式,提供了相对完备的各种分支,以覆盖软件开发过程中的大部分场景,以致于在相当长的一段时间内,人们认为这就是标准的 Git 的分支模式(因为从它的名字上看,也很容易产生这样的幻觉)。但是,Git-Flow 也存在着明显的一些问题,如:
分支特别多,而且每类分支都有特定限定的用法,开发者很难记住什么分支是干什么的。
整个分支模式过于复杂,大大超出大部分团队和项目的需求。
feature 分支的生命周期过长导致的合并冲突。如果一个特性所在 feature 分支生命周期过长,它跟 develop 分支的差异就越大,这样,在该特性集成到 develop 的过程中,潜在的代码冲突将是集成的噩梦。
像 develop 分支,感觉其实存在的意义不是太大,完全通过 master 就可以替代集成的作用,额外为变更提交的集成引入 develop 分支,对分支模式来说,变得更加的复杂;同样,如果取消 develop 分支,那么,hotfix 分支存在的意义也就没有必要了,因为这个时候,hotfix 分支与 feature 分支就没有任何的差别。
那么,有没有一种分支模式,既包含开发任务对于主线的隔离,又相对 Git-Flow 轻量一点?我认为,真正的解决方案,应该是本质极简单的。这里,我们就不得不给大家介绍另一种分支模式,叫做 GitHub-Flow。
GitHub-Flow
在 GitHub-Flow 上,第一步就是没有 Git-Flow 中所介绍的 release 分支。对于 GitHub-Flow 来说,发布应该是持续地,当一个版本准备好,它就可以被部署。同样,在 hotfix 上的处理,GitHub-Flow 认为,hotfix 与那些小的特性修改没有任何区别,它的处理方式也应该与之相似。
在 GitHub-Flow 的整体流程是:
在 master 分支上的所有代码都应该是最新可部署、可工作的版本。
如果要进行新的工作,从 master 分支上拉出一个新的分支,并以工作任务清晰命名,如 “new-scheduling-strategy”。
尽可能频繁地提交代码变更到本地分支,与此同时,尽可能频繁地同步到服务端相同分支名的分支。
当准备合并代码到 master 主干分支上,通过发起 Pull Request,提请代码评审。
通过代码评审后,或与此同时,需要将该分支部署到测试环境,进行验证。
如果评审通过及验证通过,代码则合并到 master 主干分支上,应该立即部署到生产环境。
GitHub-Flow 相比 Git-Flow 来说,有个显而易见的好处——简单。另一个好处就是持续部署的要求,尽可能快速地发现 master 分支的问题,并能通过 rollback 等机制,快速恢复。将所有内容合到 master 分支中,并经常部署,意味着你可以最小化未发布的代码量,这也是精益开发和持续交付所倡导的最佳实践。部署原本是一件很繁琐的事情,但是因为要频繁做,我们就容易把这样一件事情做简单,以达到持续交付的目的。
虽然 GitHub-Flow 简化了 Git-Flow 的分支模式,但是对于部署、环境、以及发布,该分支模式仍然存在许多未回答的问题,所以,我们希望通过 GitLab-Flow 来为这些问题提供更多的参考。
GitLab-Flow
GitLab-Flow 相比于 GitHub-Flow 来说,在开发侧的区别不大,只是将 pull request 改成了 merge request,而 merge request 的用法与 pull request 类似,都可以做为代码评审、获取反馈意见的一种沟通方式。
最大的区别体现在发布侧,即引入了对应生产环境的 production 分支和对应预发环境的 pre-production 分支(如果有预发环境的话)。这样,master 分支反映的是部署在集成环境上的代码,pre-production 分支反映的是部署在预发环境的代码,production 分支反映的最新部署在生产环境的代码。
当一个特性开发完成,提交 merge request,将特性开发的代码合并到 master,并部署到集成环境进行验证;当验证通过之后,提交 merge reqeust,合并 master 到 pre-production 分支,并部署到预发环境,进行预发环境上验证;当预发环境验证成功之后,再提交 merge request,将 pre-production 分支上的代码合并到 production 分支上。
除了以上这种按环境,将主干发布向下游合并,并依次部署发布的过程。GitLab-Flow 同样支持不同版本的发布分支,即不同的版本会从 master 上拉出发布分支,不同的发布分支再走 pre-production 分支和 production 分支的方式进行发布。
从上面的介绍中,我们发现,GitLab-Flow 更多是在发布侧做了更多的工作。同样 GitLab-Flow 因为跟 GitLab 工具强依赖,所以 GitLab-Flow 与 GitLab 中的 Issue 系统也有很好的集成,在其推荐的工作模式中,每次新建一个新的 feature 分支,都是从一个 issue 上发起的,即建立 issue 与 feature 开发分支之间的映射。
pros vs cons
所以,如果我们还是按照开发和发布的分支多寡来分类的话,以上这些分支模式分别属于:
TBD 应该是主干开发,可以是分支发布,也可以是主干发布。
Git-Flow 应该是分支开发,分支发布。
GitHub-Flow 应该是分支开发,主干发布。
GitLab-Flow 支持分支开发,但支持主干发布,也支持分支发布。
了解了常见的这些分支模式之后,我们在工作中就可以根据自身的业务特点和团队规模来选择适合的实践,没有绝对好的模式,只有适合的模式。
根据团队自身和项目的特点来选择最适合的分支实践应该从哪些地方考量呢?
选择合适的实践
首先是项目的版本发布周期。如果发布周期较长,则 Git-Flow 是最好的选择。Git-Flow 可以很好地解决新功能开发、版本发布、生产系统维护等问题;如果发布周期较短,则 TBD 和 GitHub-Flow 都是不错的选择。GitHub-flow 的特色在于集成了 pull request 和代码审查。如果项目已经使用 GitHub,则 GitHub-Flow 是最佳的选择。GitHub-Flow 和 TBD 对持续集成和自动化测试等基础设施有比较高的要求。如果相关的基础设施不完善,则不建议使用。
另外,从需要发布版本的多寡的角度来看:
支持一个产品多个发布版本,用 Git-Flow。
支持一个简单产品单个发布版本,用 GitHub-Flow 或 TBD。
支持一个复杂产品单个发布版本,用 GitLab-Flow。
如果发现,现有主流的分支模式都无法满足你的要求,那么,我们可以定义自己的分支模式,如我们有个团队是基于主干开发的,于是定义了春夏秋冬分支模式,春天用“春分支”,夏天用“夏分支”…,我个人就蛮喜欢的,原因有二,一是做到了主干开发,持续发布,二是名字起得很有意思,开发工作一定要有乐趣,不是么?