这个模型是在2010年构思出来的,而现在距今已有10多年的历史,而Git本身才诞生不久。在那10年中,git-flow(本文介绍的分支模型)在许多软件团队中非常流行,以至于人们开始将其视为某种标准,但不幸的是,它也被当作教条或灵丹妙药。
在那10年中,Git本身就席卷了整个世界,并且与Git一起开发的最流行的软件类型正在越来越多地转向Web应用程序-至少在我的过滤泡中。Web应用程序通常是连续交付的,不会回滚,并且您不必支持早期运行的软件的多个版本。
这不是我十年前写博客时想到的那种软件。如果您的团队正在持续交付软件,我建议您采用更简单的工作流程(例如GitHub flow),而不是尝试将git-flow引入您的团队。
但是,如果您正在构建显式版本的软件,或者如果您需要在早期支持软件的多个版本,那么git-flow仍然可能像适合您团队的人一样适合您的团队最近10年。在这种情况下,请继续阅读。
总而言之,请始终记住万灵药不存在。考虑您自己的情况。别讨厌 自己决定。
在这篇文章中,我介绍了大约一年前为我的一些项目(在工作中和在私人项目中)引入的开发模型,事实证明该模型非常成功。我一直想写一阵子,但是直到现在,我还没有真正找到彻底这样做的时间。我不会谈论任何项目的细节,而只会谈论分支策略和发布管理。
去中心化但中心化
我们使用的并且与此分支模型配合良好的存储库设置是具有中央“真实”存储库的存储库设置。请注意,此 repo 仅 被视为 中央仓库(由于 Git 是 DVCS,因此在技术层面上没有中央仓库这样的东西)。我们将此存储库称为origin,因为所有 Git 用户都熟悉此名称。
每个开发人员pulls和pushes到名为origin的存储库。但除了中心化的推拉关系,每个开发者也可能会从其他同行那里fetch变化,形成子团队。
主要分支
关键地是,开发模型受到现有模型的极大启发。中央仓库拥有两个具有无限生命周期的主要分支:
master
develop
每个Git的使用者都应该熟悉origin的master分支。
与master分支平行,存在另一个分支,称为develop。
我们认为origin/master是源代码HEAD始终反映生产就绪状态的主要分支 。
我们认为origin/develop是主分支,其中的源代码 HEAD始终反映下一个版本最新交付的开发更改的状态。有些人将其称为“集成分支”。这是构建任何自动夜间构建的地方。
当develop分支中的源代码达到稳定点并准备发布时,所有更改都应该合并到master 。
因此,每次将更改合并回 时master,根据定义,这是一个新的生产版本。我们在这方面往往非常严格,因此理论上,我们可以使用 Git 钩子脚本在每次提交 master。
支持分支
在主要分支master和旁边develop,我们的开发模型使用各种支持分支来帮助团队成员之间的并行开发、轻松跟踪功能、准备生产版本并帮助快速修复实时生产问题。与主要分支不同,这些分支的生命周期总是有限的,因为它们最终将被删除。
我们可能使用的不同类型的分支是:
Feature branches
Release branches
Hotfix branches
这些分支中的每一个都有特定的目的,并且在哪些分支可以是它们的原始分支以及哪些分支必须是它们的合并目标方面受到严格的规则约束。
从技术角度来看,这些分支绝不是“特殊的”。分支类型根据我们如何使用它们进行分类。它们当然是普通的Git 分支。
Feature branches
可能来自以下分支:
develop
必须合并到的分支:
develop
分支命名规定:
除了 master, develop, release-, 或hotfix-
功能分支(或有时称为主题分支)用于为即将发布或遥远的未来版本开发新功能。当开始开发功能时,此时可能不知道将合并该功能的目标版本。功能分支的本质在于,只要该功能处于开发阶段,它就存在,但最终会被合并回develop(明确将新功能添加到即将发布的版本中)或丢弃(如果实验令人失望)。
功能分支通常只存在于开发者仓库中,而不存在于origin.
创建feature branches
当开始一个新特性的工作时,从这个develop分支创建一个新的分支。
Git命令
$ git checkout -b myfeature develop
Switched to a new branch "myfeature"
将已经开发的功能分支合并到develop分支中
可以将完成的featuren分支合并到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标志,这很容易做到 。
是的,它会创建更多(空)提交对象,但收益远大于成本。
Release branches
可能来自于develop
必须合并到develop and master
分支命名规则
release-*
Release branches支持准备新的生产版本。它们允许在最后一刻打点 i 和交叉 t。此外,它们允许修复小错误并为发布准备元数据(版本号、构建日期等)。通过在发布分支上完成所有这些工作,该develop 分支被清除以接收下一个大版本的功能。
创建出来自于develop新Release branches,至少所有针对待构建版本的功能都必须合并 到develop。所有针对未来版本的功能可能都不是——它们必须等到Release branches分支创建后。
正是在release分支的开始,即将发布的版本被分配了一个版本号——而不是更早的版本。直到那一刻,develop 分支反映了“下一个版本”的变化,但不清楚“下一个版本”最终会变成 0.3 还是 1.0,直到elease分支启动。该决定是在发布分支开始时做出的,并由项目关于版本号增加的规则执行。
创建release分支
release分支是从develop分支创建的。例如,假设版本 1.1.5 是当前的生产版本,我们即将发布一个大版本。此时,develop分支已为"下一版本"做好准备,这次发版将成为1.2版(而不是 1.1.6 或 2.0)。因此,创建一个release分支并给该分支命名一个反映新版本的名称
$ git checkout -b release-1.2 develop
切换到新分支“release-1.2”
$ ./bump-version.sh 1.2
文件修改成功,版本撞到1.2。
$ git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 个文件已更改,1 个插入(+),1 个删除(-)
在创建一个新分支并切换到它之后,我们增加了版本号。这 bump-version.sh是一个虚构的 shell 脚本,它更改了工作副本中的一些文件以反映新版本。(这当然可以是手动更改——关键是某些文件发生了更改。)然后,提交了增加的版本号。
这个新分支可能会存在一段时间,直到正式发布。在此期间,可能会在此分支(而不是在develop分支上)错误修复Bug,但是,禁止在此处添加大型新功能的代码。新的功能代码,必须合并到中develop分支,然后,等待下一个重要版本的发布。
完成一个release分支
当release分支的状态准备好,可以准备发布时,在准备发布时,可以先执行一些操作。首先,将release分支合并到master分支(因为根据定义,每次提交到master的代码都是一个新版本)。接下来,必须将此次提交打上标记,以方便参考此历史版本。最后,在releasef分支提交的如bugfix的代码必须合并回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
这个release分支工作已经全部完成,并且已经打上tag以便将来查看。
为了保留在release分支提交的代码,我们需要将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分支,对应的release分支可以被删除掉了。
$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).
hotfix 分支
此分支从master分支fork。此会合并回develop分支和master分支。
此分支命名的格式:hotfix-*
热修复分支与发布分支非常相似,因为它们也意味着为一个新的生产版本做准备,尽管是计划外的。当必须立即解决生产版本中的关键bug时,可能会从标记生产版本的主分支上的相应标记中分离出一个热修复分支。
团队可以借用这个特性,一个人在develop分支上工作,另一个正在准备一次生产的修复代码。
创建hotfix 分支
从master分支创建hotfix 分支。举例,假设1.2版本是当前正在运行的产品版本,并由于一个严重的bug而引起麻烦。但是处于开发中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(-)
别忘记在hotfix分支名后面加版本号.然后在hotfix分支修复bug。
$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)
完成hotfix分支
当在hotfix修复工作完成后,bug修复代码需要合并到master分支,同时也需要合并到develop分支,这样做的目的,是为了确保下一个版本中也包含bug修复代码,这跟release分支完成有些类似。
首先,将hotfix分支合并到master分支,并打上tag
$ 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
接下来,将hotfix分支也合并到develop分支
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
这里有一个例外是,如果当前存在一个release分支时,需要将热修复代码合并到该release分支中,而不是开发。当release分支完成时,将bug修复程序反向合并release分支中最终将导致bug修复程序也合并到develop分支中。(如果在debelop的工作立即需要修复这个bug,而不能等待release分支完成,那么您也可以安全地将这个bug修复合并到现在的develop分支中。)
最终,移除这个临时的分支
$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).
总结
虽然这一分支模型并没有什么新鲜的东西,但本文一开始提到的“大图”对我们的项目非常有用。它形成了一个易于理解的优雅的心智模型,并允许团队成员开发对分支和发布过程的共享理解。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。