一个成功的 Git 分支模型(适用于商业应用开发)

在这篇文章中,我将推广一下大约一年前我介绍过的一些项目(公私皆有)中使用的开发模型,它们的结果都很是成功。有段时间我很是想写出来分享一下,可是我至今才抽出时间来。我不会言及任何项目细节,仅讨论分支策略和发布管理。laravel

file

为什么使用 git? 

关于 Git 和集中式源码版本控制系统的优缺点对比讨论,  此 web。这里有不少精彩激烈的论战。做为一名开发者,如今我更偏好使用 Git 。Git 真的改变了开发者关于合并和分支的认知。我来自传统的 CVS/Subversion 世界,合并/分支是件恐怖的事情 (「当心合并冲突,它们会反噬你!」),并且你们偶尔才会作这件事。git

可是使用 Git,这些操做就显得垂手可得,它们会成为你 平常 工做流的核心部分。例如,在 CVS/Subversion 手册中,分支和合并直到最后章节才首次出现(仅供高级用户参考),可是在 每一个 Git 手册中,第三章就覆盖到了(基本上)。github

由于具备简单和可重复的基因,分支和合并再也不是什么值得担忧的问题了。版本控制工具应该专一于分支/合并,而非其余事情。web

关于工具的了解已经够了,那么咱们就开始进入开发模型了。这个我要介绍的开发模型,不过是每一个团队成员进入软件开发流程以前必须遵循的规范。安全

去中心化和中心化 

基于一个「真正」中心库的这种分支模型可让咱们很好的协同工做。注意这个库仅仅是被认为一个中心库(由于 GIT 在技术角度并无中心库这一说法)。咱们将此仓库命名为 origin,由于这名字被 Git 用户熟知。服务器

!并发

全部开发者都从 origin 库拉取或向其推送代码。在中心化的推送-拉取关系以外,开发者也能够从其余的开发者库拉取代码更改。例如,为了开发一个大型功能,有多位开发者组成一个开发小组,在功能完成以前,开发过程没必要推送至 origin 库。在上面的图中就有 Alice 和 Bob, Alice 和 David,还有 Clair 和 David 之间组成的小组。svn

技术层面,Alice 要在本地库加一个远程 Git 分支并命名为 bob,指向 Bob 的仓库。其余人也同样。工具

主分支 

file

git 的核心在开发模式上受到了现有模式的极大启发,中心仓库在整个生命周期保持了两个主要的分支:ui

  • master
  • develop

每一个 Git 用户都对在 origin 的  master 分支很熟悉。 跟 master 分支并行的是另外一个称为 develop 的分支。

 咱们称 origin/master 为主分支,这个分支源码的 HEAD 一直指向 可用于生产环境 的状态。

咱们称 origin/develop 为主分支,这个分支源码的 HEAD 老是反映下一个版本的最新开发情况。有些人称这个分支为 "整合分支" 。全部的每日自动构建都是从这儿构建的。

当 develop 分支上的源代码达到一个稳定点并准备发布时, 全部的更改都应该以某种方式合并回 master 分支, 而后使用发行版本进行标注。 接下来将从细节上讨论这是如何完成的。

所以, 每次将变动合并回 master分支时, 这是一个 根据定义 的新产品发布。 咱们趋向于对此很是严格,所以理论上来说, 咱们能够在每次提交到 master 分支时, 使用一个 Git 钩子脚原本自动完成构建和发行咱们的软件到生产服务器。

辅助分支 

在讨论完 master 分支和 develop 分支后,将要讨论的多样化的辅助分支,支持成员间并行开发, 轻松跟踪功能开发、生产版本发布、还能快速修复生产环境中产生的 Bug 。和主分支不一样的是,辅助分支只有有限的生命周期,一般在完成使命后会被删除。

可能用到的辅助分支分类有:

  • 功能分支
  • 发布分支
  • 修复 Bug 分支

每一个分支都有特殊的用途。这些分支的来源分支和他们要合并回的分支都有严格的定义。在后面咱们再具体讨论。

技术上,辅助分支都没有特殊含义。咱们就是根据具体使用功能对辅助分支进行分类。辅助分支和普通的 Git 分支没有区别。

功能分支 

!

功能分支可能源自于:

develop 分支

功能分支必须合并回:

develop 分支

功能分支命名惯例:

任何名字均可以,但不能包含 master, develop, release-*, 或者 hotfix-*

功能分支(或称为特性分支)是被用来开发新功能的,这些新功能是要即将上线或更长时间后发布的。功能分支建立后开始开发时,以后将要合并的时间点是不知道的。功能分支的精髓是伴随开发过程一直存在,可是确定会被合并回 develop (在下一个预期的发布版本中清晰的添加新功能 ) 或被丢弃 (万一实验不尽如人意)。

功能分支一般存在开发人员的仓库中,不会出如今 origin 仓库。

建立一个功能分支 

当咱们开始写一个新的功能时,请从 develop 分支中切换出来

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

在开发中加入已经完成的功能 

完成的功能可能被合并进入 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-forwark方法进行提交。这就能够避免丢失功能分支的历史信息而且能够把全部的功能在叠加在一块儿提交上去,请看图片对比:

!

在后一种状况中,从 Git 历史中是没法查看到是哪几个提交对象在一块儿实现了一个功能 -——您必须手动读取全部日志消息。还原整个功能(即一组完整的提交)在后一种状况下是真的让人很头疼的事情,可是若是使用使用 --no-ff 标志,就能够很容易完成这个任务的。

固然啦,它虽然创做更多的空的提交对象,可是增益却远远大于成本。

发布分支 

该分支可能源自于:

develop 分支

必须合并到:

develop 和 master 分支

分支命名习惯:

release-*

发布分支(Release branches) 支持新产品发布的准备。 它们容许在最后一刻追求细节。此外,它们容许小错误修复以及为发布准备元数据(版本号,构建日期等)。经过在发布分支上作的这些工做, develop 分支被清除以接收下一个大版本的功能特性。

从 develop 分支检出一个新发布分支的重要时刻就是当开发(基本上)反映了新版预期状态的时候。 至少,在那时,全部以『即将构建的发布版』( release-to-be-built )为目标的功能特性必须合并回 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 是一个虚构的脚本文件,用以改变工做副本中的一些文件来反映新版本。(固然也能够手动啦,重点是 那些 改变的文件)而后,碰撞后的版本号就被提交了。

直到肯定会推出发布版的这段时间里,新分支都会一直存在。在此期间,bug 修复可能会应用到这个分支上(而不是 develop 分支)。 严禁在此分支添加大的新功能特性。 这些分支必须合并回 develop 分支,而后,等待下一个大版本的到来。

完成并发布你的分支 

当你真的准备好要发布分支的时候,还须要执行一些别的操做。首先,发行版必须合并进 master 分支中(必定确保每次提交到 master 分支的都是最新的版本)。接下来,请必定标记对 master 分支的更新记录,用于之后查看该版本时进行参考。最后, 发布新分支所作的更改须要从新合并为 develop 分支,以确保之后的版本也修复了这些错误。

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-*

热修复(hotfix)分支和发布(release)分支很像,由于它们都意味着即将有新的生产版本发布,尽管不是意料之中的。它们产生的缘由是现有的生产版本出现了不受欢迎的状况,从而必须当即起做用。当生产版本中的一个严重的 bug 必须立刻被修复,热修复分支可能从 master 分支上用于标记生产版本的对应标签上建立分支。

其本质是当有人准备对生产版本进行快速修复时,团队成员(在 develop 分支)能够继续工做。

建立修复 bug 分支 

修复 bug 分支建立于 master 分支。 例如,1.2版本是当前生产环境的版本而且有 bug 。可是 develop 分支上的修改还不够稳定。这时咱们能够建立一个修复 bug 分支来解决这个问题:

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

完成一个修复 bug 分支

当完成一个修复 bug 分支以后,bug 分支须要合并到 masterdevelop 分支上,以保证在下一版本中也包含该 bug 修复。 这与完成发布分支彻底类似。

首先,更新 master 并对此次发布打上 tag 。

$ 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 分支里包含 bug 修复分支的改动:

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

对于上面的规则,有一点是例外的: 当发布分支已经存在时, bug 修复分支的改动应该合并到该发布分支,而不是 develop 分支。当发布分支完成的时候, 把 bug 修复分支反向合并到发布分支中,最终也会致使 bug 修复被合并到 develop 分支中去。(若是 develop 分支中的工做立刻就要这个 bug 修复的改动而且不能等待发布分支完成,那么如今你也能够安全地将 bug 修复合并到 develop 分支中去。)
最后,移除临时分支:

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

小结 

虽然这种分支模式目前看来已经不是什么新鲜事了,但这篇文章开头的 「大图」已经证实,这种模式对咱们的项目实实在在的是很是有用。它造成了一个优雅而且更加容易理解的模型,而且能增强团队中成员们对于分支和其释放过程的理解。

这里提供给你们上面大图更加清晰的 PDF 版本,建议把它打印出来挂在墙上膜拜而且以便开发过程当中快速查看。

更新: 若是有人须要这张图片: 这里附上下载文件 gitflow-model.src.key 

Git 分支模型.pdf 下载

更多现代化 PHP 知识,请前往 Laravel / PHP 知识社区

相关文章
相关标签/搜索