工程化中的版本管理
在带领团队负责一个项目的时候 ,我们经常面临一个头疼的问题:代码如何进行管理?尤其是随着项目规模和复杂性的增加,如何高效的组织、协调和跟踪代码变得至关重要。git作为现代工程化中的瑞士军刀为高效的团队协作和精准的代码管理提供了坚实基础。
版本管理是项目团队面临的重要问题,随着项目规模和复杂性的增加,需要有效地组织、协调和跟踪代码。预发布标签是一种包含字母和数字的标识符,用于版本控制。对于前端项目,可以使用单仓库管理代码,如使用Lerna管理版本依赖和发布。此外,还可以参考Git、Pro Git等资源。在版本定义方面,Node.js和Yarn都有相应的规范,而NPM可以使用node-semver和semver规范。
分支策略
常见的分支策略有四种:git-flow、GitHub flow、GitLab flow、OneFlow。
git-flow
git-flow是2010年由大佬Vincent Driessen在他的一篇博客里提出来的。它定义了一整套完善的基于Git分支模型的框架,结合了版本发布的研发流程,适合管理具有明确版本变化或者需要支持多个版本的项目。下面是流程图:
优点:分支管理严格,代码合并清晰,支持构建多个版本。
缺点:分支流程过多反而也是一种缺点。理想丰满、现实骨干。实际运行起来比较麻烦。
GitHub flow
很多项目和网站是“持续发布”,代码一但有变动,就部署一次。只需要一个最新的版本,这时git-flow工作流就不太适用了。GitHub fow于2021年创建,是git-flow的简化版,专门配合"持续发布"、是一个轻量级的、基于分支的工作流。是Github.com使用的工作流。 遵循的6条原则:
master
分支永远是可以随时部署发布的- 需求新增基于master,并创建一个语义化的分支
- 定期推送本地分支到远程
- 合并
master
需要提PR - PR一旦经过code review无误既可合并到
master
分支 master
分支一旦接收到合并请求就可以立即触发自动构建发布。
大致流程:
- 第一步:从
master
分支拉出新分支,不区分功能或者补丁分支 - 第二步:新分支开发完成后,向
master
发起一个PR。 - 第三步:相关人员对PR进行评审、讨论、修改。
- 第四步:你的PR被接受合并进
master
分支。
优点:分支和流程足够简单,适用于持续发布场景。 缺点:不适用多版本产品线场景。还有一点就是要保证高质量对开发者素质比较高,代码贡献者和code review人员素质没那么高,质量就无法得到保证。
Gitlab flow
Gitlab flow是GitLab在2014年提出的11条最佳实践。是git-flow与Github flow的综合。吸取了两者的优点增加了环境分支,并且对持续发布和版本发布都提出了相应的准则。是Gitlab.com的做法。
其实Gitlab flow和git-flow在开发流程上的区别并不大,也是使用特性分支,而不是在主分支上提交。将PR(pull request)改成了MR(merge request),引入和pre-productionhe分支和production分支。
Gitlab flow最大的原则是upsteam first(上游优先)。只有一个主分支master
,他是所有分支的上游。代码只能由上游往下游合并。
优点:git提交历史清晰、支持构建多个版本
缺点:其实感觉和git-flow没有什么两样。
OneFlow
感觉就是git-flow的简化版,除了 develop 开发分支和最新发布 master 分支,其余皆是临时分支,一旦开发完成即可。另一种说法就是每一次的版本更新都是基于上一个版本产生的。详细地址。
commit message
commit message是我们每次开发都要写的,写好commit message不仅更方便的快速浏览历史信息,快速查找,还可以直接从commit message生成change log。所以一定要有一个统一规范的格式。
当前主流的规范是基于Angular团队的Conventional Commits。
其他
git hook
钩子:提前定义好的函数,在特定的时间点被调用,以实现特定的功能。
git提供了客户端
和服务端
的钩子。例如常用的客户端钩子pre-commit
、commit-msg
。我们可以使用这些钩子完成一些自动化场景的校验功能,如代码格式校验、commit message校验等。
Husky
官网。Husky是一个git客户端hook工具。git hooks默认存储在.git/hooks目录下,但是这个目录又是不受版本控制的。虽然可以设置hookPath来指定存放hooks的路径,但是有简单专门的Husky为啥不用它呢。Husky会专门创建一个.husky
文件来存储配置。
tag
git可以给仓库历史中的某一个提交打上标签,以示重要。虽然commit有规范的message了,但是我们可以使用git tag并用SemVer(语义化版本控制规范)规范标签的命令,方便在日后维护的时候复查。也可以通过标签大致了解当前项目的迭代情况。
SemVer
以前端npm包版本进行介绍,SemVer具体查看地址。
BNF范式(巴克斯范式)
一种用简单符号定义关系的规范。 规范:
::=
:表示定义""
:双引号里的内容表示字符<>
:尖括号里的内容表示必须[]
:方括号内表示可选{}
:大括号内的内容为可重复0-无数次|
:表示左右任选一项
例如:SemVer中定义的合法的语义化版本号
<valid semver> ::= <version core>
| <version core> "-" <pre-release>
| <version core> "+" <build>
| <version core> "-" <pre-release> "+" <build>
<version core> ::= <major> "." <minor> "." <patch>
翻译成人话:语义版本号由一下四种形式之一构成
- - 这是最基本的形式,只包含主版本号、次版本号和补丁版本号,形如 "X.Y.Z"。
- "-" - 在基本形式的基础上,添加了一个预发布标签。预发布标签是一个包含字母和数字的标识符,形如 "alpha","beta" 等。
- "+" - 在基本形式的基础上,添加了一个构建标签。构建标签是一个包含字母、数字和符号的标识符,形如 "build1","build2" 等。
- "-" "+" - 在预发布标签的基础上,又添加了一个构建标签。这种形式允许同时存在预发布标签和构建标签。
合法的语义化版本
参考BNF范式中SemVer的定义。
版本号
major
:即X(主版本号),有不兼容的 API 更改时升级主版本号minor
:即Y(次版本号),有向后兼容的方式添加功能时升级次版本号patch
:即Z(修订版本号),有向后兼容的缺陷修复时升级修订版本号
先行版本号
标有先行版本号表示这个版本并非稳定而且可能无法满足预期的兼容性需求。其实就是测试版。带先行版本号的都比只有版本号的小。例如:1.0.0-alpha < 1.0.0
alpha
:内测版beta
:公测版rc
:正式版的候选版
范围定义
在前端项目的package.json
文件中经常看到版本号前有~
、^
等字符。这就是控制版本号范围的标志符。
详细地址:package.json版本定义、npm的node-semver版本定义
^
:锁死版本号中最左非零数字,后面的都可以更新。^0.0.1
匹配不到0.0.2
、^1.2.3-beta.2
能匹配到1.2.3-beta.4
但是不能匹配到1.2.4-beta.2
。^1.2.3
不能匹配到1.2.4-beta.4
~
:如果指定了次要版本,则允许更改补丁版本,如果没有则会允许更小级别的更改。~1.2.3-beta.2
能匹配到1.2.3-beta.4
匹配不到1.2.4.beta.2
- 还有
-
、>
、>=
、<
、<=
、latest
、||
前端常用包管理器:npm、cnpm、yarn、pnpm
推荐使用pnpm。
npm
nodejs自带的包管理器,基于语义版本控制的思想设计的。使用package.json
文件来声明版本,用package-lock.json
文件来锁定版本。
cnpm
Alibaba出品的包管理器。只是和npm的仓库不同。因为国内网络的问题,npm包从国外官方服务器下载慢,于是建了一个国内镜像仓库,每几分钟从国外官方服务器同步一下。
yarn
和npm很像,npm仓库上的包也会同步到yarn仓库中。中文官网 优点:
- 安装快:yarn会缓存每个下载过的包,再次使用时不用重复下载。同时利用并行下载最大化资源利用率。
- 安全:执行代码之前通过算法校验安装包的完整性。
yarn命令和npm命令稍微有些不同。例如常用的
- npm install -> yarn add
- npm uninstall -> yarn remove
- npm update -> yarn upgrade
- 执行脚本省去
run
操作符
pnpm
后起之秀。pnpm的仓库源使用的是npm的源。继承了yarn的优点。主要优点在于节省磁盘空间、安装速度快、创建一个非扁平的node_modules目录。利用硬链接和符号链接来避免复制所有本地缓存源文件。中文官网
其他
- NPM仓库不允许发布相同版本号的包。这是因为版本控制的一个重要原则是唯一性,同一个版本的包应该只有一个,以确保依赖的稳定性和一致性。
- 虽然Sonatype Nexus(常用maven、npm私服)支持npm包的同版本的更新,但是还是不建议更新,非要使用同一版本可以通知使用者使用
npm update [<pkg...>]
命令更新更新的包。
Maven
maven是一个流行的Java项目管理工具,可以对Java项目进行构建、管理。下面聊聊maven中的版本控制相关知识。
版本管理
由groupId
、artifactId
和version
这三个变量确定一个唯一id。
maven的版本号也是采用的通用的major
、minor
、patch
三级来控制的。maven提供了SNAPSHOT
概念。每次都会更新带有SNAPSHOT
后缀的包。发布稳定版本不要这个后缀。
依赖管理
当我们需要一个第三方包时,直接在pom.xml中配置一个依赖就可以了。例如我们项目需要a包,a包中需要b包。Maven会自动导入a包和b包。
依赖关系
Maven定义了compile
、test
、runtime
和provided
、system
这五种依赖范围。
范围 | 说明 | 例子 |
---|---|---|
compile | 编译时需要(默认范围) | 绝大部分的包都是这个范围,如spring-boot-starter-web |
test | 编译测试需要 | junit |
runtime | 编译不需要,运行时需要 | 数据库驱动mysql-connector-j 、spring-boot-devtools 等 |
provided | 编译时需要用到,但运行时由JDK或某个服务器提供 | servlet-api |
system | 本地路径 | 一般不用,有私服有本地仓库干嘛用本地系统路径下的包 |
我的实践
下面讲讲我们自己的工程化版本管理。我们使用gogs搭建远程仓库,采用git-flow分支策略,使用Sonatype Nexus搭建maven、npm私服。 分支:master、develop、feature/、release/,hotfix/* 根据生命周期区分
- 主分支(时间长):master、develop
- 临时分支:feature/、release/,hotfix/*
根据用途区分
- 发布/预发布分支:master、release/*
- 开发分支:develop
- 功能分支:feature/*
- 热修复分支:hotfix/*
分支用途
- master:最稳定分支,跟仓库中包的最新版本保持一致。
- develop分支:作为开发分支,所有最新的功能都在该分支下进行开发,develop是所有分支中功能最全,代码最新的一个分支。拥有最新的、已经验证过的 feature / bugfix,Pull Request 的**目标合入分支。**与master分支一样也不允许直接进行修改和提交。只能通过发起pr合并进来。
- feature/*:命名规则feature/功能名称,作为新功能的开发分支,该分支从 develop 创建,开发完毕之后需要重新合并到 develop。在该功能开发完成之后拉取最新的develop分支的代码合并到本分支,然后进行commit。
- release/*:
- 正式发布分支,命名规则为 release/x.y.z,一般从 develop 拉出来进行发布,x.y.z 为待发布的版本号。打
vx.y.z
标签。例如:release/1.0.1。发布完成后合并到develop、master分支 - beta 发布分支,命名规则为 release/x.y.z-beta,打
vx.y.z-beta(.\d+)?
标签,发布beta 版本。例如:release/1.0.1-beta
- 正式发布分支,命名规则为 release/x.y.z,一般从 develop 拉出来进行发布,x.y.z 为待发布的版本号。打
验证通过后,因为 beta 发布分支上会存在无用的 commit(比如 lerna 修改 package.json 这种),所以不直接 PR 到 develop,而是从 develop 拉分支,从 beta 发布分支 cherry pick 有用的 commit 到新分支,然后 PR 到 develop。
- feature分支,特性分支。通常从develop分支拉出,每个新特性的开发对应一个特性分支,用于开发人员提交代码并进行自测。自测完成后,会将feature分支的代码合并至develop分支,进入下一个release。
- hotfix分支,热修复分支,生产环境发现bug时创建的临时分支,问题验证通过之后合并到develop和master分支。
前端大型项目就用monorepo仓库管理代码,使用pnpm/lerna管理版本依赖和发版,
链接:
转载自:https://juejin.cn/post/7276350321093853238