猫头鹰的深夜翻译开发者必须了解的分支发布模型

52次阅读

共计 4870 个字符,预计需要花费 13 分钟才能阅读完成。

前沿

本文只会聚焦于分支发布策略,不会涉及任何的项目细节。

为什么选择 Git

想要深入了解 Git 和中心化代码版本管理系统的优缺点比较,可以在网上自行查询,这个话题一直争论不休。作为一个开发者,我更倾向于使用 Git。Git 确实改变了开发者对代码合并和分支管理的认识。作为一个使用过传统的 CVS 工具的人,合并 / 开分支是一个比较恐怖的行为,迫不得已才执行一次。

但是,Git 的分支合并和创建操作就非常简单,这些操作已经被是为每日工作流的一部分。比如,在介绍 CVS 的书本中,分支和合并只会在末尾的章节面向高端开发者介绍一下,而在每一本说 Git 的书中,基本上在第三章就会说明。

因为 Git 在这个操作上的简洁性和重复性特性,分支开发和合并不再是一件可怕的事情。版本控制工具本就应当更好的帮助分支的创建和合并。

不谈工具,接着要介绍开发模型。今天我要介绍的这个模型本质上是一组流程,每个团队成员通过遵循这一组流程来管理软件开发过程。

去中心化又中心化

在分支发布模型中,我们会设置一个中心化的“真理”仓库。这个仓库只是名义上的中心仓库(因为 Git 是一个去中心化的仓库,因此从技术角度上来说这个仓库不是中心化的)。我们将该仓库标记为 origin,因为 origin 这个命名对于所有的 Git 用户来说都是很熟悉的。

每个开发者从 origin 拉取和上传代码。每个开发者之间也可以从彼此的代码仓库拉取或构件子代码团队。这种方式适用于大型团队,即子团队可以先在一个团队分支上进行特性开发,在特性稳定后再提交到 origin 仓库。

主分支

中央仓库持有两个拥有永久生命周期的分支:master 和 develop
origin 仓库中的 master 主分支对每个 Git 用户都是再熟悉不过的了。和 master 分支平齐的分支称为 develop 开发分支。

origin/master 分支是项目的主分支,源代码的 HEAD 标签永远指向了该分支上的一个可发布版本。

我们将 origin/develop 分支是为另一种类型的主分支,该分支上的代码始终处于一个稳定的可发布的状态。该分支上的所有变更都应当被合并到 master 分支上,并搭上一个发布标签。具体的操作将在后文详细描述。

因此,每当变更被合并到 master 分支上时,都意味着释放了一个新的发布版本。我们严格的遵循这一定义,因此理论上来说,每当有一个代码提交至 master 分支上,我们应当使用 Git 钩子来自动构建和发布代码到生产环境服务器。

辅助分支

在主分支 master 和 develop 之外,开发过程模型还使用了各种辅助分支来实现团队成员间的并行开发,简化功能开发,做生产发布的准备工作和快速修复生产环境的故障。和主分支不同,这些分支都有着一定的生命周期,因为他们最终都会被删除。

辅助分支的类型包括:

  • 特性分支 Feature Branch
  • 发布分支 Release Branch
  • 修复分支 Hotfix Branch

每一类分支都有着特定的目标,并且受限于不同的约束,比如该类分支应当从哪类分支中生成,最终合并到哪类分支中去。

但是这些分支从 Git 的角度上来说并无区别,它们就是通常意义的 Git Branch,只是我们赋予了他们不同的使用语义。

特性分支 Feature Branch

来源分支:develop 开发分支
合并分支:develop 开发分支
分支命名规范:master, develop, release-, 和 hotfix-


特性分支是为了支持未来上线的新功能来进行开发分支。在开始特性分支开发时,对应的未来的发布分支可能是未知的。特性分支的精髓在于只要该特性还在开发中,该分支就会一直存在,并会最终合并回 develop 开发分支(确定该特性会被发布)或是被抛弃(特性的开发结果没有达到预期)。

特性分支一般只存在于开发者的仓库中。

创建特性分支

$ git checkout -b myfeature develop
Switched to a new branch "myfeature"

合并开发完成的特性分支到 develop 分支中

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
Deleted branch myfeature (was 05e9557).
$ git push origin develop

--no-ff标签使得合并操作创建一个新的提交记录,即使该合并可以被 fast-forward 快速合并到目标分支中。这种方式避免了所有在 feature 分支上的历史提交记录的丢失。对比如下:

右图中无法从 Git 提交历史中看出来哪些提交记录构成了 feature 分支,你需要手动阅读所有的日志消息。回滚特性代码在右图中是一件非常麻烦的事情,但是通过 --no-ff 合并的左图中就比较容易。虽然它会创建更多的提交记录,但是总的来说优点大于缺点。

发布分支 Release Feature

来源分支:develop 开发分支
合并分支:develop 开发分支和 master 主分支
分支命名规范:release-*

发布分支支持为一个新的生产环境发布做准备。它允许在上线之前进行最后的操作。除此以外,它允许小的 bug 修复和发布前的元数据准备操作(版本号,构建日期等)。通过在发布分支上做这些事情,开发分支变得更加简洁,只需要接收未来会发布的新特性。

当开发分支基本达到了发布一个新版本的状态时,需要从开发分支创建一个新的发布分支。所有满足发布条件的特性分支此时必须已经被合并到开发分支中。而尚未达到发布条件的特性分支无需合并到 master 分支,它们必须等到发布分支生命周期结束后才能合并到开发分支上。

在创建发布分支时,会为本次发布定义一个版本号(不会早于此)。直到这一刻,用于下一次发布的开发分支才知道自己下一次的发布版本号为 0.3 还是 1.0。在发布分支创建时会按照版本号的演进规则决定本次发布的版本号。

创建发布分支

发布分支创建于开发分支。比如,假设当前生产环境的版本号为 1.1.5,并且接着会有一个重大的发布。此时开发分支已经达到了下一次发布的要求,并且我们决定该版本为 1.2(不是 1.1.6 或是 2.0)。因粗我们创建分支并且在发布分支的命名中带上了新的版本号:

$ git checkout -b release-1.2 develop
Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2
Files modified successfully, version bumped to 1.2.
$ git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed, 1 insertions(+), 1 deletions(-)

在创建了新的分支并切换到该分支上后,我们开始切换版本号。在此处,bump-version.sh是一个虚拟的 shell 脚本,它会在发布版本文件上执行一些版本操作(当然了这些操作可以通过手动完成)。接着,版本操作被提交。

这个新分支会存在一段时间,直到发布被完全执行。在此期间,可以在分支上修复 bug(而非 develop 分支上),但是禁止添加大的新特性。它们必须先被合并到开发分支,然后等待下一个发布节点。

结束发布分支

当发布分支确实可以发布时,在此之前需要执行一些操作。首先,发布分支被合并到 master 分支上。接着,提交到 master 分支上的代码必须打上标签,从而在未来可以引用该历史版本。最后,发布分支上的代码必须被合并会开发分支,使得开发分支上的代码也包含了发布版本的内容以及修复的 bug。

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2

此时发布已经完成,并且打上了标签以便引用。
接着将变更合并会 develop 分支:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)

这一步合并操作可能会存在冲突,需要解决冲突并提交。
在这些都完成后,我们可以删除掉发布分支,因为我们不再需要该分支了:

$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

修复分支 Hotfix Branch

来源分支:master 主分支
合并分支:develop 开发分支和 master 主分支
分支命名规范:hotfix-*

修复分支和发布分支很接近,因为它们都是为了生产环境发布做准备的一个分支。修复分支产生的缘由在于需要立刻对生产环境中的代码进行快速变更。当生产环境版本代码出现重大 bug 需要立刻处理时,可以从对应版本的生产环境代码拉一个修复分支进行处理。

该分支的核心点在于团队的其他成员可以继续基于开发分支进行开发,而负责修复的成员可以独立的快速修复生产环境问题。

创建修复分支

修复分支创建于 master 分支。比如版本 1.2 是当前正在运行的生产环境版本,并且因为一个缺陷出现了故障。但是开发分支上的代码尚不稳定。我们需要创建修复分支并开始修复问题:

$ git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1"
$ ./bump-version.sh 1.2.1
Files modified successfully, version bumped to 1.2.1.
$ git commit -a -m "Bumped version number to 1.2.1"
[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)

切换新的发布版本后不要忘了切换代码中的版本号
接着在一次或多次提交的代码中修复问题。

$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)

关闭修复分支

修复完毕后,修复分支需要合并会主分支,也需要合并回开发分支,从而确保该修复包含在下次发布代码中。这与发布分支的关闭过程完全相同。
首先,更新 master 代码的标签并发布:

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2.1

接着将代码合并回开发分支:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)

这里一个特殊点在于,如果此时存在一个发布分支,修复分支的变更需要合并到发布分支中,而非开发分支。合并到发布分支中,可以最终将该修复通过发布分支带回到开发分支中。(如果开发分支也需要立刻修复该问题,无法等到发布分支合并回来,也可以先将该变更安全的合并会发布分支)

$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).

正文完
 0