浅谈基于 Git 的版本控制工作流

2021-12-07 17:55:06 浏览数 (2)

博主说:本文借鉴了很多「 DRPrincess」博主的文章内容,在此对其表示感谢。

为了更好的理解基于 Git 的版本控制工作流,我们不妨先来回答几个问题?

  • 什么是版本控制?
  • 什么是版本控制系统?
  • 为什么要做版本控制?
  • 为什么选择基于 Git 的版本控制?

要回答这些问题,最好的方法,莫过于回顾一下版本控制的发展历史。

因此,在本文中,我们就从「版本控制简史」出发,揭开「基于 Git 的版本控制工作流」的神秘面纱。

版本控制简史

版本控制,是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理。版本控制最主要的目的就是追踪文件的变更。它将什么时候、什么人更改了文件的什么内容等信息忠实地了记录下来。每一次文件的改变,文件的版本号都将增加。

除了记录版本变更外,版本控制的另一个重要功能是并行开发。软件开发往往是多人协同作业,版本控制可以有效地解决版本的同步以及不同开发者之间的开发通信问题,提高协同开发的效率。并行开发中最常见的不同版本软件的错误修正问题也可以通过版本控制中分支与合并的方法有效地解决。

但版本控制是目的而不是实现的工具,所以我们还需要通过某种工具来实现版本控制的目的,我们将这样的工具称之为 Version Controll System,缩写为 VCS,即版本控制系统。我们可以把一个版本控制系统简单的理解为一个“数据库”,在需要的时候,它可以帮我们完整地保存一个项目的快照。当我们需要查看一个之前的快照(称之为“版本”)时,版本控制系统可以显示出当前版本与上一个版本之间的所有改动的细节。

早在 1986 年 12 月,Dick Grune 就以 shell 脚本的形式发布了第一个流行的版本控制系统 CVS 的雏形。1989 年 4 月,Brian Berliner 设计了 CVS 并编写了代码。CVS 是一个 C/S 系统,其设计思路为,在一台服务器上建立一个源代码库,库里可以存放许多不同项目的源程序,由源代码库管理员统一管理这些源程序。每个用户在使用源代码库之前,首先要把源代码库里的项目文件下载到本地,然后用户可以在本地任意修改,最后用 CVS 命令进行提交,由 CVS 源代码库统一管理修改。这样,就好像只有一个人在修改文件一样,既避免了冲突,又可以做到跟踪文件变化等。

2000 年,CollabNet Inc 开发了 Subversion,缩写为 SVN,是一个开放源代码的版本控制系统,现已发展成了 Apache 基金会的项目。相对于 CVS,SVN 采用了分支管理系统,它的设计目标就是取代 CVS,但与 CVS 相同的是,SVN 也采用了 C/S 体系,项目的各种版本都存储在服务器上,程序开发人员首先将从服务器上获得一份项目的最新版本,并将其复制到本机,然后在此基础上,每个开发人员可以在自己的客户端进行独立的开发工作,并且可以随时将新代码提交给服务器。当然也可以通过更新操作获取服务器上的最新代码,从而保持与其他开发者所使用版本的一致性。

2005 年,Linux 之父 Linus Torvalds 为了帮助管理 Linux 内核开发而开发了一个开放源码的版本控制软件 Git。说起来,Git 的诞生还有一些戏剧性,Linus 最初使用 BitKeeper 作为版本控制系统,但在 2005 年,Andrew Tridgell 写了一个程序,可以连接 BitKeeper 的存储库,BitKeeper 著作权拥有者 Larry McVoy 认为 Andrew Tridgell 对 BitKeeper 内部使用的协议进行逆向工程,决定收回无偿使用 BitKeeper 的许可。Linux 内核开发团队与 BitMover 公司进行磋商,但无法解决他们之间的歧见。最终,Linus Torvalds 决定自行开发版本控制系统替代 BitKeeper,就用十天的时间编写出了 Git 的第一个版本。

如上所述,从 CVS、到 SVN、再到 Git 的变化,也是版本控制系统演进的过程。我们可以将 CVS、SVN 和 Git 大致分为两类:

  • 集中式版本控制系统:CVS 和 SVN 属于这一类。它们用集中管理的单一服务器,来保存所有文件修订版本,而协同工作的人们都通过客户端连到这台服务器,下载最新的代码或者是更新提交。但是如果中央服务器宕机了,那宕机的这一段时间,大家都无法更新提交更新,没法协同工作;更糟糕的情况下,如果中央服务器的数据没有做备份而且损坏,那么所有记录就都丢失了。
  • 分布式版本控制系统:Git 属于这一类。分布式版本控制系统最大的特点就是客户端并不只是提取最新版本的文件快照,而是把代码仓库完整地镜像下来,每个客户端其实都可以当做是中央服务器,当中央服务器数据损坏了,从任何一个本地客户端都可以重新恢复。而且我们可以随时随地提交代码,因为我们提交代码是提交到本地的服务器,所以效率大大提高。

现如今,Git 应该算是最受欢迎的版本控制工具了。例如现在世界上最大的两个代码托管平台 GitHub 和 GitLab,都是基于 Git 进行版本控制的;在国内,大家使用较多的中文代码托管平台 Gitee,也是基于 Git 进行版本控制的。由此可见,Git 作为版本控制工具,其速度快、分布式等特性,深受大家喜爱的。因此,了解基于 Git 的版本控制工作流,还是与我们有益的!

什么是工作流?

工作流,即工作流程。在项目开发过程中,多人协作是很常见的现象,每个人拉取自己分支、实现自己的业务逻辑,虽然各自在分支上互不干扰,但是我们总归需要把分支合并到一起,而且真实项目中涉及到很多问题,例如版本迭代,版本发布,bug 修复等,为了更好的管理代码,需要制定一个工作流程,这就是我们说的工作流,也有人叫它分支管理策略。

工作流不涉及任何命令,因为它就是一个规则,完全由开发者自定义,并且自行遵守,正所谓无规矩不成方圆,就是这个道理。其中,Git Flow 出现的最早,GitHub Flow 在 Git Flow 的基础上,做了一些优化,适用于持续版本的发布,而 GitLab Flow 出现的时间比较晚,所以综合了前面两种工作流的优点,制定而成的一种工作流。接下来,我们就详细了解这三个工作流。

Git Flow

Git Flow 是 Vincent Driessen 2010 年发布出来的他自己的分支管理模型,到现在为止,使用度非常高,可以说是一个非常成熟的 Git 工作流。Git Flow 的分支结构,按功能来说,可以分为 5 种分支,从 5 种分支的生命周期上,又可以分为长期分支和短期分支,或者更贴切的描述为,主要分支和辅助分支。

主要分支

在采用 Git Flow 工作流的项目中,代码的中央仓库会一直存在以下两个长期分支:

  • master
  • develop

其中,origin/master分支上的最新代码永远是版本发布状态,origin/develop分支则是最新的开发进度。当develop上的代码达到一个稳定的状态,可以发布版本的时候,develop上这些修改会以某种特别方式被合并到master分支上,然后标记上对应的版本标签。

辅助分支

除了主要分支,Git Flow 的开发模式还需要一系列的辅助分支,来帮助更好的并行开发,简化功能开发和问题修复。辅助分支不需要一直存在,仅当我们需要的时候,创建辅助分支就可以,当我们不需要的时候,也可以删除辅助分支。辅助分支分为以下几类:

  • Feature Branch
  • Release Branch
  • Hotfix Branch

Feature 分支用来做分模块功能开发,命名看开发者喜好,不要和其他类型的分支命名弄混淆就好,举个坏例子,命名为master就是一个非常不妥当的举动。模块完成之后,会合并到develop分支,然后删除自己。

Release 分支用来做版本发布的预发布分支,建议命名为release-xxx。例如在软件1.0.0版本的功能全部开发完成,提交测试之后,从develop检出release-1.0.0,测试中出现的小问题,在release分支进行修改提交,测试完毕准备发布的时候,代码会合并到masterdevelopmaster分支合并后会打上对应版本标签v1.0.0,合并后删除自己,这样做的好处是,在测试的时候,不影响下一个版本功能并行开发。

Hotfix 分支是用来做线上的紧急 bug 修复的分支,建议命名为hotfix-xxx。当线上某个版本出现了问题,将检出对应版本的代码,创建 Hotfix 分支,问题修复后,合并回masterdevelop,然后删除自己。这里注意,合并到master的时候,也要打上修复后的版本标签。

Merge 加上 --no-ff 参数

需要说明的是,Git Flow 的作者 Vincent Driessen 非常建议,合并分支的时候,加上--no-ff参数,这个参数的意思是不要选择 Fast-Forward 合并方式,而是策略合并,策略合并会让我们多一个合并提交。这样做的好处是保证一个非常清晰的提交历史,可以看到被合并分支的存在。下面是对比图,左侧是加上参数的,后者是普通的提交:

示意图

如上图所示,这是 Vincent Driessen 于 2010 年给出的 Git Flow 示意图,也是我们所有想要学习 Git Flow 的人都应该了解的一张图。图中画了 Git Flow 的五种分支,masterdevelopfeaturereleasehoxfixes,其中masterdevelop字体被加粗代表主要分支。master分支每合并一个分支,无论是hotfix还是release,都会打一个版本标签。通过箭头可以清楚的看到分支的开始和结束走向,例如feature分支从develop开始,最终合并回develophoxfixesmaster检出创建,最后合并回developmastermaster也打上了标签。

GitHub Flow

GitHub Flow 是世界上最大的代码托管平台,也称为“世界上最大的同性交友网站” GitHub 制定并使用的工作流,其是一个轻量级,基于分支的工作流,支持团队和项目的定期部署,由 Scott Chacon 在 2011 年 8月 31 号正式发布。

模型说明

在 GitHub Flow 中,只有一个长期分支master,而且master分支上的代码永远是可发布状态。一般来说,master会设置为受保护状态,只有有权限的人才能推送代码到master分支。以 GitHub 官方教程为准,遵循 GitHub Flow 需要经历以下几个步骤:

  • 创建分支
  • 添加提交
  • 提出 PR 请求
  • 讨论和评估你的代码
  • 部署
  • 合并

简单解释一下,其大致流程为:如果有新功能开发、版本迭代或者 bug 修复等需求,我们就从master分支上检出新的分支;将检出的新分支代码拉取到本地,在本地环境中进行编码,完成后,向远程新分支仓库推送代码;当我们需要反馈问题、取得帮助,或者想合并分支代码时,可以发起一个 Pull Request,常简称为 PR;当我们的代码通过项目维护者(有权限向master分支合并代码的人)讨论和评估后,就可以部署代码;待部署完成、验证通过后,代码就应该被合并到目标分支。

示意图

与 Git Flow 的示意图相比,GitHub Flow 的示意图可以称得上简单明了,因为 GitHub Flow 推荐做法就是只有一个主分支master,团队成员们的分支代码通过 PR 来合并到主分支上。实际上,上面的图仅是创建分支的示意图,但无论是创建分支还是添加提交、提出 PR 请求等,都不过是围绕着主分支按照上述的流程推进而已,如果大家感兴趣,可以通过「 深入理解 GitHub Flow」查看全部示意图。

特色功能

因为 GItHub Flow 的初衷就是用于在 GitHub 上进行团队协作,所以借助于 GitHub 平台的功能,GItHub Flow 中也引入了一些比较实用的工作流程,其中最出色的两个功能莫过于 PR 与问题追踪了。

PR

在工作流中引入 PR,是 GItHub Flow 的一个特色,它的用处并不仅仅是合并分支,还有以下功能:

  • 控制分支合并权限
  • 问题讨论或者寻求其他小伙伴们的帮助
  • Code Review

有了 PR 功能之后,相信我们再提交代码的时候,就得慎之又慎了。否则的话,代码写的太烂,就等着被喷吧!

问题追踪

在日常开发中,我们可能会用到很多第三方的开源库,如果使用过程中遇到了问题,我们可以去其 GitHub 仓库上搜索一下 Issue 列表,看看有没有人遇到过、项目维护者修复了没有,一般未解决的 Issue 是Open状态,已解决的 Issue 是Closed状态,这就是问题追踪。

如果你是一个项目维护者,除了标记 Issue 的开启和关闭,还可以给它标记上不同的标签。当提交的时候,如果提交信息中有fix #1等字段,可以自动关闭对应编号的 Issue。

GitLab Flow

这个工作流十分地年轻,是 GitLab 的 CEO Sytse Sijbrandij 在 2014 年 9月 29 正式发布出来的。因为出现的比前面两种工作流稍微晚一些,所以它有个非常大的优势,集百家之长,补百家之短。GitLab 既支持 Git Flow 的分支策略,也支持 GitHub Flow 的 PR 和问题追踪。

Git Flow & GitHub Flow 的瑕疵

当 Git Flow 出现后,它解决了之前项目管理的很让人头疼的分支管理,但是实际使用过程中,也暴露了很多问题:

  • 默认工作分支是develop,但是大部分版本管理工具默认分支都是master,开始的时候总是需要切换很麻烦。
  • Hotfix 和 Release 分支在需要版本快速迭代的项目中,几乎用不到,因为刚开发完就直接合并到master发版,出现问题develop就直接修复发布下个版本了。
  • Hotfix 和 Release 分支,一个从master创建,一个从develop创建,使用完毕,需要合并回developmaster。而且在实际项目管理中,很多开发者会忘记合并回develop或者master

GitHub Flow 的出现,非常大程度上简化了 Git Flow ,因为只有一个长期分支master,并且提供 GUI 操作工具,一定程度上避免了上述的几个问题,然而在一些实际问题面前,仅仅使用master分支显然有点力不从心,例如:

  • 版本的延迟发布(例如 iOS 应用审核到通过中间,可能也要在master上推送代码)
  • 不同环境的部署 (例如:测试环境,预发环境,正式环境)
  • 不同版本发布与修复 (是的,只有一个master分支真的不够用)

GitLab Flow 解决方案

为了解决上面提到的那些问题,GitLab Flow 给出了以下的解决方法。

版本的延迟发布 Prodution Branch

master分支不够,于是添加了一个prodution分支,专门用来发布版本。

不同环境的部署 Environment Branches & Upstream First

每个环境,都对应一个分支,例如下图中的pre-productionprodution分支都对应不同的环境,这个工作流模型比较适用服务端,测试环境,预发环境,正式环境,一个环境建一个分支。

这里要注意,代码合并的顺序,要按环境依次推送,确保代码被充分测试过,才会从上游分支合并到下游分支。除非是很紧急的情况,才允许跳过上游分支,直接合并到下游分支。这个被定义为一个规则,名字叫 “upstream first”,翻译过来是 “上游优先”。

版本发布分支 Release Branches & Upstream First

只有当对外发布软件的时候,才需要创建release分支。对外发布版本的记录是非常重要的,如果线上出现了一个问题,需要拿到问题出现对应版本的代码,才能准确定位问题。

在 Git Flow 中,版本记录是通过master上的tag来记录的。发现问题,创建hotfix分支,完成之后合并到masterdevelop

在 GitLab Flow 中,建议的做法是每一个稳定版本,都要从master分支拉出一个分支,比如2-3-stable2-4-stable等等。发现问题,就从对应版本分支创建修复分支,完成之后,先合并到master,然后才能再合并到release分支,遵循 “上游优先” 原则。

分支命名实践

现如今,越来越多的公司都会利用 GitLab 来搭建自己的代码托管平台,因此就以 GitLab Flow 为例,给出一个较好的分支命名实践。

如果存在多个环境,则为每个环境建立一个长期分支,可以命名为:

  • master,表示主分支,用于生产环境;
  • beta,表示内测分支,用于内测环境;
  • test,表示测试分支,用于测试环境。

在此,着重解释一下“内测环境”吧,实际上,内测环境应该是生产环境的一部分,是从生产环境隔离出来一部分用于内测,以保证线上回归测试时不影响真实的用户,因此两者共用一套生产数据库,仅是通过流量入口做区分。

接下来,根据不同的目的,为新拉取的分支取不同的名称:

  • 如果是开发需求,则从master拉取新分支,命名为feature-1xx-2xx-3xx,其中每一部分都有不同的含义,如
    • feature为固定词,表示这是一个新特性分支;
    • 1xx表示新特性的描述,为防止分支名过长,可以用缩写;
    • 2xx表示新分支创建的时间,格式为YYYYMMDD
    • 3xx表示新分支的创建者,姓名拼音或者英文名均可。

给出一个开发需求的分支命名示例,feature-SupportIM-20200711-chariesgavin,整个分支名称的含义就是,“某人在某时创建了某个功能的新特性分支”。开发、测试及代码合并的流程,大致如下:

  1. master分支拉取新的开发分支,进行编码,自测;
  2. 自测完成后,将代码合并到test分支,并且在test环境进行测试;
  3. test环境测试通过后,将代码合并到beta分支,并且在beta环境进行线上回归测试;
  4. beta环境测试通过后,将代码合并到master分支,并且将代码同步到生产环境;
  5. 生产环境上线后,就再从master分支打一个tag,其作用和稳定分支stable、发布分支release一样,用于回滚代码,命名为tag-xxx,其中xxx自定义即可,如版本号。

如果线上的代码一直没问题,自然是万事大吉,但难免会遇到各种各样的问题。这时,我们就遇到了另一种场景,即 BUG 修复。

  • 如果是 BUG 修复,则从master拉取新分支,命名为hotfix-1xx-2xx-3xx,其中每一部分都有不同的含义,如
    • hotfix为固定词,表示这是一个修复 BUG 的分支;
    • 1xx表示 BUG 的描述,为防止分支名过长,可以用缩写;
    • 2xx表示新分支创建的时间,格式为YYYYMMDD
    • 3xx表示新分支的创建者,姓名拼音或者英文名均可。

给出一个 BUG 修复分支命名示例,hotfix-messageRepeat-20200711-chariesgavin,整个分支名称的含义就是,“某人在某时创建了修复某个 BUG 的新分支”。理论上来说,BUG 修复的开发、测试及代码合并的流程应该和上述的开发需求是一致的,毕竟如果生产环境出现了问题,其他前置环境肯定也是跑不掉的,修复已知问题终归是值得提倡的;但在比较紧急的情况下,没有足够的时间让我们在不同的环境进行测试,该流程也是可以简化的,大致如下:

  1. master分支拉取新的开发分支,进行编码,自测;
  2. 自测完成后,将代码直接合并到beta分支,上线到内测环境进行测试;
  3. 内测环境通过后,再将代码合并到master分支,同步到生产环境,并从master分支打一个tag,备份稳定代码;
  4. 最后,再将修复 BUG 的代码同步到不同环境的稳定分支。

在这里,有一点可能让我们诟病,那就是分支名称太长了。确实,当我们想把更多的信息都揉进一个名称的时候,难免会遇到这样的问题!但如果是feature-1.0或者hotfix-20200710这类名称,可能开发周期稍微长一些的时候,大家都容易忘了这样的分支到底是谁创建的、实现了什么功能吧?因此,与之相比,我感觉分支名称稍微长一些还是可以接受的。

当然,就如 Git Flow 一样,任何工作流想要起作用,都需要我们认同它、打心里接受它,然后才能自觉的遵守其规范,毕竟,公司总不至于因为我们不遵守分支命名规范而开除我们吧?公司采取硬性规定的另算。但这些工作流之所以能得到大家广泛的认同,并且流传之广,自然还是尤其魅力的,或多或少还是能够提高团队协作效率的。采取与否,您来决定!

参考资料

  • Git 工作流程
  • A successful Git branching model
  • GitHub Flow
  • 深入理解 GitHub Flow
  • Introduction to GitLab Flow
  • Git三大特色之WorkFlow(工作流)

0 人点赞