Git Branching Model - tenji/ks GitHub Wiki
GIT分支管理模型
在这篇文章中,我介绍了大约一年前我为我的一些项目(在工作中和在私人项目中)引入的开发模型,事实证明该模型非常成功。 我一直想写点关于这个模型的东西,但是直到现在,我还没有真正找到时间进行彻底的写作。我不会谈论任何项目的详细信息,而只会谈论分支策略和发布管理。
一、为什么选择 Git
二、分散但集中
我们正在使用并且适合这个分支模型的仓库有一个“真实”的中央仓库。请注意,此仓库仅被视为中央仓库(由于 Git 是DVCS
,因此在技术层面上没有中央仓库的概念)。我们将此仓库称为origin
,因为所有 Git 用户都熟悉该名称。
每个开发人员都拉取并推送到origin
仓库。但是,除了集中的推拉关系之外,每个开发人员还可以从其他同伴那里拉取变更以组成子团队。例如,在将进行中的工作过早地推送到origin
仓库之前,与两个或多个开发人员一起使用一项重要的新功能可能会很有用。在上图中,有 Alice 和 Bob,Alice和 David 以及 Clair 和 David 的子团队。
从技术上讲,这仅意味着 Alice 定义了一个名为bob
的 Git 远程仓库,该遥控器指向 Bob 的仓库,反之亦然。
三、主要分支(The main branches)
从根本上说,开发模型的灵感来自于现有模型。中央存储库包含两个主要永久分支:
- master
- develop
每个 Git 用户都应该熟悉origin
的master
分支。 与master
分支平行,存在另一个分支称为develop
。
我们认为origin/master
的HEAD
源码是始终反映生产环境当前代码的主要分支。
我们认为origin/develop
的HEAD
源码是始终反映具有下一个版本的最新交付开发更改代码的主要分支。有人会称之为“集成分支”,每日自动化构建任务均应该从该处构建。
当develop
分支中的源码达到稳定版本并准备发布时,所有更改都应以某种方式合并回master
分支,然后用发布版本号打标签,具体怎么操作下面会谈到。
因此,每次将更改合并回master
分支时,根据定义,这是一个新的生产版本。我们通常对此非常严格,因此从理论上讲,每当有新的提交合入master
分支时,我们就可以使用 Git Hook Script 自动将代码构建和更新到生产服务器。
四、支撑分支(Supporting branches)
除了主要的master
和develop
分支之外,我们的开发模型还使用各种支撑分支(Supporting branches)来帮助团队成员之间进行并行开发,简化功能跟踪,为生产发布做准备并协助快速解决生产中的实际问题。 与主要分支(The main branches)不同,这些分支的生命周期总是有限的,因为它们最终将被删除。
我们可能使用的不同类型的分支是:
- Feature branches
- Release branches
- Hotfix branches
这些分支中的每一个都有特定的目的,并受严格的规则约束,即哪些分支可能是其原始分支,最终必须合入哪些目标分支。我们将在一分钟内逐步解决它们。
从技术的角度来看,这些分支绝不是“特殊的”。分支类型按我们的使用方式进行分类。它们当然是普通的旧 Git 分支。
4.1 Feature branches
从以下分支拉取:
develop
合入以下分支
develop
分支命名规则
除了
master
,develop
,release-*
,hotfix-*
之外都可以
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 来执行。这样可以避免丢失有关要素分支历史存在的信息,并将所有添加了要素的提交分组在一起。 比较:
在后一种情况下,无法从 Git 历史记录中看到哪些提交对象一起实现了某个特性,这样的话你将不得不手动读取所有日志消息。在后一种情况下,回滚整个功能(即一组提交)也很头疼,而如果使用--no-ff
标志则很容易做到。
当然,这会创建更多(空)提交对象,但收益远大于成本。
4.2 Release branches
从以下分支拉取:
develop
合入以下分支
develop 和 master
分支命名规则
release-*
release
分支为新产品版本做准备,允许在新版本发布的最后时刻做修修补补。此外,它们还允许进行较小的错误修复并为发布新版本准备元数据(版本号,构建日期等)。通过在release
分支上完成所有这些工作,develop
分支可以被解放出来进行下一个版本的特性开发。
从develop
分支拉取release
分支的关键时刻是develop
(几乎)何时反映新版本的所需状态。此时至少要构建的发行版的所有功能必须合入到develop
分支。面向将来发行版的所有特性可能不需要合入develop
分支—它们必须等到release
分支出来之后。
比较适合为即将发布的发行版本分配版本号的时间是刚刚拉取release
分支的时候。直到那一刻,develop
分支反映了“下一个发布版本”的更改,但是直到relese
分支开始运行之前,“下一个发布版本”最终是0.3
或1.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
分支与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...