第一次翻译,轻喷,如有错误请在评论区指出,我会尽快修改。
以下是正文
在这篇文章中,我将介绍一年前我为我的一些项目(包括工作项目和私人项目)引入的开发模型,这个模型非常成功。我想写这篇文章已经有一段时间了,但是一直没有足够的时间去认真地写这篇文章,现在有这个机会了。我不会谈论任何项目的细节,仅仅是谈论关于分支策略和发布管理。
为什么是Git?
有关Git与集中式源代码管理系统相比的利弊的深入讨论,请参见这篇文章。这往往涉及很多争论。作为一名开发者,我爱Git胜过其他一切工具。我曾经使用古老的CVS/Subversion,合并/分支一直被认为有点可怕(“小心合并冲突,他们会咬人”),有些事情你只想经历一次。
但是使用Git,这些操作非常方便和简单,他们会成为你工作流程的核心部分之一。例如,在CVS/Subversion手册中,分支和合并第一次被讨论是在后面的章节(高级用法),而在每个Git手册中,第3章(基础)就进行了介绍。
由于它的简单性和重复性,分支和合并不再是什么可怕的东西。版本控制工具应该比任何其他工具更有助于进行分支/合并。
关于工具的讨论就到此为止,让我们进入开发模式吧。我在这里将要介绍的模型本质上不过是一组过程,每个团队成员都必须遵循这些过程才能进入一个托管软件开发过程。
分散但集中
我们使用的仓库设置和分支模型能够很好地工作,主要是因为有一个“真正”的中央仓库。请注意,这个仓库仅被视为中央仓库(由于Git是DVCS,因此在技术层面上没有中央仓库)。 我们将此仓库称origin,因为所有Git用户都熟悉该名称。
每个开发者都从这个origin仓库拉去和推送代码。但是,除了集中推拉关系之外,每个开发者还可以从其他同行那里拉取修改,组成子团队。例如,与两个或多个开发者一起开发一个比较复杂的新特性,将正在进行的工作提前推动到origin仓库,这样会很有用。上面图有Alice和Bob,Alice和David,Clair和David三个子团队。
从技术上讲,这不过是ALice定义了一个名为Bob的指向Bob的仓库的远程仓库,反之亦然。
主分支
在核心方面,开发模型受到现有模型的极大启发,中央仓库有两个无限生命周期的主分支。
- master
- develop
每个Git用户都熟悉origin仓库的master分支。与master分支平行的另一个分支被称为develop。
我们把origin/master当做用于生产环境中的源代码的主分支。
origin/master中的源代码总是代表了下一个发布的版本。有些人将此称为“整合分支”。这是任何自动夜间构建的地方。
当开发分支中的源代码到达一个稳定点并准备发布时,所有的更改都应该以某种方式合并回主程序中,然后用一个发布号进行标记。如何进行详细处理将进一步讨论。
因此,每次更改被合并到master分支时,就定义了一个新的生产版本。在这一点上,我们是非常严肃的,所以从理论上讲,我们可以使用Git钩子脚本,在每次提交maser分支时候,在产品服务器上自动构建和回滚我们的软件。
辅助分支
接下来,我们的开发模式使用各种辅助分支来帮助团队成员之间的并行开发,方便追踪新特性,准备生产发布,帮助快速修复现线上产品问题。与主分支不同,这些分支总是具有有限的寿命时间,因为它们最终将被移除。
我们可能使用的不同分支有:
- Feature 分支
- Release 分支
- Hotfix 分支
每个分支都有特定的用途,并且必须遵守严格的规则,即哪些分支可以是它们的起始分支,哪些分支必须是它们的合并目标。我们马上来看看他们。
从技术角度看,这些分支绝不是“特殊”的。分支类型是根据我们使用它们的方式来分类的。它们仍然是我们熟悉的Git分支。
feature分支
可能创建于:
develop
必定会合并到:
develop
分支命名约定:
除了master,develop,release-*,或 hotfix-*以外
特性分支(或有时称为主题分支)用于为即将发布或遥远的将来版本开发的新特性。 当开始开发特性时,此时可能不知道将合并该特性的目标版本。 特性分支的本质是只要特性在开发中就存在,但是最终会合并回到develop分支中(以确保将新功能添加到即将发布的版本中)或丢弃(如果实验令人失望)。
特性分支只存在与开发人员的仓库,不存在于origin仓库
创建一个特性分支
$ git checkout -b myfeature develop
// 创建并切换到“myfeature”分支
将完成的特性合并到develop分支
$ git checkout develop
// 切换到“develop”分支
$ git merge --no-ff myfeature
// 将新特性合并到“develop”分支
$ git branch -d myfeature
// 删除特性分支
$ git push origin develop
// 推送变更
--no-ff使合并始终创建一个新的提交对象,即使合并可以通过快进来执行。 这样可以避免丢失有关特性分支历史存在的信息,并将所有添加了特性的提交组织在一起。 比较:
在第二种情况下,无法从Git历史记录中看到哪些提交对象一起实现了特性,您将不得不手动读取所有日志消息。 在第二种情况下,还原整个功能(即一组提交)确实很头疼,而如果使用--no-ff则很轻松很多。
当然,这将会产生一些额外的(空)提交,但是收益比成本大得多。
release分支
可能产生于
develop
必须要合并到
develop 和 master
分支命名规范
release-*
release分支支持一个新的生产版本的发行。他们允许最后几分钟的修改。此外,他们允许较小的错误修复和准备版本的元数据(版本号、构建日期等)。通过在发布版本上执行这些操作,develop将做好发布下一个大版本的准备。
当开发版本已经能够反映新版本特性的时候,就是将release分支从develop分支中分离出来的好时机。在这个时候,至少最新的针对最新发型版本进行构建的新特性都必须要合并回develop分支。所有针对未来版本的特性则不需要合并——它们必须等到release分支被分离后才能合并。
在准备一个release分支的时候就可以为这个版本进行命名了,不需要太早。直到那一刻,develop分支仍反映“下一版本”的变化,但直到分离分支开始,尚不清楚该“下一版本”是否最终变成0.3或1.0。确定版本号是在准备一个release分支时开始的,并根据项目在版本号上的规则来决定。
创建一个release分支
发布分支是从开发分支创建的。例如,假设1.1.5是当前的生产版本,我们即将发布一个大版本。开发的状态已经为“下一个版本”做好了准备,我们已经决定这将变成1.2版(而不是1.1.6或2.0)。因此,我们退出发行版,并给发布分支一个反映新版本号的名称:
$ git checkout -b release-1.2 develop
// 切换到一个新分支 release-1.2
$ ./bump-version.sh 1.2
// 文件修改成功,版本号 1.2
$ git commit -a -m "Bumped version number to 1.2"
在创建一个新的分支并切换到它之后,我会修改版本号。在这里bump-version.sh
是一个虚构的shell脚本,它可以更改工作副本中的一些文件,以反映新版本(当然也可以手动更改——关键是一些文件发生更改),然后Bump版本被提交。
该新分支可能会保留一段时间,直到版本被明确的发布后。在此期间,可以在该分支中(而不是develop分支)进行bug修复。在此期间,严格禁止添加较大的新功能。它们必须要合并到develop分支中,在下一个大版本中发布。
完成一个release分支
当release分支准备好发布的时候,需要执行一些操作。首先,release分支合并到master分支(因为master分支的的每一次提交都是一个新版本,一定要记住)。接下来,master分支上的下一次提交需要进行标记,以便日后回顾历史版本。最后,在release分支上所做的更改需要合并回develop分支,以便将来的版本也包含这些错误修复。
在Git中的前两步
$ git checkout master
// 切换到master分支
$ git merge --no-ff release-1.2
// 进行合并
$ git tag -a 1.2
// 打标签
现在完成发布,并做好了标记。
建议:您最好使用`-s`或`-u `对标记加密签名
为了保存在release分支中做的修改,我们需要将它合并回develop分支,因此,在Git中:
$ git checkout develop
// 切换到develop分支
$ git merge --no-ff release-1.2
// 合并分支
这一步骤可能会导致合并冲突(我们甚至可能会因此更改版本号)。如果这样的话,修复后再次进行提交。
现在,我们真的完成了release分支,并且可以移除掉它了,因为我们再也不需要它了。
$ git branch -d release-1.2
// 删除release-1.2分支
hotfix分支
可能产生于
master
必须合并到
develop和master
分支命名规范
hotfix-*
hotfix分支非常类似于release分支,因为它也是为了生产版本做准备,尽管是hotfix分支在计划之外。它产生的原因是生产版本存在问题,需要立即进行修复。当在生产版本中发现了一个严重bug时,可以将hotfix从master分支上被标记过的相应地方分离出来。
本质上讲,团队成员的工作(在develop分支)仍可以继续进行,由另一个人来准备快速的修复版本。
创建hotfix分支
hotfix分支从master分支上创建。例如,版本1.2是当前发布的生产版本,但是由于存在bug而不稳定。但是develop分支还在修改也不稳定。我们需要分离出一个hotfix分支,并修复这个问题:
$ git checkout -b hotfix-1.2.1 master
// 创建hotfix-1.2.1并切换到该分支
$ ./bump-version.sh 1.2.1
// 执行shell搅拌 文件修改成功 修改版本号
$ git commit -a -m "Bumped version number to 1.2.1"
// 提交分支
不要忘记在分支结束后增加版本号!
然后在一个或多个提交中修复bug。
$ git commit -m "Fixed severe production problem"
完成hotfix分支
完成后,需要将bug修复合回到master分支,但是我们也需要合并到develop分支,以确保bug修复包含在下一个发行版本中。这和释放release分支做法类似。
$ git checkout master
// 切换到master分支
$ git merge --no-ff hotfix-1.2.1
// 合并分支
$ git tag -a 1.2.1
// 打标签
建议:您最好使用`-s`或`-u `对标记加密签名
接下来,将bug修复合并到develop分支
$ git checkout develop
// 切换到develop分支
$ git merge --no-ff hotfix-1.2.1
// 合并分支
这里有一个例外,当release分支还存在,hotfix分支需要到release分支,而不是develop分支 。当release分支完成后,bug修复合并到release分支会让bug修复也包含到develop分支中(如果develop分钟需要立即修复这个bug,并且不能等待release分支完成,你也可以安全地将bug修复合并到develop中)。
最后移除掉这个临时的分支:
$ git branch -d hotfix-1.2.1
// 删除分支
总结
虽然这个分支模式并没有真正令人惊讶的新事物,但这个帖子开头的“大图片”在我们的项目中是非常有用的。它形成了一个优雅的心理模型,易于团队成员理解分支和发布过程。
这里提供了一个高质量的PDF版本的数字.你可以随时把它挂在墙上,以供快速参考。
Git-branching-model.pdf