本文先是对Vincent Driessen提出git-flow分支模型的原文逐句进行了翻译 ,然后提取文中有价值的信息;在此基础之上详细解释了git-flow的工作流程 ;最后举例说明了git-flow在开发过程中的使用。阅读本文,您可以收获:
- Vincent Driessen提出git-flow模型时候的背景知识和他对于git的一些思考;
- git-flow模型的原理和设计思路;
- git-flow在实践中的应用;
- 常见的git-flow命令。
翻译/创作不易,如果这篇文章对您有些许启示,欢迎在留言区讨论,谢谢!
一、 git-flow分支模型的提出
开始之前,让我们先拜读Vincent Driessen 在2010.01.05 发布的一篇文章。
原文地址为:nvie.com
原文开始
一个成功的Git分支模型 -- Vincent Driessen
2020年3月5日更新
这个模型是在 2010 年构思的,当时 Git 本身也刚出现不久,时光如梭,现在已经有 10 多年了。在这10年里,git-flow(指的是2010.01.05发布的文章中介绍的分支模型)在许多软件团队中变得非常流行,人们开始把它当作一种标准。但不幸的是,它也被当作一种教条或灵丹妙药。
在这些10年中,Git已经风靡全球,并且使用Git开发的最流行的软件正在向Web应用转变。Web应用通常是持续交付的,不会回滚,并且您不必支持在广泛应用中运行的多个软件版本。
当我在10年前写这篇博文时,并不是指的这类(持续交付的)软件。如果你的团队正在进行持续交付类软件的开发,我建议采用更简单的工作流程(比如GitHub flow ),而不是试图将git-flow硬塞到你的团队中。
然而,如果你正在构建有明确版本 的软件,或者需要支持多个软件版本,那么git-flow可能仍然适合你的团队,就像在过去的10年里适合其他人一样。
总之,永远记住并不存在万灵药。要考虑自己的实际情况,不要盲目崇拜某种方法。
正文 -- 写于2010.01.05
在这篇文章中,我介绍了我大约一年前为我的一些项目(包括工作和私人项目)引入的开发模型,而这个模型已经被证明非常成功。我不会谈论任何项目的细节,只会讨论分支策略和发布管理 。下面这个全景图涵盖了本文关于分支模型讨论的所有方面。
1. 为什么是git?
关于Git相对于集中式源代码控制系统的优缺点 的讨论,请参考网络上的相关内容。作为一名开发者,我更喜欢Git,它真正改变了开发者对合并和分支 的思考方式。从我过去使用的经典CVS/Subversion
角度来看,合并/分支相关的操作一直被认为有些可怕,而且是偶尔才会进行的操作。
但是在Git中,这些操作非常简单和廉价,并且它们被视为日常工作流程的核心部分之一 。例如,在CVS/Subversion
相关的书籍中,分支和合并通常在后面的章节中讨论(供高级用户使用),而在每本Git的书籍中,它已经在第3章中涵盖了基础知识。
由于其简单性和重复性,分支和合并不再是令人害怕的事情。版本控制工具应该在分支/合并方面提供帮助,而不仅仅是其他方面。
关于使用哪种工具的讨论到此为止,我们继续进入开发模型。我在这里要介绍的模型实际上不过是一组团队成员必须遵循的流程,以建立一个受控的软件开发过程。
2. 分散但集中
我们使用的存储库设置与这个分支模型很好地配合,它是具有一个中央"真实"存储库的设置。请注意,这个存储库是假想的中央存储库(因为Git是一种分布式版本控制系统,在技术层面上没有中央存储库的概念)。我们将把这个存储库称为origin,因为这个名称对所有Git用户来说都很熟悉。
每个开发者都从origin进行拉取和推送操作。但除了集中化的推拉关系外,每个开发者还可以从其他同行那里拉取更改以形成子团队。例如,在将工作进展 推送到origin之前,与两个或更多的开发者一起合作完成一个大型新功能可能会很有用。在上面的图中,有Alice和Bob、Alice和David、Clair和David这些子团队(正如上图所示)。 (笔者注:这段话作者是在说,使用git的团队可以做到大型新功能的开发由多个团队合作完成) 从技术上讲,这意味着Alice在Git中定义了一个名为bob的远程库,指向Bob的存储库,反之亦然。
3. main分支
这个开发模型受到了现有模型的很大启发。抽象出来的中央存储库origin包含两个持久化分支:
-
- master
-
- develop
origin/master分支对于每个Git用户来说应该是熟悉的。与master分支并行存在的另一个分支称为develop。两者的关系如下图所示:
我们认为origin/master是主分支,(原因是:)其中HEAD的源代码始终反映出一个可供生产使用的状态。
我们认为origin/develop是主分支,(原因是:)其中HEAD的源代码始终反映出下一个发布的最新开发变更的状态。有些人将其称为"集成分支"。
当develop分支中的源代码达到稳定点并准备好发布时,所有的变更都应该以某种方式合并回master,并标记一个发布号。这一过程如何具体完成将在后面进一步讨论。
因此,每次将变更合并回master时,将会产生一个新的生产发布。这一点应该严格遵守。我们可以使用Git钩子脚本,在master上提交时自动构建和部署我们的软件到生产服务器。
4. Supporting分支
除了主分支master和develop外,我们的开发模型还使用了各种支持分支,以帮助团队成员进行并行开发 ,方便追踪功能 ,为生产发布做准备 ,并在快速修复实 时生产问题时提供帮助。与主分支不同,这些分支总是有限的生命周期,因为它们最终将被删除。
这些supporting分支包括:
-
- feature(功能)分支
-
- release(发布)分支
-
- hotfix(热修复)分支
每个分支都有特定的目的,并受到严格的规则约束,规定了哪些分支可以作为其源分支,哪些分支必须作为其合并目标分支。接下来我们将逐一介绍它们。
4.1. Feature分支
- 可以从以下分支创建:
- develop
- 必须合并回:
- develop
- 分支命名约定:
- 除了master、develop、release-*或hotfix-*之外的任何名称
上述关系表示如下图:
功能分支(有时也称为主题分支)用于开发即将到来或遥远未来版本的新功能 。当开始开发一个功能时,此功能将被合并到哪个目标发布中可能还不清楚。功能分支的本质是它存在于功能开发期间,但最终将被合并 回develop(以确保新功能添加到即将发布的版本中)或被丢弃(在需求更改或者开发失败的情况下)。
功能分支通常只存在于开发者的存储库中,而不在origin中。
4.1.1. 创建一个feature分支
开始开发一个新功能时,从develop分支创建一个新的分支。
bash
$ git checkout -b myfeature develop # Switched to a new branch "myfeature"
在develop分支上合并一个已完成的功能。 已完成的功能可以合并到develop分支,以确保将它们添加到即将发布的版本中:
bash
$ 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
标志,则可以轻松完成。
是的,它会创建更多(空的)提交记录,但是收益远大于成本。
4.2. Release分支
- 可以从以下分支创建:
- develop
- 必须合并回:
- develop和master
- 分支命名约定:
- release-*
发布分支用于准备新的生产发布 。它们允许最后一刻的修补漏洞和完善细节。此外,它们还允许进行小的错误修复和为发布准备元数据(版本号、构建日期等)。通过在发布分支上完成所有这些工作,开发分支就可以为下一个重大发布接收新功能。
从develop分支创建一个新的发布分支的关键时刻是当develop(几乎)反映出新发布的期望状态时。至少此时应将所有目标为要构建的发布的功能合并到develop中。所有打算在未来版本中发布的功能都不可以被合并,它们必须等待发布分支创建后再合并。 (笔者注:release分支存在时,feature分支中的内容不可以再合并到develop分支中)
在release分支被创建之后,会给下一次的master分配一个版本号;这个版本号必须是在release分支被创建之后才决定的,develop分支对于这个版本号是无感的,也不应该参与到版本号的确定过程中来。也就是说,release分支完全决定了下一次master的版本号。
4.2.1 创建一个Release分支
发布分支是从develop分支创建的。例如,假设版本1.1.5是当前的生产发布,而我们即将发布一个重大版本。develop分支的状态已准备好用于"下一个发布",我们已经决定这将成为1.2版本(而不是1.1.6或2.0)。因此,我们创建一个发布分支,并给它一个反映新版本号的名称:
bash
$ 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脚本,用来改变记录软件版本号的文件。然后,提交commit后的版本号。
这个release分支可能会存在一段时间,直到可以正式发布该版本(指的是合并到master分支上去)。在此期间,可以在此分支上应用错误修复 (而不是在develop分支上)。严格禁止在此处添加大型新功能 。它们在生命周期结束之前必须合并到develop分支,因此需要等待下一个重大发布。
4.2.2 Release分支的结束
当release中的bug被修改完毕,正式发布之前需要做如下工作:
-
- 首先,将发布分支合并到master(因为每个在master上的提交都是一个新的发布,根据定义来说)。
-
- 接下来,在master上的那个提交必须被标记,以便将来可以轻松地参考这个历史版本。
-
- 最后,需要将在发布分支上进行的更改合并回develop,以便将来的发布也包含了这些错误修复。 (笔者注:简单来说就是合并到master和develop分支上去,并在合并到master分支之后打上tag)
Git操作如下:
bash
$ 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中执行如下操作:
bash
$ git checkout develop
# Switched to branch 'develop'
$ git merge --no-ff release-1.2
# Merge made by recursive.
# (Summary of changes)
这一步可能会导致合并冲突(尤其是因为我们更改了版本号)。如果发生冲突,请解决冲突并提交。
现在我们真正完成了,发布分支可以被删除,因为我们不再需要它:
bash
$ git branch -d release-1.2
# Deleted branch release-1.2 (was ff452fe).
4.3. Hotfix分支
- 可以从以下分支创建:
- master
- 必须合并回:
- develop和master
- 分支命名约定:
- hotfix-*
上述关系表示如下:
热修复分支与发布分支非常类似 ,它们都是为了准备新的生产发布,尽管是非计划性的 。 (笔者注:理想情况下,master在运行过程中不应该有bug,可是难免会出现意想不到的问题,这个时候并没有打算发布一个新版本的计划,所以需要使用hotfix分支紧急处理此bug) 热修复分支的存在是为了立即处理生产版本中不希望出现的问题。当需要立即解决生产版本中的关键错误时,可以从标记了生产版本的master分支上创建一个热修复分支。
hotfix处理的问题是:在团队成员在develop分支上继续开发新功能的同时,另一个人可能正在处理紧急的线上bug。
4.3.1 创建Hotfix分支
热修复分支是从master分支创建的。例如,假设版本1.2是当前正在运行并由于严重错误而引起麻烦的生产发布版本。但是develop上的更改尚不稳定。然后,我们可以创建一个热修复分支并开始解决问题:
bash
$ 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并将修复内容提交为一个或多个单独的提交。
bash
$ git commit -m "Fixed severe production problem"
# [hotfix-1.2.1 abbe5d6] Fixed severe production problem
# 5 files changed, 32 insertions(+), 17 deletions(-)
4.3.2 结束Hotfix分支
当完成修复后,需要将bug修复合并回master分支,并且还需要将其合并回develop分支,以确保该bug修复也包含在下一个发布中。这与完成发布分支的方式完全相似。
首先,更新master分支并为发布做标记。
bash
$ 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
接下来,也要将bug修复包含在develop分支中:
bash
$ git checkout develop
# Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
# Merge made by recursive.
# (Summary of changes)
这里的一个例外是,如果当前存在发布分支,那么热修复的更改需要合并到该发布分支中,而不是develop分支。将bug修复向后合并到发布分支最终会导致在发布分支完成时将bug修复合并到develop分支中。(如果develop分支中的工作立即需要此bug修复,并且不能等待发布分支完成,您也可以安全地将bug修复现在就合并到develop分支中。) (笔者注:这里作者在讲如果release分支存在的时候,线上出现了紧急bug,该如何做?给出的解决办法仍然是使用hotfix分支,当紧急bug解决之后hotfix无疑是要合并到master中去的,并且也要合并到release分支中,但是要不要合并到develop中去要视情况而定,情况一:此紧急bug如果影响到了develop的工作,那就将其合并到develop中;情况二:此紧急bug不影响现在develop的工作,那就可以将其合并到release中,反正release在销毁之前也要合并到develop中,无非是时间上靠后一些)
最后,删除临时分支:
bash
$ git branch -d hotfix-1.2.1
# Deleted branch hotfix-1.2.1 (was abbe5d6).
5. 总结
虽然这个分支模型没有什么特别新颖的地方,但它构建了一个优雅的心智模型,易于理解,并让团队成员形成对分支和发布过程的共同理解。
原文结束
二、 原文重点信息摘要
从原文中摘取出来的一些有用的信息,与git-flow相关或者无关,但是对于提升对git的认识十分有帮助的:
- 在
git-flow
分支模型发布十年之后,作者建议:如果开发的软件有明确的版本,则推荐使用git-flow
这种分支模型;如果软件是持续交付的则使用GitHub flow
分支模型。强调了不要教条。 - 作者对git-flow有着清晰的定义:它是一种分支和发布管理的策略(或者模型),用来约束软件开发过程。
- git-flow模型将分支划分成两种:无限生命周期的分支(master, develop)和有限生命周期的分支(feature, release, hotfix);前者又被称为main(主)分支 ,后者被称为supporting(辅助)分支。
- master分支作为主分支的原因是:master分支始终代表的是最新的生产环境 中的代码;而develop分支作为主分支的原因是:develop分支始终待变的是最新的开发环境下的代码。
- 有限生命周期分支的一个特点就是:最终会被删除。
- feature分支必须基于develop分支创建,生命周期结束之前必须合并回develop分支,或者被丢弃。
- 作者推荐在使用
git merge
的时候带上--no-ff
,因为这样做可以保留更多的提交信息,但是代价是会多一次空的提交记录。 - release分支必须基于develop分支创建,并且在生命周期结束之前必须合并回develop和master分支,注意这里是"和"不是"或"。
- hotfix分支必须基于【标记了生产版本的】master分支创建,并且在生命周期结束之前必须合并回develop和master分支,注意这里是"和"不是"或"。
- master分支线上运行的时候,develop分支上可能已经有了不稳定的新功能,所以无法通过develop创建新的release分支。hotfix分支存在的意义在于修复master分支上的紧急问题。
- 在整个过程中,只有master分支需要被tag,往mater分支上打tag的时机有两个,一个是release合并到master时,此时master应该增加一个大版本号 ,另外一个是hotfix合并到master时,这个时候master应该增加一个小版本号。
- 解决master上的bug使用的永远都是hotfix分支,当在hotfix分支上修复线上bug,修复完成之后将hotfix合并到master分支上,并tag;然后,如果此时release分支存在,就将hotfix合并到release分支上,如果release分支不存在,就将hotfix合并到develop上;如果release存在,并且develop因为此紧急bug无法继续开发,那就将hotfix同时合并到release和develop分支上。
三、git-flow设计目标
- 并行开发:允许多个团队成员同时进行独立的功能开发,而不会造成混乱和代码冲突。
- 版本控制:提供清晰的版本控制机制,使得每个发布版本都能够被标记和追踪。
- Bug修复和紧急处理:允许团队快速响应Bug修复和紧急处理的需求,并确保这些修复能够顺利地集成到主要分支中。
- 长期维护:支持对已发布版本的长期维护和修复,以满足用户需求。
四、git-flow全景图详细分析
如果您看原文译文比较吃力(很大一部分原因是我翻译的不好),那么在这个小节,我将通过多个维度详细分析文章中给出的git-flow分支模型的全景图,力求将该模型的工作流程解释清楚。
1. 从左向右理解全景图
- 上图从左往右的各个分支分别为feature, develop, release, hotfixes, master,其中红色框住的有feature,release,hotfixes,它们统称为support分支,特点是有限的生命周期 ,即使用完之后会被删除掉。蓝色框住的是develop和master,它们俩又称为main分支,特点是无限生命周期 ,又称为持久化分支。
- 在五个框顶部都各有一行小字,表示这个分支与bug 的关系。在feature和develop分支上标注no bug ,意思是对于这两个分支来说不存在bug一说,因为这个时候准确的叫法是不稳定的新功能 。在release分支上的小字是release bug ,用这样的记号是为了区别后面的urgent bug,release bug只存在于release分支上,并且由release分支负责fix,fix之后有三种处理方式:①修改只停留在release上;②修改被合并到develop上;③修改被合并到develop和release上,如下图所示:
- 第一种情况适用的场景:开发根本就没好好加班,release上的bug实在是太多了,光是小徐就分到了100个bug要改,今天上午才改了1个,这个时候将修改合并到develop上去没有意义,不说了,小徐要在release上修改剩下的99个了。
- 第二种情况对应的场景:release上爆出来一个bug,这个bug非常的致命,已经影响到了后续新功能的开发,需要优先解决,所以被解决之后立马会合并到develop分支中;或者,虽然小徐还有99个bug没改,但是小徐一向谨慎认真,为了尽量不影响其他同事,所以小徐每改一个就将代码合并到develop中一次,他不怕麻烦。
- 第三种情况对应的场景:小徐加了一周班,终于把100个bug解决了,可以从预上线release分支正式上线到master了,这个时候需要将release上修改的内容合并到master和develop上,保证master在tag的时候develop和master上的代码是相同的。
- 显然,第三种情况要慎之又慎,因为一旦release合并到master,release就会消失。
- 在master分支上的小字是urgent bug ,含义是紧急bug ,紧急bug的修复和release bug的修复方式不同,紧急bug只能通过新建分支的方式进行合并,原因是master分支上的内容必须通过merge进行修改,并且每次merge之后版本号都要发生变化 ,也就是说master的内容不可能和release分支上一样可以直接在本分支上修改。用来修改紧急bug的分支就是hotfix 分支,在hotfix分支上修改完之后会合并到develop和master分支上,并且改变master分支的小版本号,如下图所示:
遗憾的是,Vincent Driessen的文章中,对hotfix分支修改线上bug分两种情况进行了讨论,但是他给出的全景图只展示了一种。为了弥补这个遗憾,我将在下一个维度看这幅全景图的时候补上这种情况。
- 最后来看看feature和develop的关系。显然,feature只和develop有关系,和其他三个分支都没有关系,因为feature分支从develop分支来,又回到develop分支去。但是事实果真如此吗?
2. 从上到下理解全景图
根据release分支的存在与否,可以将全景图分成三个部分:①release分支创建之前;②release分支存在时;③release分支销毁后。
2.1 release分支创建之前
下图是release分支创建之前的部分:
这个时候,强调两件事情:
- 只有release创建之前的feature会进入到下一个版本,下图中的feature1就会进入1.0版本,而feature2就不会。
- release创建之前的hotfix只需要无脑合并到develop和master中就可以了。
2.2 release分支创建存在时
这个时候的情况表示为:
这个时候,develop专心于解决bug,强调两件事:
- feature不可以在release存在的时候合并到develop上去。
- 如果此时有urgent bug, 虽然还是创建hotfix分支解决,但是又和release出现之前有所不同,会出现两种情况,如下图所示: 情况一:hotfix的修改不会影响到目前develop分支的开发,所以先合并到release上,等release销毁前再同其他内容一起合并到develop上。
情况二:这个urgent bug非常关键,不仅影响到了master,还使develop目前的工作无法继续开展,所以需要将修改内容立刻合并到develop上去。
这种情况的分析见上文原文重点信息摘要 第12条。
2.3 release分支销毁之后
此时的情况如下图所示:
此时,强调三件事:①feature可以向develop中合并了;②develop和master中的内容再release销毁之后是相同的;③master的大版本号改变了。
3. 理解全景图中的环路
全景图中存在有6个环路,从左到右,从上到下依次来看:
- 红色:这个环路从develop出发,到feature,最后汇入develop,表示的含义是跨版本的新功能开发的过程。
- 蓝色:这个环路从develop出发,到feature,最后汇入develop,表示的含义是新版本中要包含的新功能的开发过程。
- 草绿色:这个环路从develop出发,到feature,最后汇入develop,但是创建的时候release分支也存在,实际上表示的含义是基于release中已经修改了bug之后的develop进行新功能的开发。
- 橘色:这个环路从develop出发,到release,最后汇入develop,表示的含义是release中修复了影响下个新功能开发的bug。
- 灰色:这个环路从develop出发,到release,最后汇入develop,表示的含义是融入新功能之后的软件版本升级。
- 黄色:这个环路从master出发,到release,最后汇入master,表示的含义是线上bug紧急修复。
五、使用git-flow
要使用Gitflow工作流程,需要先安装并配置git-flow
插件。以下是安装和使用git-flow
的一般步骤:
- 安装
git-flow
插件 :在vscode插件商城中搜索gitflow
并进行安装。 - 初始化仓库 :进入项目目录,并使用以下命令初始化Git仓库:
git init
。 - 启用
git-flow
:在项目目录中执行以下命令以启用git-flow
:git flow init
。gitflow
插件将引导您进行一些基本配置选择,如选择主分支名和临时分支名的前缀等。 - 开始开发新功能 :执行
git flow feature start <feature-name>
命令以从develop
分支创建一个新的功能分支。 - 完成功能开发 :在功能分支上进行代码编写、测试和提交。完成后,使用
git flow feature finish <feature-name>
命令将功能分支合并回develop
分支。 - 发布新版本 :执行
git flow release start <release-version>
命令以从develop
分支创建一个新的发布分支。在发布分支上进行测试、Bug修复等操作。完成后,使用git flow release finish <release-version>
命令将发布分支合并回develop
和master
分支,并打上版本标签。 - 紧急修复 :如果在已发布版本中发现紧急Bug,可以使用
git flow hotfix start <hotfix-name>
命令创建一个紧急修复分支。在修复分支上进行修复,并使用git flow hotfix finish <hotfix-name>
命令将其合并回develop
和master
分支。
六、git-flow相关指令
git flow feature publish <feature-name>
:将功能分支推送到远程仓库。git flow feature track <feature-name>
:从远程仓库跟踪某个功能分支。git flow feature pull <remote> <feature-name>
:从远程仓库拉取某个功能分支。git flow hotfix publish <hotfix-name>
:将修复分支推送到远程仓库。git flow hotfix track <hotfix-name>
:从远程仓库跟踪某个修复分支。git flow hotfix pull <remote> <hotfix-name>
:从远程仓库拉取某个修复分支。