A successful Git branching model-抠脚翻译

请一定要结合英文原文观看,以防误导。

经典Git分支模型

前文

  文章主要介绍作者在撰文前一年左右在自己的项目中(包括工作和私下的)的开发模型,后续的事实证明真的很有成效。文章不会包含任何的项目相关信息,只会涉及分支策略和发布管理的内容。

Git分支图


为什么选择Git

  关于Git与集中式源码控制系统的优缺点的详尽讨论,可以参见GitSvnComparsion,那里有诸多的论战。

  作为一名开发者,我更喜欢Git而不是其他任意一个工具。Git真正的改变了开发者对于合并和分支的认知,我来自于古老的CVS/Subversion世界,合并和分支一直被认为是有些可怕的(小心合并冲突,会让你头疼)和偶尔才会做一次的事情。

  但是对于Git,这些操作变的十分简单和便捷,甚至被认为是日常工作流的核心部分之一。比如在一些CVS/Subversion的书籍中,合并和分支会在比较靠后的章节中才会初次讨论,属于进阶部分;而在Git书籍中却往往在第三章基础部分就已经出现了。

  因为其简单性和重复性的特征,合并和分支不再是令人害怕的事情。版本控制工具应该在合并/分支上能相比其他工具提供更多的协助。

  关于工具的介绍已经足够多了,接下来让我们进入开发模型部分。我将要介绍的模型本质上不过是一组流程,每一个团队成员都必须遵守这些流程才能进入到一个受管理的软件开发过程。


分散但又集中

  我们使用的仓库体制与这个分支模型能很好的结合在一起,就是使用一个中心”真实(truth)”仓库。需要注意的是,这个仓库只是被我们视为中央仓库(由于Git是DVCS-分布式版本控制系统,因此在技术层面上并没有中央仓库这个东西)。我们先用origin来指代这个仓库,这个名称对于所有Git用户都不会陌生。

子团队协作

  每个开发者会推送(push)和拉取(pull)到origin,但是除了集中控制的推拉关系外,每个开发者还可以从其他同伴那里拉取变更的内容从而组成了一个个子团队。例如,可能很有用的情况:两个或更多开发者一起工作开发一个新的大功能,这时将正在进行中的工作推送到origin可能会显得过早了。在上图中,有Alice和Bob, Alice和David,以及Clair和David的子团队。

  从技术上讲,这意味着Alice定义了一个命名为bob的Git remote,指向Bob的仓库,反之亦然。


主分支

  根本上讲,发展模式在很大程度上受到现有模式的启发。如中央仓库包括两个主要的分支,其生命周期为无限期。

  • master
  • develop

主分支

  每一个Git用户都很熟悉对应origin的master分支,还存在着另一个与master平行的分支叫做develop。

  我们认为origin/master是HEAD指向的源码一直对应生产就绪状态(production-ready)的主要分支。

  我们认为origin/develop是HEAD指向的源码一直对应为下一版本提供开发修改的最新一次交付的状态(production-ready)的主要分支,有些人会把它称为“整合分支”,这是所有每晚自动构建的基础。

  当develop分支中的源码达到稳定点并准备发布时,所有的修改都应该以某种方式合并到master分支,然后用发布号标记。我们会在后面的内容再详细介绍这一部分。

  因此,每次将修改合并回master分支时,根据定义这其实就是一个新的生产版本。我们通常会对此非常严格,因此理论上讲,每当有提交到master时,我们可以使用Git hook脚本自动构建和推送我们的软件到生产服务器上。


辅助分支

  除了主分支master和develop以外,我们的开发模型还使用各种辅助分支以帮助团队成员之间进行并行开发,简化功能跟踪,为生产发布做准备,并协助快速解决生产中的实际问题。但与主分支不同的是这些分支的生命周期有限,最终都会被删除。

我们可以使用的不同类型的分支包括:

  • Feature branches 功能分支
  • Release branches 发布版本分支
  • Hotfix branches 热修复分支

  这些分支中的每一个都有其特定的目的,并受严格的规则约束,包括哪些分支可能是其原始分支,哪些分支必须是其合并目标。我们会马上逐个的简单介绍它们。

  从技术角度看,这些分支绝不是”特殊”的。分支类型是按照我们的使用方式进行的分类,它们本身仍然只是普通的Git分支。

Feature分支

可能源自于:develop

必须合并回:develop

分支命名约束:除了master, develop, release-, or hotfix-

Feature分支

  Feature分支(有时也叫主题分支-topic)用于为即将或较远未来发布的版本开发新的功能。当开始开发功能时,此功能将要被合并到的目标版本可能还未可得知。Feature分支的本质是只要功能还在开发中就会一直存在,但最终会合并回develop分支(以确保将新功能加入即将发布的版本)或直接丢弃(如果开发结果令人失望)。

  Feature分支通常仅存在于开发人员的本地库中,而不会在origin远程库。

创建一个Feature分支

  在开始开发一个新功能时,请从develop分支中开启新的分支。

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

将已完成分支合并入develop分支

  已经完成的功能可以合并到develop分支,以确保会将它们添加到即将发布的版本。

1
2
3
4
5
6
7
8
$ 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历史记录中看到那些提交对象一起实现了功能——你将不得不手动读取所有的日志信息。在后一种情况下,还原整个功能(即一组提交)确实很令人头疼,但如果使用–no-ff指令则可以很轻松的还原。

  是的,这会创建更多的(空)提交对象,但收益远大于成本。

release分支

可能源自于:develop

必须合并回:develop 和 master

分支命名约束:release-*

  release分支为新的产品版本的发布准备提供支持,允许开发者在最后时刻进行细致的准备工作。此外允许进行小型错误的修复和准备版本的元数据(包括版本号、构建日期等)。

  从develop分支分离出新的release分支的关键时刻是develop(差不多)对应上新版本的期望状态。至少在这个时间点上,所有针对待构建版本的功能必须合并到develop分支,所有针对未来版本的功能则可能不会——它们必须等到release分支分离出来。

  正是在release分支的开始为即将发布的版本分配了版本号,较早则没有。直到这一刻,develop分支都对应着”下个版本”的修改,但是直到release分支开始之前,尚不清楚”下个版本”最终会变成0.3还是1.0。相关操作是在release分支开始才发生的,并且由项目的版本号bump规则来执行。

创建一个release分支

  release分支是从develop分支上创建的。比如,可以说版本1.1.5是当前的生产版本,我们会有一个大的版本更新即将发布。develop的状态已为”下个版本”准备就绪,我们决定将其确定为版本1.2(而不是1.1.6或2.0)。因此,我们创建了release分支,并通过版本号进行了命名。

1
2
3
4
5
6
7
$ 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版本号。bump-version.sh在这里是一个虚构的shell脚本,它会修改一些工作副本的文件并映射到新的版本。当然这可以是一个手动更改——要点是某些文件会更改。然后提交bump版本号。

  这个新分支可能已经存在了一定的时间,直到版本可以明确地发布出去。在此期间,一些Bug修复可能会应用在此分支上(而不是develop分支)。禁止在这个分支添加新的大型功能,它们必须合并到develop分支,因此需要等待下次大型版本更新。

完成一个release分支

  当release分支的状态就绪,并到真实发布阶段时,需要执行一些操作。首先,将release分支合并到master分支(记住,每次对master分支的提交都是一次新的发布)。然后,必须标记对master分支的提交,以便于将来比较容易的对这个历史版本的参考。最后,需要将release分支上的变更重新合并到develop分支,以便将来的发布版本也会包含这些Bug修复。

  前两个Git步骤:

1
2
3
4
5
6
$ 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

  此发布版本现在已完成,并标记1.2以供将来参考。

编辑:你可能也想使用 -s 或 -u < key >指令来对标记进行加密签名。

  为了保留release分支上所做的修改,我们仍需要将其合并回develop分支。

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

  这一步很可能导致合并冲突(非常可能发生,因为我们已经修改了版本号)。如果导致了合并冲突,请修复后再提交。

  现在我们已经完成了工作,可以删除release分支了,不会再需要它了。

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

Hotfix分支

可能源自于:master

必须合并回:develop 和 master

分支命名约束:hotfix-*

Hotfix分支

  Hotfix分支非常像release分支,他们都为了新的版本发布做准备工作,尽管是未计划过的。他们源自于对未达到期望的实时生产版本的快速反应。当必须立刻解决生产版本中的严重Bug时,可以从master分支上对应生产版本的标记分离出一个Hotfix分支。

  本质上当一个开发者在准备一个快速的生产修复时,其他团队成员可以继续工作(在develop分支上)。

创建一个Hotfix分支

  Hotfix分支是在master分支上创建的。比如,我们说当前正在运行的版本是1.2,并且因为严重的Bug造成了一些问题。而直接在develop分支上进行修改是不稳定的。所以我们会分离出一个Hotfix分支来解决问题。

1
2
3
4
5
6
7
$ 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(-)

  在分离出分支后别忘了bump版本号。

  然后,修复Bug并在一次或多次提交中提交所有修改内容。

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

完成一个Hotfix分支

  完成工作后,Bug修复需要合并回master分支,也需要合并回develop分支,以确保下一个版本更新也包括这些修复。这与release分支的完成过程非常类似。

  首先,更新到master分支并标记版本。

1
2
3
4
5
6
$ 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

编辑:你可能也想使用 -s 或 -u < key >指令来对标记进行加密签名。

  然后,也合并修复内容到develop分支。

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

  此规则的一个例外是:当已存在一个release分支时,需要把修复内容合并到release分支,而不是develop分支。当release分支已经完成后,合并到release分支的修复内容最终也会合并到develop分支。(如果开发人员需要立即修复这些Bug,并且不能等到release分支完成,也可以安全的将修复内容合并到develop)

  最后,删除临时分支。

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

总结

  尽管这个分支模型并没有什么让人震惊的新东西,但本文开头的”全局图”在我们的项目中被证明是很有用的。它形成了一个易于理解的优雅的思维模型,并可以使团队成员对分支和发布过程形成一种共识。

  此处提供了图片的高质量PDF版本,你可以把它挂在墙上,以便随时快速的参考。

  更新:为一些提出要求的人:这里是主图表图片(Apple Keynote)的gitflow-model.src.key

Git-branching-model.pdf


参考博客和文章书籍等:

A successful Git branching model

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容