本篇文章以帮助更多的人使用正确的git分支模型,这是一个非常成功的git分支模型,在github上广为流传。
本篇文章不是一个基础文章,你需要先有一定的git基础知识。
主分支
从核心上说,开发模型很大程度上受到现有模型的启发。中央仓库拥有两个具有无限生命周期的主要分支:
master
develop
git用户应该都非常熟悉在origin仓库的master分支,与master分支并行,存在另一个分支称为develop.
我们认为origin/master是HEAD的主分支,它的源代码总是反映生产就绪状态。
我们认为origin/develop是HEAD的主要分支,其中HEAD的源代码总是反映下一个版本最新交付的开发更改的状态。有人称之为“集成分支”。这是构建任何自动构建的地方。
当开发分支中的源代码达到稳定点并准备发布时,所有的更改都应该以某种方式合并回master,然后用发布号标记。具体是如何做到的,我们将进一步讨论。
因此,每当变更被合并回master时,根据定义,这就是一个新的生产版本。我们倾向于在这一点上非常严格,所以理论上,当每次在master上有一个提交时,我们可以使用一个Git hook脚本来自动构建和推出我们的软件到我们的生产服务器上。
支持分支
除了master和develop的主分支之外,我们的开发模型还使用了各种支持分支来帮助团队成员之间的并行开发,简化特性的跟踪,为生产版本做准备,并帮助快速修复生产问题。与主分支不同,这些分支的生命时间总是有限的,因为它们最终将被删除。
我们可以使用的不同类型的分支是:
- Feature branches
- Release branches
- Hotfix branches
这些分支中的每一个都有特定的目的,并且都有严格的规则约束,哪些分支可以是它们的原始分支,哪些分支必须是它们的合并目标。我们马上就会讲到。
从技术角度来看,这些分支绝不是“特殊的”。分支类型是根据我们如何使用它们来分类的。它们当然是普通的老Git分支。
Feature branches (特性分支)
可以分出从:
develop
必须合并回:
develop
分支命名约定:
除了master,develop,release-*,或者hotfix-*
特性分支(有时称为主题分支)用于为即将到来的或遥远的未来版本开发新特性。当开始开发一个特性时,这个特性将被合并的目标版本可能还不知道。特性分支的本质是,只要特性还在开发中,它就会存在,但最终会被合并到develop
中(为即将发布的版本添加新特性)或被丢弃(以防出现令人失望的实验)。
特性分支通常只存在于开发人员的仓库中,而不存在于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
--no-ff标志使merge总是创建一个新的提交对象,即使merge可以以fast-forward方或执行。这样可以避免丢失特性分支的历史存在信息,并将添加该特性的所有提交分组在一起。比较:
在后一种情况下,不可能从Git历史中看到哪个提交对象一起实现了某个特性—您必须手动读取所有日志消息。在后一种情况下,恢复整个特性(比如一组提交)是真正令人头疼的事情,而如果使用--no-ff标志,则很容易做到。
是的,它将创建更多的(空的)提交对象,但是收益远远大于成本。
Release branches (发布分支)
可以分出从:
develop
必须合并回:
develop和master
分支命名约定:
release-*
发布分支支持新产品发布的准备工作。此外,它们还允许对小错误进行修复,并为发布(版本号、构建日期等)准备元数据。通过在发布分支上完成所有这些工作,develop分支就可以接收下一个大发布的特性。
从develop分支出一个新的发布分支的关键时刻是当开发(几乎)反映了新发布的期望状态时。至少,所有针对待构建版本的特性都必须合并在一起,以便在此时进行开发。所有针对未来版本的特性可能都不是——它们必须等到发布分支分支出来之后。
正是在发布分支的开始,即将发布的版本才会得到版本号,而不是更早。在那之前,开发分支反映了“下一个版本”的变化,但在发布分支开始之前,“下一个版本”最终会变成0.3还是1.0还不清楚。这个决定是在发布分支开始时做出的,并由项目关于版本号碰撞的规则执行。
创建发布分支
发布分支是从开发分支创建的。例如,版本1.1.5是当前的产品版本,我们即将发布一个大版本。开发状态已经为“下一个版本”做好了准备,我们已经决定这将成为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是一个虚构的shell脚本,它更改工作副本中的一些文件以反映新版本。(当然,这可以是一个手动更改—关键是一些文件更改。)然后,提交升级的版本号。
这个新的分支可能会在那里存在一段时间,直到正式发布。在此期间,bug修复可能应用于这个分支(而不是开发分支)。在这里,添加大型新功能是严格禁止的。它们必须合并到开发中,等待下一个大版本的发布。
完成发布分支
当发布分支的状态准备成为一个真正的发布时,需要执行一些操作。首先,发布分支被合并到master(因为根据定义,master上的每个提交都是一个新发布,请记住)。接下来,必须对master上的提交进行标记,以便将来方便地引用此历史版本。最后,在发布分支上所做的更改需要合并回开发中,以便将来的发布也包含这些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
发行版现在已经完成,并标记以供以后参考。
为了保持在发布分支中所做的更改,我们需要将这些更改合并到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).
Hotfix branches(热修复分支)
可以分出从:
master
必须合并回:
develop和master
分支命名约定:
hotfix-*
热修复分支与发布分支非常相似,因为它们也意味着为新的产品发布做准备,尽管是在计划外的。它们产生于必须立即对现场生产版本的不希望的状态采取行动。当必须立即解决生产版本中的一个严重错误时,可以从标识生产版本的主分支上的相应标记分支出一个热修复分支。
本质是团队成员(在开发分支上)的工作可以继续,而另一个人正在准备一个快速的生产修复。
创建修补程序分支
热修复分支是从master分支创建的。例如,假设版本1.2是当前正在运行的生产版本,由于一个严重的错误而导致问题。但develop上的变更仍然不稳定版。然后我们可能会分支出一个热修复分支,并开始修复这个问题:
$ 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 commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)
完成一个修补程序分支
完成后,bug修复需要合并回master,但也需要合并回develop,以确保bug修复也包含在下一个版本中。这与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
接下来,在develop中也包含bugfix:
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
这里的规则存在一个例外,当存在一个release分支时,热修复更改需要合并到该release分支中,而不是开发。将bug修复反向合并到release分支中,最终会导致当release分支完成时,bug修复也被合并到开发中。(如果develop中的工作立即需要这个bug修复,而不能等待release分支完成,那么您现在也可以安全地将这个bug修复合并到develop中。)
最后,移除临时分支:
$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).
Summary
虽然这个分支模型并没有什么真正令人震惊的新内容,但下面这张“大局”图在我们的项目中非常有用。它形成了一个易于理解的优雅的思维模型,并允许团队成员对分支和发布过程形成一个共同的理解.