一个成功的Git
分支模型
A successful Git branching model » nvie.com
反思说明(2020年3月5日)
这个模型是在2010年构思的,现在已经超过10年了,而且是在Git本身诞生后不久。在这10年里,git-flow(本文所阐述的分支模型)在许多软件团队中大受欢迎,以至于人们开始把它当作某种标准--但不幸的是,也被当作教条或万能药。
在这10年中,Git本身已经在世界范围内掀起了一场风暴,而使用Git开发的最流行的软件类型正在更多地转向网络应用--至少在我的信息茧房中是这样。网络应用通常是持续交付的,不会回滚,而且你不必支持多个版本的软件。
这不是我在10年前写这篇博文时想到的那类软件。如果你的团队正在做软件的持续交付,我建议采用更简单的工作流程(比如GitHub flow),而不是试图把git-flow塞进你的团队。
然而,如果你正在构建的软件是明确的版本,或者你需要在外支持多个版本的软件,那么git-flow可能仍然像过去10年里对人们一样适合你的团队。在这种情况下,请继续阅读。
最后,请永远记住,万能药是不存在的。考虑到你自己的背景。不要怨恨。为自己做决定。
分布式的,但是又中心化的
在我们的分支模型中,有一个权威的中心仓库(称为origin
)。因为Git是分布式的,没有真正的所谓“中心仓库”。这个只是我们的叫法而已。
每个开发者拉取、推送到origin
。除此之外,每个开发者也可以从其他同行那里拉取变化,形成子团队。几人合作开发一个大特性,在未成熟之前先不推送到origin
的场景会用到。上图中的子团队如:Alice和Bob, Alice和David, Clair 和David。
从技术上讲,这无非意味着Alice定义了一个Git远程仓库,名为bob,指向Bob的仓库,反之亦然。
主要分支
主要分支为如下有无限生命周期的
master
develop
我们认为origin/master
是主要分支,HEAD
的源代码总是反映生产就绪的状态。
我们认为origin/develop
也是主要分支,HEAD
的源代码总是反映了下一个版本的最新开发状态。有些人把它称为 集成分支
。这是(夜间?)自动构建的分支。
当develop
分支中的源代码稳定并准备发布时,所有的修改都应该以某种方式合并回master
分支,然后标记release版本。具体怎么做,我们会进一步讨论。
因此,每次当变化被合并到maser
时,根据定义,这就是一个新的生产版本。我们在这方面的要求非常严格,所以理论上我们可以用一个Git钩子脚本来自动构建我们的软件,并在每master
有提交的情况下将其推出到生产服务器。
支撑分支
除了主要分支(main branches),还有其他分支帮助团队成员之间的平行开发,简化功能跟踪,为生产发布做准备,并协助快速修复现场生产问题。
与主要分支(main branches)不同,这些分支的寿命总是有限的,因为它们最终会被删除。
- Feature branches
- Release branches
- Hotfix branches
每一个分支都有特定的目的,并受到严格的规则约束,即哪些分支可以成为它们的起始分支,哪些分支必须成为它们的合并目标。我们将在一分钟内看完它们。
从技术角度看,这些分支绝非 "特殊"。这些分支的类型是按照我们的使用方式来分类的。它们当然是普通的 Git 分支。
特性分支
-
可以来自:
develop
-
必须被合并到:
develop
-
命名规范:
除
master
,develop
,release-*
, orhotfix-*
之外
feature branch
是用来为最近的或很久之后的版本开发新特性的。当开始开发一个特性时,这个特性将被纳入的目标版本在当时很可能是未知的。
特性分支的本质是,只要该特性还在开发中,它就一直存在,但最终会被合并回develop
中(以保证将新特性添加到即将发布的版本中)或被丢弃(在失败的情况下)。
feature branch
典型情况只存在于开发者仓库中。而不是中心仓库origin
。
创建特性分支
$ 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
The --no-ff
flag causes the merge to always create a new commit object, even if the merge could be performed with a fast-forward. This avoids losing information about the historical existence of a feature branch and groups together all commits that together added the feature. Compare:
In the latter case, it is impossible to see from the Git history which of the commit objects together have implemented a feature—you would have to manually read all the log messages. Reverting a whole feature (i.e. a group of commits), is a true headache in the latter situation, whereas it is easily done if the --no-ff
flag was used.
Yes, it will create a few more (empty) commit objects, but the gain is much bigger than the cost.
发布分支
- 可以来自:
develop
- 必须被合并到:
develop master
- 命名规范:
release-*
release branch
支持新生产版本的准备工作。它们允许最后一刻的仔细检查。此外,它们还可以进行小的错误修复,并为发布版本准备元数据(版本号、构建日期等)。通过在发布分支上完成所有这些工作,develop
分支就可以为下一个大的发布版本接收功能了。
将新的发布分支从develop
分支中分离出来的关键时刻是当develop
分支(几乎)反映了新版本的理想状态。
-
至少所有针对即将发布的版本的特性都必须在这个时间点合并到
develop
中。 -
所有针对未来版本的功能不能合并到
develop
——这些功能必须等到release branch被创建出来之后才能合并。
正是在发布分支创建的时候,即将发布的版本才会被分配一个版本号,之前则不能。在这之前,develop
反映的是 "下一个版本 "的变化,但在发布分支开始之前,这个 "下一个版本 "最终会变成0.3还是1.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(-)
这个新的分支可能会在那里存在一段时间,直到发行版肯定会推出。在此期间,可以在这个分支(而不是在develop
)进行错误修复。严格禁止在这里添加大型新功能。它们必须被合并到develop
中,因此,要等到下一个大版本。
合并、删除分支
当发布分支的状态准备好成为一个真正的版本时,需要进行一些操作。首先,发布分支被合并到master
上(因为master
上的每个提交都是一个新的版本,请记住)。接下来,master
上的提交必须被打上标签,以方便将来参考这个历史版本。最后,发布分支上的修改需要合并到develop
中,这样未来的版本也会包含这些错误的修正。
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
git checkout develop
# Switched to branch 'develop'
git merge --no-ff release-1.2
# Merge made by recursive.
# (Summary of change
git branch -d release-1.2
# Deleted branch release-1.2 (was ff452fe).
修复分支
- 可以来自于
master
- 必须被合并到
develop and master
- 分支命名规范
hotfix-*
热修复分支与发布分支非常相似,因为它们也是为了准备新的生产版本,尽管是未经计划的。它们的出现是由于需要对生产版本的不理想状态立即采取行动。当生产版本中的关键错误必须立即解决时,可以从标志着生产版本的主分支上的相应标签中分出一个热修复分支。
其本质是,团队成员的工作(在develop
分支上)可以继续进行,而另一个人正在准备快速的生产修复。
创建修复分析
热修复分支是由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(-)
结束修复分支
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
git checkout develop
# Switched to branch 'develop'
git merge --no-ff hotfix-1.2.1
# Merge made by recursive.
# (Summary of changes)
这里的一个例外是,当目前存在一个发布分支时,需要将修复分支的修改合并到该发布分支,而不是develop
。将bugfix合并到release分支,最终会导致bugfix也被合并到develop
中,当发布分支完成时。(如果develop
中的工作立即需要这个bugfix,并且不能等待发布分支的完成,那么现在也可以安全地将bugfix合并到develop
中)。
git branch -d hotfix-1.2.1
# Deleted branch hotfix-1.2.1 (was abbe5d6).