翻译-Git分支管理的典范
作者:Vincent Driessen。翻译:Aningsk。本作品采用知识共享署名-相同方式共享 3.0 未本地化版本许可协议进行许可。
来翻译一篇国外的博客,介绍Git的分支管理策略。翻译不一定准确,顺便复习英语四级啦。哈哈,新短语get:dotting of i's and crossing t's。(为什么我觉得原文多个of)
原文:A successful Git branching model
作者:Vincent Driessen
时间:2010年1月5号 星期二
在这篇博文中,我将介绍在一年间我所有项目(包含工作的和私下的项目)的开发模式,我觉得这简直就是典范。我很早就想写这东西啦,现在终于有时间啦!我不会介绍任何项目的具体细节,仅仅介绍分支策略和发布管理。
这里以Git作为我们代码的版本控制工具。
为啥用Git?
关于Git和集中式代码管理系统的优劣讨论,请看这个网站,那上面有大量的网络论战。作为一个开发者,现在我更喜欢Git,其他的嘛……呵呵。Git重新定义了开发者心目中关于"合并"与"分支"的概念。我是从古老的CVS/Subversion世界过来的,在那些版本控制中,合并和分支总是一件有点可怕的事情("当心合并冲突,能恶心死你!"),并且合并或分支你不能同时操作。
但是当我来到Git世界,这些操作变得极其轻盈简单,而且这些操作简直都成为了你日常工作流程的核心。举个栗子,在CVS/Subversion的手册里,"分支"与"合并"都是在非常靠后的章节中才出现,那完全是给高级用户看的;而在每一本Git的书籍中,在第3章就见到了,完全就是基础功能嘛。
由于它简单且重复的特征,分支与合并操作不再是可怕的东西。版本控制工具都应该把对"分支"与"合并"的支持作为重中之重。
在工具上说的够多了,让我们来说说开发模式。在这里我将要呈现的模式只是一个规程,为了实现易于管理的软件开发流程,希望每个团队成员都能遵循这个规程。
"支"散而神不散
供我们使用的代码库能够在分支模式上很好的工作,这个代码库有一个核心"真实的"库。注意:这个库只是人们认为的核心代码库(Git作为一个分布式版本控制系统,在技术层面上不存在什么核心代码库的概念)。我们称这个库为origin,对于Git的用户来说这个名字可并不陌生。
每一个开发者都向原点库(origin)下载(pull)和推送(push)。除了来自核心的下载与推送关系,每个开发者都有可能要从一些子团队的其他同事那里下载变更的代码。举个栗子,面对一个庞大的新特性,两个或更多的开发者协同工作是非常有用的,除非有人过早的将工作推送到origin。在上面的图中,有子团队Alice和Bob、子团队Alice和David、子团队Clair和David。
从技术上来说,这无非就是Alice定义一个叫做bob的Git远程库,指向Bob的代码库,反之亦然。
主分支
在核心思想上,这个开发模式很大程度上是受启发于这里已存在的模式——核心代码库拥有两个无限生命周期的主分支:
master
develop
在origin上的master分支应该被每个Git用户所熟知。平行于master分支的另一个分支叫做develop。
我们始终把由HEAD指向的origin/master上的代码,作为就绪产品(production-ready)状态。
我们始终把由HEAD指向的origin/develop上的代码,作为为开发下次发布版本而最新提交的代码改动的状态。或许叫做"整合分支"更贴切,因为自动每晚更新(nightly builds)就是更新这个分支。
当develop分支上的代码达到稳定的状态并且准备发布,其上的所有代码改动都应该合并回master分支,并标记上发布编号。这个操作的细节待会儿会讨论。
因此,每一次代码改动合并回master分支,都定义为一个新的产品发布。我们倾向于严格遵守这条理论,所以每次提交到master分支,我们就可以使用Git的挂钩脚本(hook script)自动编译并把我们的软件展示给产品服务商。
辅助分支
在主分支master和develop之后,我们的开发模式使用了各种各样的辅助分支,来帮助团队成员间的平行开发。辅助分支容易追踪产品的特点、方便准备产品发布、促进快速修复产品问题。不像主分支,辅助分支通常有有限的生命周期,因为它们最终会被移除。
我们使用的不同类型分支有:
特点分支(Feature branches)
发布分支(Release branches)
热修复分支(Hotfix branches)
每一种分支都有它们特定的目的,一定要严格遵守这条准则:辅助分支从哪条分支分离出来,最终一般就要合并到那条分支上去。待会儿我们会浏览一下它们的用法。
注意,这些分支从技术角度上没有任何特别之处,划分出这些分支类型完全是因为我们以这样的意图使用它们。当然它们就是普通的Git分支。
特点分支(Feature branches)
可能从哪里分支出来:
develop
必须合并回哪里:
develop
分支命名约定:
除了master、develop、release-*、hotfix-*之外,名字随便起。
特点分支(有时候也叫主题分支[topic branches])用来开发一个即将面世的或一个代表未来的新特点。当开始一个新特点的开发时,这个目标可能包含了一些未知的特点。特点分支的真髓就是:它存在于整个特点的开发周期中,最后它会被合并到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 myfeaturn (was 05e9557)
$ git push origin develop
使用 --no-ff选项可以使此次合并操作总是创建新的的提交对象,即使此次合并可以以快速模式(fast-forward)进行。这是为了避免丢失一个特点分支的历史存在信息,并且能够把所有与该特点有关的提交聚合在一起。大家可以比较一下:
在后者,你不可能从Git历史中看到有关这个特点的提交对象——你必须亲自阅读全部的记录信息,才能知道哪些提交是属于这个特点的。让我们回到整个特点(也就是那一堆提交),在后者中,我们看不到清晰的界限,谁看了也会头疼;然而,如果有--no-ff选项,就完全没有问题。
的确,这会额外创建一点(空的)提交对象,但是这点开销相对于获得的效果完全不是事儿。
可惜的是,我没有找到让--no-ff选项成为git merge默认选项的方法,它真是应该成为默认的啊。
发布分支(Release branches)
可能从哪里分支出来:
develop
必须合并回哪里:
develop和master
分支命名约定:
release-*
发布分支是用来支持新产品发布前的准备工作。它们为最后时刻的仔细核查留出余地。此外,它们也是为了修复小BUG和准备发布版元数据(版本号、编译日期等等)留出了余地。至少,全部以发布-编译为目标的代码,必须通过这里在此时合并到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-version.sh是一个虚构的shell脚本,用来修改工作副本中的某些文件来反映新的版本号。(当然,可以手动更改;这里要说的是:某些文件改动了。)然后,提交对版本号的更改。
完成一个发布分支
当一个发布分支已经为成为一个发行版做好了准备,还有一些工作要做。首先,将发布分支合并到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).
热修复分支(Hotfix branches)
可能从哪里分支出来:
master
必须合并回哪里:
develop和master
分支命名约定:
hotfix-*
热修复分支跟发布分支非常像,因为它们都是意味着准备新的产品发行,虽然热修复分支不在计划中,呵呵。在现行版本产品出现了我们不期望看到的状态时,就需要热修复分支了:我们需要对产品不正常的状态立即采取行动。当现行版本产品中危险的BUG必须立即解决时,热修复分支常从标记有相应现行版本号的master分支上分离出来。
它的本质特征是:整个团队成员的工作(在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 servere production problem"
[hotfix-1.2.1 abbe5d6] Fixed servere production problem
5 files changed, 32 insertions(+), 17 deletions(-)
完成一个热修复分支
修改完成,对BUG的修复需要合并回master分支,但同时需要合并回develop分支,这是为了确保本次修复依然包含在以后的发行版中。这与完成一个发布分支时的操作出于完全相同的意图。
首先,更新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>选项来暗地里标记你的标签。
然后,也要把对BUG的修复包含进develop分支:
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
对于这一条规则有一个例外:当有一个发布分支已经存在了,热修复分支需要合并到这个发布分支中,而不是develop分支。当发布分支已经完成,合并BUG修复到发布分支,最终也会将此次修复合并到develop分支的。(如果在develop分支上的工作迫切需要此次修复,并且不能等待发布分支的完成,你也可以安全地将此次修复合并到develop分支中。)
最后,移除这个临时的分支:
$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).
总结
这个分支模式里面真没有什么惊世骇俗的东西,博文开始贴出的"大图"对我们的项目真的非常有用。它是那么的易于理解,以优雅而直击心灵的方式,向团队的每个成员讲述一个在开发过程中,他们之间共享、默契的过程。
这里提供了一个高清的PDF版本。进击吧,少年!把它挂到墙上,时时刻刻提醒自己。
更新:任何人如果需要的话可以在gitflow-model.src.key下载主要的图片(使用苹果公司Keynote软件)。
如果你想与我取得联系,推特,我是@nvie。
作者:Vincent Driessen
翻译:Aningsk
2015-10-15