Git Branching Model - tenji/ks GitHub Wiki

GIT分支管理模型

在这篇文章中,我介绍了大约一年前我为我的一些项目(在工作中和在私人项目中)引入的开发模型,事实证明该模型非常成功。 我一直想写点关于这个模型的东西,但是直到现在,我还没有真正找到时间进行彻底的写作。我不会谈论任何项目的详细信息,而只会谈论分支策略和发布管理。

Git Model

一、为什么选择 Git

二、分散但集中

我们正在使用并且适合这个分支模型的仓库有一个“真实”的中央仓库。请注意,此仓库仅被视为中央仓库(由于 Git 是DVCS,因此在技术层面上没有中央仓库的概念)。我们将此仓库称为origin,因为所有 Git 用户都熟悉该名称。

每个开发人员都拉取并推送到origin仓库。但是,除了集中的推拉关系之外,每个开发人员还可以从其他同伴那里拉取变更以组成子团队。例如,在将进行中的工作过早地推送到origin仓库之前,与两个或多个开发人员一起使用一项重要的新功能可能会很有用。在上图中,有 Alice 和 Bob,Alice和 David 以及 Clair 和 David 的子团队。

从技术上讲,这仅意味着 Alice 定义了一个名为bob的 Git 远程仓库,该遥控器指向 Bob 的仓库,反之亦然。

三、主要分支(The main branches)

从根本上说,开发模型的灵感来自于现有模型。中央存储库包含两个主要永久分支:

  • master
  • develop

每个 Git 用户都应该熟悉originmaster分支。 与master分支平行,存在另一个分支称为develop

我们认为origin/masterHEAD源码是始终反映生产环境当前代码的主要分支。

我们认为origin/developHEAD源码是始终反映具有下一个版本的最新交付开发更改代码的主要分支。有人会称之为“集成分支”,每日自动化构建任务均应该从该处构建。

develop分支中的源码达到稳定版本并准备发布时,所有更改都应以某种方式合并回master分支,然后用发布版本号打标签,具体怎么操作下面会谈到。

因此,每次将更改合并回master分支时,根据定义,这是一个新的生产版本。我们通常对此非常严格,因此从理论上讲,每当有新的提交合入master分支时,我们就可以使用 Git Hook Script 自动将代码构建和更新到生产服务器。

四、支撑分支(Supporting branches)

除了主要的masterdevelop分支之外,我们的开发模型还使用各种支撑分支(Supporting branches)来帮助团队成员之间进行并行开发,简化功能跟踪,为生产发布做准备并协助快速解决生产中的实际问题。 与主要分支(The main branches)不同,这些分支的生命周期总是有限的,因为它们最终将被删除。

我们可能使用的不同类型的分支是:

  • Feature branches
  • Release branches
  • Hotfix branches

这些分支中的每一个都有特定的目的,并受严格的规则约束,即哪些分支可能是其原始分支,最终必须合入哪些目标分支。我们将在一分钟内逐步解决它们。

从技术的角度来看,这些分支绝不是“特殊的”。分支类型按我们的使用方式进行分类。它们当然是普通的旧 Git 分支。

4.1 Feature branches

从以下分支拉取:

develop

合入以下分支

develop

分支命名规则

除了master, develop, release-*, hotfix-*之外都可以

feature branch

4.1.1 创建新的 feature branch

在开始开发新的特性时,从develop分支生成特性分支。

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

4.1.2 将已完成的 feature branch 合入 develop branch

可以将完成的特性合入到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 来执行。这样可以避免丢失有关要素分支历史存在的信息,并将所有添加了要素的提交分组在一起。 比较:

merge without ff

在后一种情况下,无法从 Git 历史记录中看到哪些提交对象一起实现了某个特性,这样的话你将不得不手动读取所有日志消息。在后一种情况下,回滚整个功能(即一组提交)也很头疼,而如果使用--no-ff标志则很容易做到。

当然,这会创建更多(空)提交对象,但收益远大于成本。

4.2 Release branches

从以下分支拉取:

develop

合入以下分支

develop 和 master

分支命名规则

release-*

release分支为新产品版本做准备,允许在新版本发布的最后时刻做修修补补。此外,它们还允许进行较小的错误修复并为发布新版本准备元数据(版本号,构建日期等)。通过在release分支上完成所有这些工作,develop分支可以被解放出来进行下一个版本的特性开发。

develop分支拉取release分支的关键时刻是develop(几乎)何时反映新版本的所需状态。此时至少要构建的发行版的所有功能必须合入到develop分支。面向将来发行版的所有特性可能不需要合入develop分支—它们必须等到release分支出来之后。

比较适合为即将发布的发行版本分配版本号的时间是刚刚拉取release分支的时候。直到那一刻,develop分支反映了“下一个发布版本”的更改,但是直到relese分支开始运行之前,“下一个发布版本”最终是0.31.0还不清楚。该决定是在release分支的开始时做出的,并且由项目版本号的命名规则来执行。

4.2.1 创建新的 release branch

release分支是从develop分支创建的。例如,1.1.5 版本是当前的生产环境版本,我们即将发布一个大版本。develop分支状态已准备就绪,可用于“下一个发行版”,我们已经决定将版本号定为 1.2 (而不是 1.1.6 或 2.0)。 因此,我们拉取一个新的分支并给release分支起一个反映新版本号的名称:

$ 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脚本,它会更改工作副本中的某些文件以反映新版本。(当然,这可以是手动更改,关键是有些文件会更改)然后,提交被修改的版本号。

这个新分支可能会存在一段时间,直到确定可以发布该版本为止。在此期间,BUG 修复可以合入此分支(而不是develop分支)。严格禁止在此分支合入大的特性代码。大的特性代码必须合入到develop分支中,并在下一个发行版本中发布。

4.2.2 完成 release branch

release分支可以正式发布时,需要执行一些操作。首先,将release分支合并到master中(因为根据我们的约定,master上的每个提交都对应一个发行版本)。接下来,代码合入到master分支时必须打上版本标签,以便将来可以轻松查看历史发布版本。最后,需要将对release分支所做的更改重新合并到develop分支中,以便将来的发行版也包含我们在release分支上提交的 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
The release is now done, and tagged for future reference.

为了保留在release分支中所做的更改,我们需要将这些更改重新合并到develop分支中。 在 Git 中:

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

此步骤很可能导致合并冲突(可能必然发生,因为我们已经更改了版本号)。如果发生冲突了,请解决重提并提交。

现在我们已经发布完版本了,就可以删除掉release分支,因为我们不再需要它了:

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

4.3 Hotfix branches

从以下分支拉取:

master

合入以下分支

develop 和 master

分支命名规则

hotfix-*

hotfix branch

hotfix分支与release分支非常相似,尽管它们是计划外的,但它们的目的也是为了发布新的生产版本。它们源于必须对不期望的实时生产版本立即采取行动。当必须立即解决生产版本中的严重错误时,可以从标记生产版本的master分支上的相应标记中拉出一个hotfix分支。

拉取hotfix分支的本质是团队成员的工作(在develop分支)可以继续,而另一个人正在准备快速的修复生产环境的问题。

4.3.1 创建 hotfix branch

hotifx分支是从master分支创建的。例如,当前生产环境正在运行的版本是1.2,但是运行过程中出现了严重的错误。同时develop分支的还不稳定。因此,我们可能会拉取出一个hotfix分支并开始解决问题:

$ 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 并在一个或多个单独的 commits 中提交。

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

4.3.2 完成 hotfix branch

完成后,该 bugfix 需要合并回master,也需要合并回develop分支,以确保该 bugfix 也包含在下一发行版中。这与release分支的完成方式完全相似。

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

接下来,也将 bugfix 合入develop分支中:

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

该规则的一个例外是,如果当前存在release分支,需要将hotfix更改合并到该release分支而不是develop分支。因为在release分支完成后,release分支的修改最终也会合并到develop分支中。(如果develop分支也需要立刻修复该错误,而不能等待release分支完成,则现在也可以安全地将hotfix的修改合并到develop分支中。)

最后,删除临时分支:

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

五、总结

Waiting for Editing...

原文链接

A successful Git branching model