原文:https://nvie.com/posts/a-successful-git-branching-model/
在这篇文章中,我介绍了我在大约一年前为我的一些项目(包括工作项目和私人项目)介绍的开发模型,结果证明它非常成功。我想写这篇文章已经有一段时间了,但到现在为止,我还没有真正找到足够的时间去彻底地写这篇文章。我不会谈论任何项目的细节,仅仅是关于分支策略和发布管理。
为什么是吉特?
有关Git与集中式源代码控制系统的优缺点的详细讨论,请参阅万维网。那里有很多火焰之战。作为一名开发人员,我更喜欢Git,而不是今天的所有其他工具。GIT确实改变了开发人员对合并和分支的看法。从我来自的经典CVS/Subversion世界来看,合并/分支一直被认为有点可怕(“小心合并冲突,它们咬你!”)有时候你只做一次。
但是使用Git,这些操作非常便宜和简单,它们被认为是您的核心部分之一。每日工作流程真的。例如,在CVS/Subversion中书籍,分支和合并首先在后面的章节中讨论(针对高级用户),而在每个 吉特 书,它已经在第3章(基础部分)中讨论过了。
由于它的简单性和重复性,分支和合并不再是什么可怕的东西。版本控制工具应该比任何其他工具更有助于分支/合并。
关于这些工具,让我们来看看开发模型吧。我在这里将要介绍的模型本质上不过是一组过程,每个团队成员都必须遵循这些过程才能进入一个托管软件开发过程。
分散但集中
我们使用的存储库设置和这个分支模型很好地工作,就是使用一个中心的“True”回购。请注意,此回购仅适用于考虑作为中心(因为Git是一个DVCS,在技术层面上没有中央回购这种东西)。我们将把这种回购称为回购。origin,因为这个名字对所有的Git用户都很熟悉。
每个开发人员都会拉并推到原点。但是,除了集中的推拉关系之外,每个开发人员还可以从其他同行那里提取变化,组成子团队。例如,在将正在进行的工作推进到origin过早。在上面的图中,有Alice和Bob、Alice和David以及Clair和David的子团队。
从技术上讲,这只意味着Alice定义了一个名为bob指向Bob的存储库,反之亦然。
主枝
在核心方面,开发模型受到现有模型的极大启发。中央回购拥有两个寿命无限长的主要分支:
masterdevelop
这个master支行origin每个Git用户都应该熟悉。平行于master分支,另一个分支存在,名为develop.
我们认为origin/master的源代码的主要分支HEAD总是反映生产准备状态。
我们认为origin/develop的源代码的主要分支HEAD始终反映最新交付的下一个版本的开发更改的状态。有些人将此称为“整合分支”。这是任何自动夜间构建的地方。
当源代码在develop分支到达稳定点并准备发布,所有更改都应该合并回master不知何故加上了一个发行号。将进一步详细讨论如何做到这一点。
因此,每次将更改合并回master,这是一个新的生产版本。按定义。我们往往对此非常严格,因此理论上,我们可以使用git钩子脚本自动构建软件并在每次提交到生产服务器时将其推出。master.
支支
在主要树枝旁边master和develop,我们的开发模型使用各种支持分支来帮助团队成员之间的并行开发,简化功能跟踪,为生产发行做准备,并帮助快速解决实际生产问题。与主要分支不同,这些分支的寿命总是有限的,因为它们最终会被移除。
我们可能使用的不同类型的分支如下:
- 特征分支
- 释放枝
- 热修复分支
每个分支都有特定的用途,并且必须遵守严格的规则,即哪些分支可以是它们的起始分支,哪些分支必须是它们的合并目标。我们马上就过去。
从技术角度看,这些分支绝不是“特殊”的。分支类型按我们使用他们。它们当然是普通的老吉特树枝。
特征分支
可从:
develop
必须重新合并成:
develop
分支命名约定:
除了master, develop, release-*,或hotfix-*
特性分支(有时称为主题分支)用于为即将发布的或遥远的将来版本开发新特性。当开始开发一个特性时,这个特性将包含在其中的目标版本很可能在这一点上是未知的。特性分支的本质是,只要功能正在开发,它就会存在,但最终将被合并回develop(在即将发布的版本中添加新特性)或丢弃(在一个令人失望的实验中)。
特性分支通常只存在于开发人员repos中,而不存在于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标志使合并始终创建一个新的提交对象,即使合并可以使用快速转发来执行。这样可以避免丢失有关特性分支历史存在的信息,并且所有组一起提交一起添加的特性。比较:
在后一种情况下,从Git历史记录中不可能看到哪些提交对象一起实现了一个特性--您必须手动读取所有的日志消息。恢复整个特性(即一组提交),在后一种情况下是真正令人头痛的,而如果--no-ff使用了旗子。
是的,它将创建更多(空的)提交对象,但收益要比成本大得多。
释放枝
可从:
develop
必须重新合并成:
develop和master
分支命名约定:
release-*
发布分支支持准备一个新的生产版本。它们允许在最后一刻点缀I‘s和交叉t’s,此外,它们还允许微小的错误修复和为发行版(版本号、构建日期等)准备元数据。通过在发布分支上完成所有这些工作,develop分支被允许接收下一个大版本的特性。
新发布分支的关键时刻develop当开发(几乎)反映新版本的期望状态时。至少所有针对即将构建的版本的功能都必须合并为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是一个虚构的shell脚本,它更改工作副本中的一些文件以反映新版本。(当然,这可能是一次人工更改-重点是一些)文件更改。)然后,提交颠簸的版本号。
这个新分支可能会在那里存在一段时间,直到发行版肯定推出为止。在此期间,可以在此分支中应用bug修复(而不是在develop)。严格禁止在这里添加大型新功能。它们必须合并成develop因此,等待下一个大发行版。
完成发布分支
当发布分支的状态准备成为真正的版本时,需要执行一些操作。首先,将发布分支合并到master(因为每次承诺master是一个新版本按定义,记住)。接下来,承诺master为了便于将来参考这个历史版本,必须进行标记。最后,需要将对发布分支所做的更改合并回develop,这样以后的版本也会包含这些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
发行版现在已经完成,并为以后的参考做了标记。
编辑:您最好使用
-s或-u <key>以加密方式对标记进行签名的标志。
为了保持在发布分支中所做的更改,我们需要将它们合并回develop尽管如此。在吉特:
$ 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分支非常类似于发布分支,因为它们也是为了为新的生产发行做准备,尽管是计划外的。它们产生于必须立即采取行动的一个不想要的状态,一个现场生产版本。当必须立即解决生产版本中的关键错误时,可能会从标记生产版本的主分支上的相应标记中分支出一个修补程序分支。
本质上是团队成员的工作develop可以继续,而另一个人正在准备一个快速的生产修复。
创建修补程序分支
修补程序分支是从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(-)
完成一个热修复分支
完成后,修补程序需要合并回master,但也需要合并回develop,以确保在下一个版本中也包含了错误修复。这与完成发布分支的方式完全类似。
首先,更新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
编辑:您最好使用
-s或-u <key>以加密方式对标记进行签名的标志。
接下来,在develop也是:
$ git checkout develop Switched to branch 'develop' $ git merge --no-ff hotfix-1.2.1 Merge made by recursive. (Summary of changes)
这条规则的一个例外是,当当前存在发布分支时,需要将修补程序更改合并到该发布分支中,而不是develop。将错误修复重新合并到发布分支将最终导致将bug合并到develop当发布分支完成时,也是如此。(如果在develop立即需要此修补程序,不能等待发布分支完成,您可以安全地将bug合并到develop现在也已经是了。)
最后,删除临时分支:
$ git branch -d hotfix-1.2.1 Deleted branch hotfix-1.2.1 (was abbe5d6).
摘要
虽然这个分支模型并没有什么令人震惊的新东西,但这篇文章开头的“大图”在我们的项目中被证明是非常有用的。它形成了一个优雅的心智模型,易于理解,并允许团队成员开发对分支和释放过程的共享理解。
这里提供了一个高质量的PDF版本的数字.你可以随时把它挂在墙上,以供快速参考。
最新情况:对于任何有要求的人来说:这是gitflow-model.src.key主要图表图像(苹果基调)。