成功的 Git 分支模型
成功的 Git 分支模型
文森特.德里森报道
于2010年01月05日 星期二
原文地址:https://nvie.com/posts/a-successful-git-branching-model/
反思笔记(2020年3月5日)
这个模型是在2010年构思的,现在已经是10多年前的了,而且是在Git本身诞生后不久。在这10年中,git-flow(本文中列出的分支模型)在许多软件团队中变得非常流行,以至于人们开始将其视为某种标准 - 但不幸的是,它也作为教条或灵丹妙药。
在这10年里,Git本身已经席卷了世界,而使用Git开发的最受欢迎的软件类型正在更多地转向Web应用程序 - 至少在我的过滤器泡沫中是这样。Web 应用通常是持续交付的,而不是回滚的,并且您不必支持在野外运行的多个版本的软件。
这不是我在10年前写博客文章时想到的软件类。如果你的团队正在持续交付软件,我建议采用更简单的工作流程(如GitHub flow),而不是试图将git-flow硬塞进你的团队中。
但是,如果您正在构建明确版本化的软件,或者如果您需要在野外支持软件的多个版本,那么git-flow可能仍然适合您的团队,就像过去10年中对人们一样。在这种情况下,请继续阅读。
总而言之,永远记住灵丹妙药并不存在。考虑你自己的背景。不要恨。自己决定。
在这篇文章中,我介绍了大约一年前我为我的一些项目(包括工作和私人项目)引入的开发模型,并且结果非常成功。我一直想写它一段时间了,但我从来没有真正找到时间彻底这样做,直到现在。我不会谈论任何项目的细节,只会谈论分支策略和发布管理。
为什么选择 git?
有关 Git 与集中式源代码控制系统相比的优缺点的全面讨论,请参阅web。那里有很多火焰战争。作为一名开发人员,我更喜欢 Git 而不是当今的所有其他工具。Git 确实改变了开发人员对合并和分支的看法。从我来自的经典CVS/Subversion世界中,合并/分支一直被认为有点可怕("当心合并冲突,它们会咬你!"),而且你只是每隔一段时间就会做一次。
但是对于Git,这些操作非常便宜和简单,它们被认为是日常工作流程的核心部分之一。例如,在 CVS/Subversion书籍中,分支和合并首先在后面的章节中讨论(对于高级用户),而在每本Git 书籍中,它已经在第 3 章(基础知识)中进行了介绍。
由于其简单性和重复性,分支和合并不再是一件值得害怕的事情。版本控制工具应该比其他任何东西都更有助于分支/合并。
关于工具已经足够多了,让我们继续讨论开发模型。我将在这里介绍的模型基本上只不过是每个团队成员必须遵循的一组过程,以便进行托管软件开发过程。
分散但集中
我们使用的存储库设置与此分支模型配合良好,即使用中央"真实"存储库。请注意,此存储库仅被视为中心存储库(由于 Git 是 DVCS,因此在技术级别上不存在中央存储库)。我们将此存储库称为origin,因为所有 Git 用户都熟悉此名称。
每个开发人员拉动并推送到源。但是,除了集中的推拉关系之外,每个开发人员还可以从其他同行那里进行更改以形成子团队。例如,在将正在进行的工作过早地推送到origin之前,与两个或多个开发人员一起开发大型新功能可能很有用。在上图中,有爱丽丝和鲍勃,爱丽丝和大卫,克莱尔和大卫的子团队。
从技术上讲,这只不过是Alice定义了一个名为bob的Git远程数据库,指向Bob的存储库,反之亦然。
主要分支
从本质上讲,开发模型受到现有模型的极大启发。中央存储库拥有两个具有无限生存期的主要分支:
- master
- develop
每个 Git 用户都应该熟悉originmaster分支。与master分支并行,存在另一个分支,称为develop.
我们认为origin/master是主分支,HEAD的源代码始终反映生产就绪状态。
我们认为origin/develop是主要分支,HEAD的源代码始终反映具有下一个版本的最新开发更改的状态。有些人会称之为"集成分支"。这是构建任何自动夜间构建的地方。
当develop分支中的源代码达到稳定点并准备发布时,所有更改都应以某种方式合并回master然后用版本号标记。如何详细完成这项工作将进一步讨论。
因此,每次将更改合并回master时,根据定义,这都是一个新的生产版本。我们倾向于非常严格,因此从理论上讲,我们可以使用Git钩子脚本来自动构建我们的软件并将其部署到我们的生产服务器,每次在master上提交时。
其他分支
除了主要分支master和develop之外,我们的开发模型使用各种支持分支来帮助团队成员之间的并行开发,简化功能跟踪,为生产版本做准备,并协助快速修复实时生产问题。与主要分支不同,这些分支的使用寿命总是有限的,因为它们最终会被删除。
我们可能使用的不同类型的分支是:
- 功能分支
- 发布分支
- 修补程序分支
这些分支中的每一个都有特定的目的,并且受严格的规则的约束,即哪些分支可能是其原始分支,哪些分支必须是其合并目标。我们将在一分钟内完成它们。
从技术角度来看,这些分支绝不是"特别的"。分支类型按我们使用它们的方式进行分类。它们当然是普通的旧Git分支。
功能分支
可能从以下位置分支:
develop
必须合并回:
develop
分支命名约定:
除master、develop版、release-或hotfix-以外的任何内容
功能分支(或有时称为主题分支)用于为即将发布或遥远的未来版本开发新功能。在开始开发功能时,此时很可能不知道将包含此功能的目标版本。功能分支的本质是,只要功能正在开发中,它就会存在,但最终将被合并回develop(明确地将新功能添加到即将发布的版本中)或丢弃(如果实验令人失望)。
功能分支通常仅存在于开发人员存储库中,而不存在于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和交叉t。此外,它们允许修复小错误并为版本准备元数据(版本号,构建日期等)。通过在发布分支上执行所有这些工作,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脚本,它更改工作副本中的某些文件以反映新版本。(这当然可以是手动更改 - 关键是某些文件会更改。然后,提交凸起的版本号。
这个新分支可能会在那里存在一段时间,直到版本可能明确推出。在此期间,错误修复可能会应用于此分支(而不是develop分支)。严禁在此处添加大型新功能。它们必须合并到develop中,因此,等待下一个大版本。
完成发布分支
当发布分支的状态准备好成为真正的发布时,需要执行一些操作。首先,发布分支被合并到master(因为根据定义master上的每个提交都是一个新版本,请记住)。接下来,必须标记master上的提交,以便将来轻松引用此历史版本。最后,在发布分支上所做的更改需要合并回develop,以便将来的版本也包含这些错误修复。
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
但是,为了保留在发布分支中所做的更改,我们需要将这些更改合并回develop版。在 Git 中:
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-*
修补程序分支与发布分支非常相似,因为它们也旨在为新的生产版本做准备,尽管这是计划外的。它们源于对现场制作版本的不良状态立即采取行动的必要性。当必须立即解决生产版本中的关键 Bug 时,修补程序分支可能会从标记生产版本的主分支上的相应标记中分支出来。
本质是团队成员的工作(在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(-)
不要忘记在分支后碰到版本号!
然后,修复 Bug 并在一个或多个单独的提交中提交修复。
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
接下来,在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中的工作立即需要此错误修复,并且不能等待发布分支完成,您也可以安全地将错误develop现在"中。
最后,删除临时分支:
git branch -d hotfix-1.2.1
# Deleted branch hotfix-1.2.1 (was abbe5d6).
总结
虽然这种分支模型没有什么真正令人震惊的新东西,但这篇文章开始的"大局"数字在我们的项目中非常有用。它形成了一个易于理解的优雅心智模型,并允许团队成员对分支和发布过程形成共同的理解。