Gitflow分支管理模型

发布时间 2023-08-18 18:40:04作者: Youbin

1 前言

Git flow 是一个Git分支管理模型,由 Vincent Driessen 于2010年发布在其个人网站的一篇博文中《A successful Git branching model》,该模型适用于多版本管理的项目,能够有效的促进团队成员之间的协作,提升代码的清晰度。

2 概览

Git flow 工作流程如下:

如上图,Git flow 流程中包含以下几个分支:

  • 主分支(master branch)
  • 热修复分支(hotfix branches)
  • 发布分支(release branches)
  • 开发分支(develop branch)
  • 功能分支(feature branches)

根据分支赋予的职能,我们又可以把上述分支分为两大类主要分支支持分支,下面我们将对这两类分支作出详细说明。

3 主要分支

主要分支是长期存在的分支,其生命周期贯穿项目的整个过程,包含:

  • 主分支(master branch)
    Git的默认分支,也是所有Git用户都熟悉的一个分支,我们常常把它称作主分支或者说生产分支。(注:现有些Git仓库管理系统把默认分支名改成main,因此main分支和这里所说的master是同义词)
  • 开发分支(develop branch)
    根据master创建得到,是与master并行的一条分支,我们常常把它称作开发分支。
    image

Git flow 流程中origin/master作为一个主要分支,其最新代码,即在Git中该分支处于HEAD位置的代码,它往往反应的是生产就绪状态的代码。

Git flow 流程中origin/develop作为一个主要分支,它与master是有明显区别的,其最新代码,即在Git中该分支处于HEAD位置的代码,它常常反应的是项目在下一次发布前的最新交付的变更状态。当develop分支代码开发过程中到达了一个比较稳定阶段,即项目完成阶段目标功能,并且测试通过之后,我们必须把所有变更合并到master分支并打上版本标签,关于合并的详细过程我们将在后面进行讨论说明。

4 支持分支

Git flow 流程中支持分支被设计的目的是协助项目组成员间更好的追踪系统功能、为生产环境产品的发布做准备、以及快速修复生产环境产品的问题。不同于主要分支的长期存在,支持分支在项目中存在的时间是有限的,通常完成其责任后就会被删除。
Git flow 流程使用到的支持分支包括:

  • 功能分支(feature branches)
  • 发布分支(release branches)
  • 热修复分支(hotfix branches)

以上每一个分支被设计出来都有其特定目的,并且受到严格的规则约束,比如说哪一个是它的来源分支,哪一个是它最终要合并的目标分支。

4.1 功能分支

image
来源分支:
开发分支(develop)
目标合并分支:
开发分支(develop)
命名约定:
feature-*,其中*需通俗易懂的表明当前所开发的是什么功能。

功能分支(有时候也可能被称为主题分支)用于为紧接着的或者未来将要发布的版本开发新的功能特性。当我们开始开发一个新功能的时候,对于它是否要合并到未来的某个发布版是不确定的,也许就在不久后进行发布,也许要等到下一个或者再下一个,当然了也可能因为功能不符合预期目标而被废弃。总而言之,功能分支只要还在开发,那么它就应该存在,直到它被合并到开发分支或者被抛弃。

功能分支通常只应该存在于开发者的本地仓库,而不是origin仓库,但是这不是必须的,比如说多人合作开发一个功能时,我们需要通过origin进行分发。

1 创建功能分支
当我们要开发一个新的功能A时,我们需要以开发分支作为源,创建一个新的分支feature-A

$ git checkout -b feature-A develop
// 切换到“feature-A”分支

2 将完成开发的功能合并到开发分支
当开发完成一个功能后,我们需要把这些功能特性合并到开发分支,以确保它能够添加后续发布的版本中去。

$ git checkout develop
// 切换到“develop”分支
$ git merge --no-ff feature-A
// 完成合并操作
$ git branch -d feature-A
// 删除本地的“feature-A”分支,如果远端origin中也存在该分支的话也需要删除
$ git push origin develop
// 将本地的“develop”变动,推动到远端origin仓库

上述--no-ff标记使得merge操作每次都会创建一个新的提交,不管merge操作是否能够以快进的方式执行(fast-forward)。这么做的目的是为了防止合并后丢失关于该功能分支的历史信息(即无法区分提交是develop提交的还是feature-A分支中的提交),同时把feature-A的所有提交组织成一个分组。下面给出一个对比图:
image

如果没有使用--no-ff标记的话,我们是很难通过历史信息看出哪些提交才是完成某个功能的组成部分(开发某个功能的过程中可能有多次提交代码),因此你需要逐条阅读日志信息来区分这个提交的作用。这时候,如果我们要回滚整个(一组提交)功能是非常困难的,然而,假如我们是使用--no-ff标记进行合并的话,那么回滚操作将相当简单。

当然了,使用--no-ff标记合并代码会产生很多感觉没什么用的空提交,但是,相对与它有这样的缺点,使用它的优点更为明显,你说是不是。

4.2 发布分支

  • 来源分支:
    开发分支(develop)
  • 目标合并分支:
    开发分支(develop)主分支(master)
  • 命名约定:
    release-*,其中*可以使用版本号,如:release-V1.0.0

发布分支的作用主要是为了发布下一个版本做准备,在这里,你可以修复一些小的BUG,同时为即将发布的版本准备一些元数据(版本号、构建日期等等)。

我们什么时候可以从开发分支分离出来一个发布分支

开发分支几乎反应我们对下一个版本的期望,并且所有目标功能都已经被合并到开发分支,这时候,我们认为分离出来一个发布分支是合适的。

1 创建发布分支
发布分支开发分支创建而来。假设,我们最新发布的产品版本为V1.1.5,当前开发分支已经处于可以发布下一个版本的状态,我们决定要发布下一个版本,该版本就按照公司版本号命名规则将其命名为V1.2.0。我们通过开发分支分离出一个发布分支,该分支就叫做release-V1.2.0(名称需能反映版本信息):

$ git checkout -b release-V1.2.0 devlop
// 切换到新分支“release-V1.2.0”
$ ./bump-version.sh V1.2.0
// 把项目版本号修改为“V1.2.0”
$ git commit -a -m "更改版本号为V1.2.0"
// 把更改版本号后有变化的文件进行提交

我们创建了一个新的发布分支release-V1.2.0,并且切换到该分支空间,紧接着,我们修改项目版本号,然后把所有变更文件进行提交。这里的bump-version.sh是一个虚构的脚本文件,表示修改版本号的所有操作,当然了我们也可以通过手工逐个更改。

分支release-V1.2.0可以存在一段时间,直到我们最终确定推出这个版本的产品,在这段时间内,我们可以在这个分支上修复一些BUG(而不是在开发分支),但是严禁添加新的大型功能(新功能你可以考虑在未来的版本上发布)。

2 完成发布分支
当发布分支release-V1.2.0已经处于可以推出最终发布版的状态时,我们需要做一些事情:首先,将发布分支release-V1.2.0合并到master主分支(需要注意的是,master主分支的每一次提交都是一个新的发布版),然后打上一个版本号标签V1.2.0方便以后追溯;最后,把发布分支release-V1.2.0合并到develop开发分支,这样你在release-V1.2.0上所做的所有更改,如修复了BUG,优化了一些内容等等都能反映到develop开发分支,最终这些变更都会包含在你未来要发布的新版本当中。

第一步,合并release-V1.2.0master分支:

$ git checkout master
// 切换到“master”分支
$ git merge --no-ff release-V1.2.0
// 完成合并
$ git tag -a V1.2.0
// 打上标签

上述操作完成后就表示发布完成啦,我们也为本次发布打上了一个版本号标签V1.2.0以供未来追溯。

第二步,合并release-V1.2.0develop分支:

$ git checkout develop
// 切换到“develop”分支
$ git merge --no-ff release-V1.2.0
// 完成合并

这一步可能会产生一些合并冲突,因为我们更改了版本号,也可能修复了一些BUG等等,但是没关系,有冲突就解决冲突,然后将它提交即可。

第三步,删除release-V1.2.0分支

$ git branch -d release-V1.2.0
// 删除分支

我们发布新版本后,release-V1.2.0分支就已经完成它存在的意义了,这时我们不再需要它(当然你也可以保留一段时间),直接删除它即可。

4.3 热修复分支

  • 来源分支:
    master
  • 目标合并分支:
    masterdevelop
  • 命名约定:
    hotfix-*

image
补丁分支和发布分支类似,它们本质上都是为发布一个新发行版做准备,不同之处在于发布分支是计划性质的,而补丁分支属于突发状况,是不可预知的。它存在的意义就是为了快速修复运行在生产环境中某个版本存在的问题,每当线上环境的发行版出现重大BUG,并且该BUG不能拖到后续发布的版本中进行修复时,我们需要从master分支中找到跟出现问题的生产版本号匹配的标签,然后从该标签处分离出一个补丁分支。这样做的目的是使得团队的其他人员可以继续在develop上进行各种功能的开发,然后另外一些人可以立即着手生产版本BUG的修复。

1 创建补丁分支
补丁分支由master分支创建而来。我们假设V1.2.0是生产环境中正在运行的版本,现在出现了一个严重BUG,如果该BUG不能及时修复的话可能会导致系统服务中断。这时我们在develop分支开发的功能或者变更又没有达到可以发布新版的状态,因此我们不得不从master中分离出来一个补丁分支,然后在该分支上对BUG进行修复:

$ git checkout -b hotfix-V1.2.1 master
// 切换到分支“hotfix-V1.2.1”
$ /bump-version.sh V1.2.1
// 更新版本号
$ git commit -a -m "更新版本号为V1.2.0"
// 提交版本更新修改

master中分离出补丁分支后不要忘了修改版本号并提交,然后修复BUG,最终把修复后的内容提交。

$ git commit -m "修复严重的生产环境BUG"

2 完成补丁分支
Bug修复完成后我们需要把补丁分支修改的内容合并到master分支,同时也要将这些内容合并到develop分支以确保未来发布的版本不再出现这个问题。
这里的操作逻辑基本上和关闭发布分支的操作逻辑类似:
第一步,将hotfix-V1.2.1合并到master分支

$ git checkout master
// 切换到“master”分支
$ git merge --no-ff hotfix-V1.2.1
// 合并“hotfix-V1.2.1”分支
$ git tag -a V1.2.1
// 打上版本号标签“V1.2.1”

第二步,将hotfix-V1.2.1合并到develop分支

$ git checkout develop
// 切换到“develop”分支
$ git merge --no-ff hotfix-V1.2.1
// 合并“hotfix-V1.2.1”分支

关于第二步,这里有个例外情况,如果说这个时候发布分支还存在的话,我们需要把补丁分支修正的内容合并到发布分支而不是develop分支。因为在我们完成并关闭发布分支的时候,这些内容最终都会被合并到develop分支当中(当然了,如果develop分支也急需修正该问题,并且等不及发布分支关闭,你也可以把它先合并到develop分支)。

第三步,删除hotfix-V1.2.1分支

$ git branch -d hotfix-V1.2.1
// 删除“hotfix-V1.2.1”分支

5 总结

以上便是Gitflow分支模型所要讲述的东西,它对于我们日常使用Git进行版本管理提供了一个很好的参考作用,它形成了一个易于理解的优雅的心智模型,并允许团队成员对分支和发布过程形成共同的理解。