likes
comments
collection
share

一个成功的Git分支模型(中文翻译)

作者站长头像
站长
· 阅读数 4

本文是对A successful Git branching model文章的中文翻译,作者Vincent Driessen在2010年写下了这篇文章,并在2020年做了一些新的思考(见本文第0章)

0 反思笔记(2020年5月5日)

这个模型构思于2010年,距今也有10来年了,但是Git诞生也没多久。在这10来年中,git-flow(本文列出的分支模型)流行于很多的软件开发团队以至于人们把它视为一种标准,但是,很不幸的是,它也被视为一种教条或者灵丹妙药。

在过去的10年中,Git席卷了整个世界,并且,基于Git开发的最流行的软件类型正在更多地转向web应用。web应用通常是持续交付的,并且,你也不需要支持多个软件版本。

这并不是10年前我写这边文章时设想的软件类型。如果你的团队正在做持续交付的软件,我会建议你使用一个更简单的工作流(例如GitHub flow),而不是将git-flow硬塞到你的团队。

然而,如果你构建的软件有明确的版本规划,或者你的软件需要支持多个版本,那么git-flow或许能够很好地适合你的团队,因为它在过去10年中已经向世人证明了。在这种情况下,请继续阅读。

总而言之,永远要记住灵丹妙药是不存在的。不要讨厌这个模型。根据你的实际情况,自行觉得。

1 简介

在这篇文章中,我介绍一个在过去1年中为我的一些项目(包括工作和个人项目)引入的并结果证明非常成功的开发模型。我一直想写它有一段时间了,但是直到现在我才真正找时间来完全地写。我不会过多地介绍项目的细节,只会谈论分支策略和发布管理。

一个成功的Git分支模型(中文翻译)

2 为什么是Git?

有关Git和集中式源代码控制系统的优势和劣势的全面讨论,请参考这篇文章。那里有很多激烈地争论。作为一名开发人员,相比其他工具,如今我更喜欢GitGit真正改变了开发人员对于合并和分支的看法。在我经历过的经典的CVS/Subersion世界中,合并/分支一直被认为有点吓人(“当心合并冲突,它们会咬你”),并且你只会偶尔才会做这些事情。

但是,使用Git时,这些操作是及其方便的,并且它们被看作你日常工作中核心部分之一。例如:在CVS/Subversion书籍中,分支和合并在最后一章中讨论(针对高级用户),但是在每一本Git书中,在第3章中(基础篇)已经介绍了。

由于它的简单性和重复特性,分支和合并不再是可怕的事情。版本控制工具应该比其他任何东西都有助于分支/合并。

Git已经说了够多了,我们接着说这个开发模型。我将在这里介绍的模型本质是一组过程,每个开发团队的成员都必须遵守它才能进入一个托管的软件过程。

3 分散而集中

我们使用的且能够和这个分支模型配合良好的仓库设置,是具有中央“真相”的仓库设置。请注意,这个仓库只是被认为是中央仓库(Git是分布式版本控制系统,从技术角度说,并没有中央仓库之类的东西)。我们将这个仓库成为origin,因为所有Git用户都熟悉这个名字。

一个成功的Git分支模型(中文翻译)

每个开发人员从origin中拉取并将自己的内容推送到origin。但是除了中心化的push-pull关系以外,每个开发也可以从其他成员那里拉取变更,这样就形成了一个子团队。例如,对于2个或者更多的开发人员在一起实现一个大功能时,在将正在进行的工作过早的推送到origin之前,这或许是很有用的。在上图中,那有alicebobalicedaviddavidclair多个子团队。

从技术上讲,这意味着,alice定义了一个名称bobGit远程仓库,指向bob的仓库,反之亦然。

4 主要的分支

在核心内容上,这个开发模型受现有模型的启发。中央仓库有用2个长期存在的分支:

  • master
  • develop

每个Git用户对于orgin中的master分支是很熟悉的。与master并行的另一个存在的分支称为develop

一个成功的Git分支模型(中文翻译)

原文过于绕口,这里使用阮一峰老师博客的内容

origin/master分支用于存放当前对外发布的版本,任何时候都可以从这个分支拿到稳定的、最新发布的版本。origin/develop分支用于日常开发,存放最新的开发版本,用于下一个版本的迭代。

develop分支中的源代码达到一个稳定的状态并且做好了发布准备,该分支的所有修改都要以某种方式合并到master分支,并且打上发布编号的tag。接下来将进一步讨论如何完成该操作。

因此,每当所有的修改都合并到master分支,就意味着一个生产版本。我们在这方面往往非常严格,因此理论上,每当master分支有一个提交时,我们可以使用Git钩子脚本来自动构建并推送我们的软件到服务器上。

5 支持的分支

除了主要的分支masterdevelop以外,我们的开发模型使用一系列支持分支来辅助在团队成员间进行并行开发,轻松地追踪功能,准备生产版本并协助快速修复上线版本的问题。与主要的分支不同,这些分支最终是要被删除的,它们的声明周期是有限的。

我们可能使用的分支类型有:

  • feature分支
  • release分支
  • hotfix分支

每一个分支都有指定的目的,并且必须遵循严格的规则,即:哪些分支是它们的原始分支,哪些分支有时它们的合并目标。我们将会在1分钟内讲述它们。

从技术角度来看,这些分支不是那么“特殊”。这些分支的类型依据我们使用它们的目的来分类。它们本质上就是Git中普通的分支。

5.1 feature分支

可能从以下分支中创建:

  • develop

可能合并到以下分支:

  • develop

分支命名规则:

  • 除了masterdeveloprelease-*或者hotfix-*以外都可以

feature分支(有时也成为topic分支)用于为即将发布或者远期规划的版本开发新的功能。当开始开发一个功能时,这个功能将被合并到的目标版本在那个时候是未知的。feature分支的本质是,只要功能处于开发阶段,他就存在,但是最终会被合并到开发分支中(以明确新功能将会被添加到即将发布的版本中)或者被丢弃(在令人失望的实验情况下)。

feature分支通常只存在于开发者的仓库中,在origin中不会存在。

5.1.1 创建feature分支

在开始开发一个新功能时,从develop分支上创建一个分支:

git checkout -b myfeature develop

5.1.2 合并一个完成的功能到develop分支

已完成的功能分支可能要合并到develop分支,已明确要添加到即将发布的版本中。

git checkout develop
git merge --no-ff myfeature
git branch -d myfeature
git push origin develop

--no-ff标志使合并始终创建一个新的提交对象,即使可以使用fast-forward进行合并。这样可以避免丢失关于这个功能分支的历史存在消息,并将所有添加该功能的提交组合在一起。

一个成功的Git分支模型(中文翻译)

在后一种情况中(即没有使用--no-ff),不可能从Git历史中查看到哪些提交对象一起实现了一个功能,你必须手动的查看所日志消息才能确定。恢复整个功能,对于后一种情况是很头疼的一件事,除非在合并代码时使用了--no-ff

是的,使用--no-ff会创建更多(空)的提交对象,但是收益大于成本。

5.2 release分支

可能从哪个分支中创建:

  • develop

必须合并到哪个分支:

  • develop
  • master

分支命名规则:

  • release-*

release分支为一个新的生产版本提供支持。它允许小错误的修复以及为发布准备元数据(版本号、构建日期等等)。通过在一个relase分支上做这些内容,develop分支就可以接收下一个大版本的功能。

develop分支创建一个新的release分支的关键时刻就是当develop分支(几乎)反映了一个新版期望的状态。至少所有针对要构建的版本的功能都必须在此时刻合并到develop分支中。针对未来版本的功能必须等待release分支被创建完成。

release分支被创建时,即将发布的版本才会得到一个版本号--而不是更早。在这之前,develop分支只是反映了“下一个版本”的变化,但不清楚“下一个版本”最终会变成0.3还是0.1,直到release分支被创建。该决定是在release分支开始时才做的决定,并且是由项目的版本号更新规则执行的。

5.2.1 创建release分支

release分支从develop分支中构建。例如:假设1.1.5是当前的生产版本,并且我们将由一个大版本要发布。develop分支已经为下一个版本做好了准备,并且我们决定将生产版本变成1.2。所以我们创建并给予release版本一个反应新版本的编号:

git checkout -b release-1.2 develop
./bum-version.sh 1.2
git commit -a -m "Bumped version number to 1.2"

在创建一个release版本并切换到该版本以后,我们提高版本号。这里,bum-version.sh是一个虚构的shell脚本,他更改工作副本中一些文件用来反应新版本。然后,这个被提高的版本被提交了。

这个版本会存在一些时间,直到可以确定推出该版本。在这段时间内,在这个分支(而不是develop分支)上也可以做一些bug修复。但是在这里添加一个大的新功能是绝对禁止的。这些新功能必须合并到develop分支,因此,等待下一个大的版本。

5.2.2 完成release分支

release分支的状态已经准备好转为正式的版本,需要采取一些行动。首先,将relase分支合并到master分支(记住,每一次在master分支上的提交表示一个新的版本)。然后,必须对master的提交做标记,以便将来参考此历史版本。最后,在relase分支上所做的修改需要合并到develop分支上,以便将来的版本也包含这些错误的修复。

Git中的前两步:

git checkout master
git merge --no-ff release-1.2
git tag -a 1.2

发布现已完成,并做了标记,以供将来参考。

为了保存这个relase分支中的修改,需要将这些合并到develop分支中。

git checkout develop
git merge --no-ff release-1.2

在这一步中很可能会导致合并冲突。如果这样,先修复这些冲突,然后提交。

现在我们已经做完了,release分支也将被移除,因为我们不再需要它了。

git branch -d release-1.2

5.3 hotfix分支

可能从哪些分支中创建:

  • master

必须合并到哪些分支:

  • develop
  • master

分支命名规则:

  • hotfix-*

一个成功的Git分支模型(中文翻译)

hotfix分支和release分支很像,因为它们也旨在为新的生产版本做准备,尽管是计划外的。它们源于对现有已发布版本的不良状态立即采取行动的必要性。当必须立即解决生产版本中的严重bug时,可以从相应的master分支上创建一个hostfix分支。

本质上团队成员(在develop分支上)继续工作,但是另外一个人在准备快速地修复生产环境的bug

5.3.1 创建hotfix分支

hotfix分支从master分支上创建。例如,假设1.2是当前正在运行的生产版本的版本号,并且由于严重的错误导致麻烦。但是,develop分支上的修改是不稳定的。我们可以创建一个hotfix分支,然后在这上面修复这个bug

git checkout -b hotfix-1.2 master
./bump-version.sh 1.2.1
git commit -a -m "Bump version number 1.2.1"

在分支创建后,不要忘记提升版本号。

然后,修复这个bug并且通过一次或者多次提交修复的内容。

git commit -m "Fixed severe production problem"

5.3.2 完成hotfix分支

完成后,需要将修复bug的内容合并到master分支上,同时也需要合并到develop分支上,以确保这些修复的内容也包含在下一个版本中。这与release分支的完成方式完全一致。

首先,更新master分支并做好版本标记

git checkout master
git merge --no-ff hotfix-1.2.1
git tab -a 1.2.1

然后,将这些修复的内容提交到develop分支

git checkout develop
git merge --no-ff hotfix-1.2.1

这个规则的一个例外是,当一个release分支存在时,hotfix分支的修改需要合并到这个release分支中,而不是develop分支。将修改的内容反向合并到release分支,当这个分支完成时,最终也会将hotfix分支上修复的内容合并到develop分支。(如果当前develop分支上的工作急需依赖此bug的修复并且不能等到完成release分支,你也可以安全地将bug的修复内容合并到develop分支中。)

最后,移除这个临时分支。

git branch -d hotfix-1.2.1

6 总结

虽然这个分支模型没有什么令人震惊的新东西,但是文章开头的“大图”所描述的流程在我们的项目中被证实是非常有用的。它形成了一个易于理解的模型,并允许团队成员对分支和发布过程形成共同的理解。

这里提供文章开头那张大图的pdf版本