一个diaodiao的Git分支模型(译)

原文出自:http://nvie.com/posts/a-successful-git-branching-model/

以下是翻译内容:

在这篇文章里,将展示我在大约一年前为我的一些项目(包括工作上的和私人的)所提出的开发模型,它最终也被证明是非常成功的。我早就打算写这样一篇文章,但直到现在才觉得是一个将其彻底完成的好时机。我不会讨论这些项目任何的细节,而仅仅是关于分支策略和发布管理的东西。


文章将围绕Git作为管理我们所有源代码版本的工具。(然后作者强行植入了一个广告,略:)

为什么用git?

网上有关于Git相比于集中式源代码控制系统的优缺点的详细讨论,这自然免不了许多争吵。作为开发者,现在我是喜欢Git甚于其它一切工具。Git真心改变了开发者对于合并与分支的看法。我来自经典的CVS/Subversion世界,在那里,合并/分支一直被人们觉得有点可怕(“小心合并时产生的冲突,它们会咬你哦!”),它们却是你经常要做的事情。

但是有了Git,这些行为就变得十分容易,代价也更低,而且它们都被当成你日常工作流程的核心部分之一了,真的。举个栗子,在CVS/Subversion书籍中,分支与合并在很靠后的章节中才会被讨论(面向高级用户的),而在每一本Git书中,它们的内容在第3章就已经被覆盖了(属于基础的)。

由于Git带来的简单性和重复性,分支与合并不再是让人惧怕的东西。版本控制工具对分支/合并的帮助能力也可以超过其它一切事物。

关于工具说得已经够多了,接着就来讲本文的开发模型吧。我将要在此展示的模型,本质上不过是一系列步骤,每个团队成员都应该遵循这些步骤,这有利于实现对软件开发过程更好的管理。

分散的,又是集中的

我们所使用的仓库配置应用了这一分支模型,运作良好,它有一个中心的“真实”仓库。请注意,这个仓库只是被“当作”是中心仓库(因为Git是一种DVCS,在技术层面上来说,并不存在所谓的中心仓库)。我们将用origin来代指这一仓库,这个名字对广大的Git用户来说一定很熟悉。

扫描二维码关注公众号,回复: 4115828 查看本文章


每个开发者都会对origin做很多pull和push操作。但除了上述集中的push-pull关系,每个开发者也会从其它人那里拉取更新,这就形成了子团队。比如说,当你和其它两个或更多的开发者一起开发一个大的功能时,为了避免过早地将当前进度push到origin,开发者之间的pull就会很有必要。上述图片中,存在Alice和Bob、Alice和David、Clair和David的工作小组。

从技术上来说,这也就是Alice定义了一个Git远程仓库,叫做bob,它指向Bob的仓库,反之亦然。

主要分支

实质上,本文的开发模型很大程度上是受到了下边这个已有的模型的启发。中心仓库持有两个主要分支,它们有着无穷的生命周期:

  • master
  • develop


origin的master分支对每个Git用户来说都应该很熟悉。与master分支相并列,存在另一个叫做develop的分支。

我们将origin/master视为主要分支,该分支上HEAD的源代码总是反映了一个可以交付的状态。

我们将origin/develop也当作主要分支,该分支上HEAD的源代码总是反映了最近提交的、为下一个发布版本而做的开发更新。有人称之为“整合分支”。这也是所有夜间自动构造的来源之处。

当develop分支上的源代码趋于稳定,已经可以发布时,所有的代码更新都应该以某种方式合并回master,并被标记一个发布号码。上述行为具体是如何做到的,后面会进一步讨论。

因此,每一次将更新合并到master时,从定义上来说,一个新的生产发布版本诞生了。我们一般对此十分严格,于是从理论上讲,我们可以用一个Git的hook脚本,每次master上有提交,脚本会自动构建软件并推出到生产服务器上。

辅助分支

紧接着主要分支master和develop,我们的开发模型使用了各种各样的辅助分支,从而可以给团队成员之间的并行开发提供支持,轻松地跟踪功能,为生产发布做准备,以及快速修复现场的生产问题。与主要分支不同的是,这些分支都只有有限的生命周期,因为它们最终都会被清除。

我们使用到的这些辅助分支有下述几种:

  • 功能分支
  • 发布分支
  • 热补丁分支

其中每条分支都有一个特定的目的,并被严格的规则所限定,如它们的起源分支可以是哪条,它们要合并进的目标分支又必须是哪条等。我们一会儿就详细讨论他们。

从技术角度看,这些分支一点也不特殊。上述的分支类型只是根据我们如何使用它们来分类的。它们当然就是普普通通的、老掉牙的Git分支啦。

功能分支

可以来源于:develop。

必须合并到:develop。

分支命名惯例:随意,除了叫mater、develop、release-*,或者hotfix-*(它们都名花有主啦)。


功能分支(有时也叫主题分支)是用来为即将到来、或是在遥远未来的发布而开发新功能的。当开始一个功能的开发,该功能将会整合到哪个发布版本里面去,这个时候可能还不得而知。功能分支的本质是,只要这个功能还在开发,该分支就会一直存在,但最终都会合并回develop(确定要将该新功能添加到即将到来的发布里)或是被丢弃(万一实验结果让人失望呢)。

功能分支通常只存在于开发者的仓库,而不是在origin里。

创建功能分支

当开始开发一个新功能,从develop分支上创建分支。

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

将完成的功能整合到develop

可以将完成的功能合并到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)来执行合并。这就避免了丢失功能分支的历史信息,也将所有形成该功能的提交组合到了一起。比较下面两者:

在后面一种情况中,想要从Git历史中看出到底是哪些提交对象加在一起实现了某个功能,这是不可能的——你将不得不去人工阅读所有的日志信息。该情况下想要revert掉整个功能(即一组提交)也会很头痛,不过要是用了 --no-ff 标志就会简单多了。

是的,它会创建一些额外的(空的)提交对象,但是得到的好处远比付出的成本要多。

发布分支

可以分支于:develop

必须合并到:develop和master

分支命名惯例:release-*

发布分支为一次新的生产发布的准备工作提供支持,它可以让你在最后一分钟仍然一丝不苟、精益求精。而且,它允许小的bug修复以及为发布准备元数据(版本号、构建日期等)。通过在发布分支上完成上述所有的工作,develop分支就可以继续接受为下次的大发布更新的功能了。

从develop上分出一个新分支的重要时机,就是当develop(基本上)已经反映了新发布的预想状态时。此时,至少所有针对此次发布的功能必须及时合并到develop上。而面向将来发布版本的功能就不需要合并了——它们必须等到目标发布分支被创建。

也就是在发布分支被创建的时候,即将发布的版本才得到一个版本号——而不是更早。在那之前,develop分支虽然反映了下次发布版本的更新,但是下一个发布版本会是0.3还是1.0,这要到发布分支创建的时候才能确定。这个问题在相应的发布分支开始时,由项目的版本号选取规则所决定。

创建发布分支

发布分支从develop分支创建。举例来说,假设当前生产发布版本是1.1.5,我们将要发布一个大的版本。develop已经准备好了下次发布,并且我们该发布将会是版本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)版本号。这里,bump-version.sh是一个假想的脚本,它更新工作拷贝目录里的一些文件来反映新版本(这当然可以手工完成——重点在于“一些”文件的更新)。然后,提交更新的版本号。

这个新分支可能会存在一段时间,直到发布版本被真正推出。在存在期间,bug的修复可能会应用到这一分支上(而不是到develop上)。在这里增加大型的新功能是被严格阻止的,它们应该被合并到develop里面去,并且等待下一个大的发布。

结束发布分支

当发布分支准备好被真正发布时,需要进行一些措施。首先,将发布分支合并到master上(因为从定义上讲,master上面的每次提交都是一个新的发布版本,还记得吗)。然后,master上的这次提交必须被加上标签,以便于将来能方便地引用这一历史版本。最终,发布分支上的更新需要被合并回develop,这样以后的发布版本也可以包含这些bug的修复。

Git上首先要做的两步是:

$ 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

发布现在完成了,而且也为未来的引用打好了标签。

(编者按:你可能也会想使用 -s 或 -u <key> 标志来加密地标记。)

为了记录发布分支里的更新,我们也需要把它们合并回develop。在Git里:

$ 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).

热补丁分支

可以分支于:master

必须合并回:develop和master

分支命名惯例:hotfix-*


热补丁分支在某种程度上和发布分支很像,因为它也是用来为一次新的生产发布做准备的,尽管它是计划之外的。当一个现场的生产版本发生了意外且有必要立即对其采取行动时,热补丁分支就出现了。当生产版本中的一个致命bug必须被立即解决,可以从master分支上找到相应的标记了该生产版本的tag,分出一个热补丁分支。

这样做的本质是,团队成员的工作(在develop分支上的)可以继续进行,同时另外的人在准备快速地修复生产版本。

创建热补丁分支

热补丁分支创建自master分支。比如,假设1.2版本是当前实际运行的生产发布版本,由于一个严重的bug造成了问题,而develop上的更新尚未稳定。于是我们可以分出一条热补丁分支,开始修复问题:

$ 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(-)

创建分支后,不要忘记更新版本号哦!

然后,修复bug并将更改提交,这可能会需要一次或多次的提交。

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

结束热补丁分支

当修复完成,修复更新需要合并回master,也需要合并回develop,从而保证在之后的发布版本中bug也得到了修复。这与发布分支的结束方式完全类似。

首先,更新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

(编者按:你可能也会想用 -s 或 -u <key> 标志来加密地标记。)

接下来,将热补丁也包括进develop中:

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

这里的规则有一个例外,如果目前存在发布分支,那么热补丁分支的更新需要被合并到该发布分支里去,而不是develop。将热补丁合并回发布分支,最终也会被合并回develop,只要发布分支的周期结束。(如果develop上的工作即刻需要这一热补丁,且不能等到发布分支的结束,你可以将热补丁安全地合并回develop里。

最后,移除这个临时的分支:

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

总结

虽然这一分支模型没有什么真正让人震惊的新东西,那张本文开始处的“大图”已经在我们的项目中被证实其卓越的价值所在。它构造了一个优雅的心理模型,该模型很容易理解,也使得团队成员们对分支和发布过程有了共识。这里也提供该图片的高质量PDF版本。去吧骚年,把它挂在墙上,以后随时可以迅速地参考它。

Git-branching-model.pdf

如果想联系我(原文作者啦,本少你当然直接在博客就可以联系到),我是twitter上的@nvie


猜你喜欢

转载自blog.csdn.net/Fun_Woody/article/details/71424918