持续交付技术7:持续集成的源头,代码分支策略

文摘   2024-07-25 23:46   广东  

在分支管理的时候经常会面临如下问题和挑战:

  1. 如何开始一个Feature的开发,而不影响别的Feature?

  2. 由于很容易创建新分支,分支多了如何管理,时间久了,如何知道每个分支是干什么的?

  3. 哪些分支已经合并回了主干?

  4. 如何进行Release的管理?开始一个Release的时候如何冻结Feature, 如何在Prepare Release的时候,开发人员可以继续开发新的功能?

  5. 线上代码出Bug了,如何快速修复?而且修复的代码要包含到开发人员的分支以及下一个Release?

高效的持续交付体系,必定需要一个合适的代码分支策略。本文将介绍常见的分支类型持续集成的分支策略和选择。

一、常见的分支类型


当下最流行的版本管理系统应该是非Git莫属。相比同类软件,Git有很多优点,其中很显著的一点,就是版本的分支(branch)和合并(merge)十分方便。有些传统的版本管理软件,分支操作实际上会生成一份现有代码的物理拷贝,而Git只生成一个指向当前版本(又称"快照")的指针,因此非常快捷易用。但是,太方便了也会产生副作用。如果不加注意,很可能会留下一个枝节蔓生、四处开放的版本库,到处都是分支,完全看不出主干发展的脉络。Vincent Driessen提出了一个分支管理的策略,非常值得借鉴!它可以使得版本库的演进保持简洁,主干清晰,各个分支各司其职、井井有条。

下面就对这一策略做一简单梳理:

1)主分支Master

首先,代码库应该有一个、且仅有一个主分支。所有提供给用户使用的正式版本,都在这个主分支上发布。
Git主分支的名字,默认叫做Master。它是自动建立的,版本库初始化以后,默认就是在主分支在进行开发。

2)开发分支Develop

主分支只用来分布重大版本,日常开发应该在另一条分支上完成。我们把开发用的分支,叫做Develop。
这个分支可以用来生成代码的最新隔夜版本(nightly)。如果想正式对外发布,就在Master分支上,对Develop分支进行"合并"(merge)。
Git创建Develop分支的命令:
# git checkout -b develop master
将Develop分支发布到Master分支的命令:
切换到Master分支
# git checkout master
对Develop分支进行合并
# git merge --no-ff develop

上面命令中的--no-ff参数是什么意思。默认情况下,Git执行"快进式合并"(fast-farward merge),会直接将Master分支指向Develop分支。
使用--no-ff参数后,会执行正常合并,在Master分支上生成一个新节点。为了保证版本演进的清晰,我们希望采用这种做法。

3)临时性分支

前面讲到版本库的两条主要分支:Master和Develop。前者用于正式发布,后者用于日常开发。其实,常设分支只需要这两条就够了,不需要其他了。
但是,除了常设分支以外,还有一些临时性分支,用于应对一些特定目的的版本开发。临时性分支主要有三种:

1)功能(feature)分支
2)预发布(release)分支
3)修补bug(fixbug)分支

这三种分支都属于临时性需要,使用完以后,应该删除,使得代码库的常设分支始终只有Master和Develop。

3.1)功能分支

接下来,一个个来看这三种"临时性分支"。
第一种是功能分支,它是为了开发某种特定功能,从Develop分支上面分出来的。开发完成后,要再并入Develop。
功能分支的名字,可以采用feature-*的形式命名。

创建一个功能分支:
# git checkout -b feature-x develop

开发完成后,将功能分支合并到develop分支:
# git checkout develop
# git merge --no-ff feature-x

删除feature分支:
# git branch -d feature-x

3.2)预发布分支

第二种是预发布分支,它是指发布正式版本之前(即合并到Master分支之前),我们可能需要有一个预发布的版本进行测试。
预发布分支是从Develop分支上面分出来的,预发布结束以后,必须合并进Develop和Master分支。它的命名,可以采用release-*的形式。

创建一个预发布分支:
# git checkout -b release-1.2 develop

确认没有问题后,合并到master分支:
# git checkout master
# git merge --no-ff release-1.2

对合并生成的新节点,做一个标签
# git tag -a 1.2

再合并到develop分支:
# git checkout develop
# git merge --no-ff release-1.2

最后,删除预发布分支:
# git branch -d release-1.2

3.3)修补bug分支

最后一种是修补bug分支。软件正式发布以后,难免会出现bug。这时就需要创建一个分支,进行bug修补。
修补bug分支是从Master分支上面分出来的。修补结束以后,再合并进Master和Develop分支。它的命名,可以采用fixbug-*的形式。

创建一个修补bug分支:
# git checkout -b fixbug-0.1 master

修补结束后,合并到master分支:
# git checkout master
# git merge --no-ff fixbug-0.1
# git tag -a 0.1.1

再合并到develop分支:
# git checkout develop
# git merge --no-ff fixbug-0.1

最后,删除"修补bug分支":
# git branch -d fixbug-0.1

二、常见持续集成的分支策略


DevOps,持续集成、持续交付,都是这几年火热的概念,也越来越深入人心。个人认为,所有这一切的基础,都是分支策略。

1、为什么需要分支策略

我们先考虑一下最”简单“的情况:所有的代码都是一个人开发,没有多人协作。所有的功能都是有序开发,每个版本都是开发完成,经过严格的设置,然后发布。并且在线上不会出现bug。这种情况下,其实是不需要分支策略的,甚至不需要分支,我们按部就班的开发、提交就可以了。

但是,这种最简单的情况在现实中是不存在的。因此也就出现了分支策略。可以说,分支策略是软件协作模式和代码发布模式的基础。

一位架构师前辈对我说过:”作为一个架构师,在学习的时候,不仅要知道怎么做,还要知道为什么这么做。不同的技术、不同的方案分别能够解决什么问题,又会带来什么问题。离开应用场景谈架构,都是耍流氓。”
因此,在详细讲解流行的分支策略之前,我们先看看在代码管理中,我们经常遇到的问题是什么。

  1. 第一版在测试的同时,开始下一版的并行开发。bug需要同时在两个版本中修复。

  2. 线上版本出现bug,需要快速修复,因此需要有线上发布的”纯净“版本。

  3. 多个历史版本需要同时维护,一个线上版本的bug修复,可能需要提交到多个历史版本以及开发版本中。
    4.打乱计划,迭代中期要求快速上线部分新功能。

2、分支策略的分类

目前各个公司使用的分支策略各不相同,但是万变不离其宗,所有的分支策略,都可以归结为三种类型:

  • 主干开发,分支发布

  • 分支开发,主干发布

  • 主干开发,主干发布

2.1 主干开发,分支发布

这种分支策略中,开发团队都将修改提交到主干分支,


在这种策略下,通常来说,所有的提交都是提交到主干。需要发布版本的时候,从主干上拉出发布分支,进行打包和最终的测试。注意这个分支是以发布为目的的,因此新功能不应该提交到该分支。在测试的过程中出现的bug,修复后仍然是提交到主干分支,然后从主干分支上采用cherry pick的方式合并到发布分支中。
发布分支测试完成后,发布到生产环境中。如果在生产环境中发现了bug,通常需要快速修复,这个时候可以在主干上修复,然后采用cherry pick的方式合并到发布分支上;也可以在发布分支上进行紧急修复,然后合并回master分支。具体选择哪一种,根据修复的紧急程度决定。如果可以,尽量采用主干提交,cherry pick合并到发布分支的方式。

这种分支策略的优点在于简单明了,易于理解。有利必有弊,这个模型对于主线代码质量要求高,如果某次提交出现了问题,那么会团队所有的后续提交都无法集成测试。另外,通常我们一个功能的开发过程中会有多次提交,多个功能的多次提交交错进行,就会导致当某个功能没有及时完成又必须上线的时候,上线版本中会包含未完成的功能,影响发布分支的代码质量。同时,所有直接提交到发布分支中的紧急修复,都必须合并回主干分支。这一点很容易被忘记。我们通常会采用以下的方法解决这些问题:

  1. 代码提交前进行code review,提高主干代码的质量。

  2. 采用功能开关配置(会带来配置复杂的问题)

  3. 专人负责将紧急修复反向合并到主干。

这种情况下,我们可以并行管理多个分支,可以进行快速的修复,通过分支合并的方式,将在多个分支中同时修复bug。但是如果在开发中期需要快速上线功能,比较困难。

这种策略下,我们无法同时维护多个版本,但是可以支撑快速发布部分新功能,因为新特性互不干扰,没有交错提交的问题。

2.2 分支开发,主干发布

这种策略下,对于一个新的功能,会拉出一个新的分支,称为特性分支。在特性分支上进行该功能的开发和测试,通过后合并回主干,在主干进行回归测试。
如果粒度比较细的话,一个新特性就是一个分支。如果粒度粗一些,可以以一个模块,或者一个快速迭代为一个分支。这两者的区别在于特性分支存在的时间长短。


这种分支策略下,功能的开发比较独立,不会因为并行开发而受到干扰。同时这种实践需要特性拆分足够小,并且需要强大的测试环境支撑,最好有持续集成和自动化测试的支撑。

2.3 主干开发,主干发布

这种策略下,只有一条分支——主干分支。开发人员的代码直接提交到主干上,软件的发布也基于主干。因此,这种策略要求主干随时处于可发布的状态,从而要求每一次的代码提交都是非常高质量的。因此,在代码提交的时候,要有严格的代码审查,要有充足的自动化测试。每一次代码提交都会触发完整的编译构建、单元测试、代码扫描、自动化测试等过程。
这种策略能够解决什么问题呢,其实个人觉得前面提出的问题,一个的解决不了。这种策略的要求是把所有的问题消灭于萌芽前,不是治病而是”治未病“。这种策略带来的好处就是可以很好的支撑持续集成和持续交付,达到一天多次交付。
大道至简,这种策略就是最简单的策略,甚至可以说是没有策略,这个就是传说中的无招胜有招了。虽然很牛,但是不是每个公司都能够玩得转的,就像没有悟性是玩不转无招胜有招的。

2.4 分支策略小结

这3种策略各自适应不同的场景和要求,没有最好,只有最合适。而且,我们可以根据自己的需求进行裁剪合并,并不一定拘泥于其中的一种。我比较喜欢的策略就是**主干开发,分支发布 + 特性分支”的方式。

3、业界使用比较广泛的分支工作流程

目前业界的代码管理中,使用最广泛的就是git,使用最广泛的分支工作流程有:

  • Git Flow

  • GitHub Flow

  • GitLab Flow

下面我们来详细的看看这3种流程。

3.1 Git Flow

下面是Git Flow的流程示意图。

Git Flow分支工作流程示意图

这张图猛一看有点乱,不太容易理解。这也是Git Flow的缺点之一:过于复杂。下面让我们来看看Git Flow的具体流程。
在Git Flow种,有很多分支,最主要的分支是Master分支和Develop分支。还有一个相对重要的分支是release分支。我们先来看看这3个分支之间的流程。
新的功能的开发,都在develop分支上提交。当满足发布条件的时候,从develop分支拉出release分支。如果有bug,则在release分支上进行修改和测试,bug修复过程种的每一次提交,都需要同步到develop分支上。当所有的bug修复,可以上线的时候,从release分支合并到master分支,并添加相应的版本tag。

还有两个分支,则是feature分支(特性分支)和hotfix分支。
Feature分支是从develop分支拉出的特性分支,相关特性的开发在特性分支上完成之后,再合并到develop分支。
Hotfix分支则用于修改线上的bug,hotfix分支的提交,最终都会合并回develop分支和master分支。合并到master分支的时候,也需要打版本标签。生产环境的发布,则从master分支来发布。

过于复杂和繁琐的流程,让现在使用git flow的公司越来越少。

3.2 GitHub Flow

GitHub Flow是完全的分支开发,主干发布。GitHub Flow种,只有master分支和特性分支。所有的代码都不能直接提交到master分支,而是提交到对应的特性分支,经过code review,测试后,再合并会master分支。这样,master分支随时处于可部署的状态。这么简单的流程,就不用画图了吧?实在要图,参考上面的分支开发,主干发布的图。
和Git Flow相反,GitHub Flow的最大的特点就是简单实用。但是需要有自动化测试、代码扫描的支持。

3.3 GitLab Flow

GitLab Flow其实是3个分支模型的总称,这3个模型如下表所示

分支模型

说明

带生产分支

适用于无法控制准确的发布时间,但又要求不断集成的项目。创建了一个Product分支来放置已经发布的代码。

带环境分支

适用于要为不同的环境创建不同的分支的项目,所有的代码都要在逐个环境种测试通过

带版本分支

适用于需要并行维护多个版本的项目,bug的修改先合并到master,然后再cherry pick到各个发布分支

这三种分支模型的示意图分别如下面的三个图所示。

带生产分支

带发布分支

带版本分支的

三、怎么选择合适的分支模型


1、不同分支策略比较


2、国内互联网公司的选择

GitLab作为最优秀的开源代码平台,被多数互联网大公司(包括阿里、携程和美团点评等)所使用,这些大厂也都采用特性分支开发策略。当然,这些大公司在长期持续交付实践中,会结合各自公司的情况做个性化的定制。

比如,携程公司在GitHub Flow的基础上,通过自行研发的集成加速器(Light Merge)和持续交付Paas平台,一起完成集成和发布。

再比如,阿里的AoneFlow,采用的是主干分支、特性分支和发布分支三种分支类型,再加上自行研发的Aone协同平台,实现持续交付。

3、选出最适合的策略

你应该已经比较清晰地理解了“主干开发”和“特性分支开发”两种策略的各自特性:

  1. “主干开发”集成效率高,冲突少,但对团队个人的开发能力有较高要求;

  2. “特性分支开发”有利于并行开发,需要一定的流程保证,能保证主干代码质量。

很难说有一种通用的分支策略可以满足我们所有场景的需求。但是,有些分支策略的原则更加适合于快速迭代发布的场景,也就更加适合 DevOps 的发展趋势。相信在没有绝对自信能力的情况下,面对绝大多数的场景,企业还是会选择“特性分支开发”的策略。所以,这里我个人比较推荐的是分支开发,主干发布的模式,也就是团队共享一条开发主干,特性开发基于主干拉出特性分支,快速开发验收后合并发布,同时,在特性分支和发布分支分别建立不同的质量门禁和自动化验收能力。

这样做的好处在于:

  • 保留“特性分支”的工作方式,便于团队协作;

  • 简化 Gitflow 的复杂分支策略,上手容易;

  • 灵活的特性分支组合集成,集成后亦可快速剥离;

  • 实现“准持续集成”

    • 略低于单主干,远高于 Gitflow 的集成频率 

    • 选择性的特性持续集成(方便灵活,但其实并非优点)

不过,在执行的过程中,需要遵守以下原则:

  • 团队共享一条主干分支;

  • 强力的特性拆分的能力;

  • 特性的粒度和分支存活的周期是关键要素。根据经验来看,分支存活的周期一般不要超过2周;

  • 特性分支的命名需规范;

  • 保证一个特性的关联改动需要提交到一条分支上,而不是到处都是,尽量做到原子性提交。


研发效能方法论
分享内容的四大方向:研发效能和软件工程方法论,软件工程技术,平台工程设计,通用五力