一个成功的Git分支开发模型

来源:互联网 发布:mac os 慢 编辑:程序博客网 时间:2024/05/17 01:50

在这篇文章里,我将介绍被运用在我大概一年前的很多项目(有公司的有私人的)里的一种开发模型,实践证明这是一种非常成功的开发模型。我一直很想介绍它,但是之前一直没有找到合适的时间来完完整整地做这件事,直到现在。我不会讨论任何项目的细节,仅仅讲述分支策略和发布管理。



本文重点关注Git作为源代码版本管理工具的作用。(顺带提一下,如果你对Git感兴趣,我们的公司GitPrime提供一些非常出色的软件工程方面的实时数据分析。)

>>为什么是git

有关Git和一些集中式源码管理系统的完整的优缺点比较的文章,你可以在网上找到很多。在那里充满着热烈的争论。作为一个开发者,现在我更愿意选择使用Git。Git实实在在地改变了开发者对于合并代码(merging)和处理分支(branching)的概念。我从古典的CVS/Subversion世界走来,合并代码/处理分支一直被认为是一件有点恐怖的事情(“注意合并代码时的冲突,它们会咬你!”)并且你只能在每过一段时间内去做(原文:something you only do every once in a while)。

但是有了Git,这些动作就会变得非常简单,而且它们会成为你日常工作的核心部分。比如说,在CVS/Subversion书籍里,分支处理会在较为靠后的章节里才出现(这部分内容主要针对高级用户),而在每一本介绍Git的书籍里,一般在前三章里就会有涉及(这是基础,针对所有用户)。

由于其简易性和高重复性,分支处理不再令人畏惧。版本控制工具被认为比其他任何工具都有助于分支处理。

谈论了很多关于工具的内容,让我们走进开发模型的部分。我即将在这里展示的模型是每一个开发团队为了管理好软件开发流程都应该遵循的一些列流程秩序。

>>分散的(Decentralized)但是集中的(Centralized

我们使用的并且目前在这套分支模型里运行完好的仓库设置是有一个集中的“真实”仓库(repo)。需要指出的是,这个仓库只是仅仅被认为是中央仓库(因为Git是一个分布式版本控制系统(DVCS),在技术层面上讲并没有中央仓库的概念)。我们把这个仓库称为origin,所有的Git用户都对这个名称很熟悉。



所有的开发者都从origin拉取代码(pull)或者上传代码到origin(push)。但是除了向中央仓库进行的push-pull操作,每个开发者都有可能从其他同事那里拉取变化的代码,形成子团队。比如说,这种情况可能发生在两个或者更多的开发者需要协作开发一个较大的新的特性的情况下,避免将个人的不成熟的代码直接推向origin。在上面的图中,Alice和Bob,Alice和David,Clair和David分别形成了三个子团队。

从技术上将,这无非意味着Alice定义了一个Git远程仓库,命名为Bob,并且指向了Bob的仓库,其余一样。

>>主要分支(Main Branch

核心层面上,开发模型很大程度上是从现有模型启发而得到的。中央仓库有两个主要分支,这两个分支具有无限的生命周期,也就是存在于我们开发过程的始终:

1.    master

2.    develop(dev)



每一个Git用户都应该熟悉master分支。与master分支平行存在的是develop分支,更多的命名为dev分支。

我们认为origin/master分支是一个主要分支,在这个分支上,源代码的HEAD指针总是指向一个可以被发布的版本状态。

我们认为origin/dev分支是一个源代码的HEAD指针总是指向那些具有为下一个发布版本而开发的最新功能的状态的分支。有些人会称之为“集成分支”(integration branch)。这是那些自动构建的来源。

当在dev分支上的代码来到一个稳定的状态并且准备发布的时候,所有的改变都应该被合并到master分支上,并且打上发布版本号的标签(tag)。后面将会详细讲述这个过程是如何进行的。

因此,任何时候当变化被合并到master分支上时,就应该有一个新的版本被发布。我们应该很严格地执行这一步操作,因此在理论上,我们可以使用一个和Git关联的脚本命令,每当master分支有一个提交(commit)时,可以自动执行构建,将我们的软件推向生产服务器。

>>辅助分支(Supporting Branch

在master分支和dev分支旁边,我们的开发模型使用了一系列辅助分支来帮助成员间实现平行开发,轻松地追踪特性,准备版本发布和快速解决实况问题。和主要分支不一样,这些辅助分支总是具有有限的生命周期,因为它们最终都要被移除。

我们使用的不同的辅助分支有:

1.    feature分支

2.    release分支

3.    hotfix分支

每一个辅助分支都有特定的目的,而且对于这些分支从哪些分支产生和最终必须要合并到哪些分支都有严格的规定。我们将立刻介绍这些。

从技术角度来说,这些分支决不是“特殊的”。我们仅仅通过我们如何使用它们来对它们进行分类。它们当然都是简简单单的git分支。

feature分支

可能从哪里分叉:dev

最终必须合并到:dev

分支命名传统:除了master、dev、release-*或者hotfix-*之外的任何名字



feature分支(或者有时候被称为话题分支(topicbranch))被用来为即将到来的或者较为遥远的将来的发布开发新特性。当我们开始开发一个新特性时,我们可能还不知道这个特性将要被整合到哪一个目标发布版本。feature分支的本质在于只要这个特性在开发,这个分支就会一直存在,但是最终它会被合并到dev分支上(以此来保证这个新特性一定会被加到即将发布的版本里)或者被放弃(可能这个新特性只是一次令人失望的实验)。

feature分支一般只存在于开发者的仓库里,而不是在origin。

创建一个feature分支

当开始要开发一个新特性时,从dev分支上分叉:

$ git checkout -b myfeature devSwitched to a new branch “myfeature”

将完成的特性整合到dev

开发完成的特性可能会被合并到dev分支上来保证将它们加到即将到来的发布版本中

$ git checkout devSwitched to branch ‘dev’$ git merge --no-ff myfeatureUpdating ea1b82a..05e9557(Summary of changes)$ git branch -d myfeatureDeleted branch myfeature (was 05e9557).$ git push origin dev


--no-ff参数用来让这次合并总是产生一个新的提交,即使合并有可能使用fast- forward模式。这会防止丢失特性分支的历史信息的丢失并且能够把把所有提交都整合到一起。下图是两者的比较:



在后一种情况下,我们无法从git的历史信息中得知哪些提交引入了特性—你可能需要人工地去查阅所有的log日志。重新回溯整个特性(或者一组提交),在后一种情况下确实是一个令人头疼的问题,然而如果我们使用--no-ff参数,那么这些操作就会很简单。

是的,这样可能会创建相对更多的(空的)提交,但是这样带来的收益远远大于成本。

Release分支

可能从哪里分叉:dev

必须合并到哪里:dev和master

命名传统:release-*

Release分支用来为一个新的生产发布版本做准备。它们用来做最后的细节工作(原文很有意思:last-minute dotting of i’s and crossing of t’s)。此外,它们为微小bug的修复和为发布准备元数据(版本号、构建日期等)留有余地。通过在release分支上完成这些工作,dev分支保持干净,用来为下一次大的发布接收新的特性。

从dev分支分叉出一个新的release分支的关键时刻是当dev分支呈现出(或者近乎呈现)一个新发布版本的理想状态时。在这时候至少所有新发布版本需要的特性都已经被合并到dev分支上了。而为未来发布版本准备的特性可能不会被合并—它们必须要等到该版本对应的release分支被分叉出来。

正式开始开发release 分支的时候,即将到来的发布版本会被分配一个版本号—不会更早。直到那个时刻,dev分支展现出了为了下一个发布而有的变化,但是并不清楚这个“下一个发布”最终是0.3版本还是1.0版本,直到开始开发release分支。决定版本号是在release分支的开始并且会随着版本的更新贯穿项目的始终。

创建一个release分支

Release分支是从dev分支上分叉而来的。举例来说,版本1.1.5是目前正在生产的版本并且我们即将会有一个大版本到来。Dev分支的状态是已经为这个大版本做好准备了,所以我们决定这个版本号是1.2(而不是1.1.6或者2.0)。所以我们从dev分支分叉出来并且给它命名为1.2,反映了新的版本号。

$ git checkout –b release-1.2 devSwitched to a new branch “release-1.2”$ ./bump-version.sh 1.2First modified successfully, version bumpedto 1.2$ git commit –a –m “Bumped version numberto 1.2”[release-1.2 74d9424] Bumped version numberto 1.21 file changed, 1 insertions(+), 1deletions(-)


在创建新的分支并且切换到这个分支之后,我们改变了版本号。这里,bump-version.sh是一个虚构的shell脚本,用来修改在工作副本中的一些文件,反映这个新版本。(当然也可以人为修改。)然后,我们提交这个修改后的版本号。

这个新的分支可能会存在一段时间,直到这个分支可能确定被推出。在那段时间,可能会在这个分支上进行bug修复(而不是在dev分支上)。我们严禁在这个分支上加入大的新特性。它们必须被合并到dev分支上,然后等待下一个大的发布。

完结一个release分支

当一个release分支呈现出一个真实的可以被发布的状态时,我们就需要实施一些行动了。首先,这个release分支被合并到master分支上(因为master分支上的每一个commit都应该是一个新的发布,记住!)。然后,我们必须为这次提交打上标签,为了给以后的版本做参考。最后,在release分支上做出的改变需要被合并到dev分支上,以便于以后的发布都能包含这些bug修复。

Git上开始的两步:

$ git checkout masterSwitched to branch ‘master’$ git merge --no-ff release-1.2Merge made by recursive(Summary of changes)$ git tag -a 1.2


现在发布已经完成了,也打上了标签为未来的发布作参考。

Edit:你也可以使用-s或者-u<key>参数来打上密码标签

为了保存在release分支上做出的改变,我们需要将它合并到dev分支上:

$ git checkout devSwitched to branch ‘dev’$ git merge --no-ff release-1.2Merge made by recursive.(Summary of changes)

这一步可能会导致合并冲突(或许,因为我们改变了版本号)。如果是这样,解决冲突然后提交。

现在我们已经完成了工作,这个release分支可以被移除了,因为我们不再需要了

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

Hotfix分支

可能从哪里分叉:master

必须合并到哪里:dev或者master

命名传统:hotfix-*



在为新生产发布版本准备的角度上,Hotfix分支和release分支非常相似,尽管hotfix分支的出现是没有计划好的。它们产生的必要性在于需要对一个正在生产的版本的不理想的状态进行快速的处理。当一个重要的bug需要被快速解决的时候,我们可以从打上对应标签的master分支上分叉出来一个hotfix分支。

这个行为的本质在于当一些成员在进行bug修复的时候,团队的其他成员基于dev分支的工作可以继续。

创建hotfix分支

Hotfix分支可以从master分支创建。比如说,版本1.2版本是目前正在生产的发布版本,由于一个严重的bug导致了一些问题。但是在dev分支上的变化还不是很稳定。我们可以从master分支上分叉出hotfix分支来修复这个问题:

$ git checkout -b hotfix-1.2.1 masterSwitched to a new branch “hotfix-1.2.1”$ ./bump-version.sh 1.2.1Files modified successfully, version bumpedto 1.2.1$ git commit -a -m “Bumped version numberto 1.2.1”[hotfix-1.2.1 41e61bb] Bumped versionnumber to 1.2.11 files changed, 1 insertions(+), 1deletions(-)


不要忘记在分叉之后修改版本号!

然后我们就可以修复该bug并且一次性的或者多次提交修复。

$ git commit –m “Fixed severe productionproblem”[hotfix-1.2.1 abbe5d6] Fixed severe productionproblem5 files changed, 32 insertions(+), 17deletions(-)

完结一个hotfix分支

当我们完成修复后,这个bug修复需要被合并到master分支,但也需要被合并到dev分支,因为我们需要让接下来的发布版本也包含这个bug修复。所以这个完结过程和release分支是非常相似的。

首先,更新master分支并且为发布打上标签。

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

Edit:你也可以使用-s或者-u<key>参数来打上密码标签

接下来,把bug修复合并到dev分支:

$ git checkout devSwitched to branch ‘dev’$ git merge --no-ff hotfix-1.2.1Merge made by recursive.(Summary of changes)

在这边有一个例外是,当有一个release分支同时存在时,这个hotfix分支必须被合并到release分支而不是dev分支。将bug修复回溯合并到release分支最终也会使其被合并到dev分支,因为最终release分支会被合并到dev分支。(如果在dev分支上的工作立即需要这个bug修复,不能等到release分支被合并过来,也当然也可以使用安全的方式将这个bugfix合并到dev分支。)

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

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

>>总结

虽然没有什么十分新鲜的东西,但是在这篇博客开始呈现的开发模型在我们的项目中极其有效。它提供了一个让开发者十分容易理解的抽象模型,让团队成员共同开发一个共享的分支发布过程。

如果想要联系我,请在Twitter上@nive。

原文地址:http://nvie.com/posts/a-successful-git-branching-model/

本人翻译水平有限,如有错误,敬请指正,谢谢!


0 0
原创粉丝点击