试图理解语义化版本和约定式提交
前言
最近写了不少新奇的小东西开源到了Github上,比如浏览器扩展或者一些运行起来的网页应用。在写浏览器扩展的过程中,我在生成 release 这一步时用 Github Action 创建了一条流水线,用于监听 manifest.json
中 version 属性值的变化,每次变化就根据 version 的值生成一个新版本的 release。也就在这时,我开始思考一个问题:我应该如何规范我的版本号?
因为我此前并未接触过相关的规范,此前参与过的项目也没有明确的提交守则与版本号命名规范,对于相关的规范我只在一些开源项目中看到过,以及偶然听人提起过,并没有一个较为完整的认识,所以我决定查询一些资料去学习一下如何规范一个项目的版本号。
所幸我在让AI帮我规范提交信息时,它提到了“约定式提交”这个概念,而“约定式提交”与“语义化版本”有着密切的关系,所以我先分别了解了两者的概念,然后了解了它们之间是如何相互对应的。
语义化版本
软件的版本号是一个软件的重要标识,而许多软件的版本号命名都遵循着一套规范,“语义化版本”就是其中的一种。
什么是语义化版本
首先来看语义化版本的概念:
语义化版本是一种用于描述软件版本的方式,它使用一组规则来描述版本的变化。语义化版本的规则如下:
- 版本号由三个部分组成:主版本号、次版本号、修订号,格式为:
主版本号.次版本号.修订号
- 版本号的递增规则如下:
- 主版本号:当软件发生了不兼容的 API 更改时,主版本号递增,次版本号和修订号重置为 0
- 次版本号:当软件添加了新功能,但向后兼容时,次版本号递增,修订号重置为 0
- 修订号:当软件进行了 bug 修复,向后兼容时,修订号递增
- 先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
(PS:向后兼容指的是,用户import你的软件包,用了里面的函数,这个软件包更新时,用户无需做任何修改,预期结果也不受影响)
举个例子
让我们以一个例子来理解这种规则:
假设我们有一个版本号为 1.0.0
的软件包供外部软件调用,这个软件包中有一个函数example(a: string, b: number): string {}
,以下三种情况分别对应了修订号、次版本号、主版本号的修改:
- example 函数体存在bug,我们在确保修复了这个bug的情况下未在软件包中引入新的API,该函数依旧能够实现预期的功能,返回预期的结果。这时仅进行了bug修复,未添加新功能,未发生不兼容的API修改,版本号变为
1.0.1
- 软件包中新增了一种example 函数的函数重载,它的实现函数变成
example(a: string, b: number, c?: boolean): string {}
,用户原有的函数调用依旧返回预期结果,但是当用户调用 example 函数传入c
参数时,返回值会发生变化,这时我们新增了一个函数重载,未发生不兼容的API修改,版本号变为1.1.0
- example 函数新增了一个参数,变成
example(a: string, b: number, c: string): string {}
,用户原有的函数调用会报错提示缺少参数。这时我们在example函数中新增了一个参数,发生了不兼容的API修改,版本号变为2.0.0
可以看到,语义化版本的规则是非常清晰的,它规定了版本号的递增规则,以及当软件发生不兼容的API修改时,版本号的变化。这也是语义化版本的最主要的作用,它可以确保用户在使用某一主版本的软件包时,已编写的代码在该软件包仅发生次版本号会修订版本号更新时,不会出现错误。
开发初始阶段的版本控制
软件的主版本号为零(0.y.z)是一种特殊状态,这意味着该软件处于开发初始阶段,一切都可能随时被改变。因此在初始开发阶段,进行版本控制最简单的做法是以 0.1.0 作为初始化开发版本,并在后续的每次发行时递增次版本号。
如何判断发布 1.0.0 版本的时机
当软件被部署到生产环境中开始被正式使用,它应该已经达到了 1.0.0 版。此时的软件已经有了一系列稳定的API在被使用者依赖,并开始需要考虑向后兼容的问题。
版本的优先级
对于不同版本号的软件,如何排序决定它们的优先级呢?在判断优先级时,我们必须把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较(版本编译信息不在这份比较的列表中)。随后,按照这三条规则进行判断:
- 对于两个正式版本,需要依据它们的主版本号、次版本号、修订号依次进行比较,直到找到第一个不同的数字,该数字较大的版本号优先级较高(例如1.0.0 < 1.0.1 < 1.1.0 < 1.1.1 < 2.0.0)
- 对于具有相同主版本号、次版本号、修订号的正式版本和先行版本,正式版本的优先级总是高于先行版本(例如1.0.0-alpha < 1.0.0)
- 对于两个先行版本,如果它们的主版本号、次版本号、修订号完全相同,其优先级必须通过由左到右的每个被句点分隔的标识符来比较,直到找到一个差异值后,按照如下规则决定:
- 只有数字的标识符以数值高低比较。(例如1.0.0-alpha.1<1.0.0-alpha.5)
- 有字母或连接号时则逐字以 ASCII 的排序来比较。(例如1.0.0-alpha.beta<1.0.0-alpha.rc)
- 数字的标识符比非数字的标识符优先级低。(例如1.0.0-0<1.0.0-alpha)
- 若开头的标识符都相同时,栏位比较多的先行版本号优先级比较高。(例如1.0.0-alpha.1<1.0.0-alpha.1.0)
再举一个最直观的例子: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0 < 1.0.1 < 1.1.0 < 1.1.1 < 2.0.0 你可以按照上述的三条规则,一一它们的优先级排列,相信你会对版本的优先级有更加深刻的认识。
约定式提交
最开始时我提到过,“约定式提交”与“语义化版本”有着密切的关系,这是因为在进行源代码管理时,我们通常会使用版本控制工具,如Git,在使用git commit
提交代码时,如果我们按照“约定式提交”的规范填写提交信息,我们可以方便地将一组特定的提交发布为一个新版本,而这个新版本可以按照对应的提交类型确定版本号如何更新。
在许多开源项目的contribute指南中,都有约定式提交的规范,比如在 arco-design的CONTRIBUTING.zh-CN.md 的 Commit 指南章节,就要求 Commit messages 需要遵循约定式提交的标准,并列出了commit的类型列表。
因此,让我们首先了解什么是约定式提交。
什么是约定式提交
首先来看约定式提交的概念:
约定式提交是一种用于描述提交信息的方式,它使用一组规则来描述提交的内容。约定式提交的规则如下:
- 提交信息必须以一个描述性的标题开头,标题的第一个单词必须是提交类型,提交类型必须是以下之一:
- feat:新功能(feature),类型 为 feat 的提交表示在代码库中新增了一个功能(这和语义化版本中的 次版本号 相对应)。
- fix:bug 修复,类型 为 fix 的提交表示在代码库中修复了一个 bug(这和语义化版本中的 修订号 相对应)。
- BREAKING CHANGE:破坏性变更,在脚注中包含 BREAKING CHANGE: 或 <类型>(范围) 后面有一个 ! 的提交,表示引入了破坏性 API 变更(这和语义化版本中的 MAJOR 相对应)。
- 其他提交类型:例如 docs、chore、style、refactor、perf、test、build、ci、revert 等
- 提交信息的结构如下所示:
<类型>[可选 范围]: <描述> [可选 正文] [可选 脚注]
让我们通过一些例子了解如何完成一个约定式提交:
- 包含了描述、范围并且脚注中有破坏性变更的提交说明:
feat(api): 新增了一个新功能 这是一个新功能的详细描述。 BREAKING CHANGE: 这个提交引入了一个破坏性变更。
- 包含了 ! 字符以提醒注意破坏性变更的提交说明:
feat(api)!: 新增了一个新功能 这是一个新功能的详细描述。
- 不包含正文的提交说明:
docs: correct spelling of CHANGELOG
约定式提交与语义化版本的对应关系
提交类型 | 语义化版本 | 描述 |
---|---|---|
feat | 次版本号 | 新功能 |
fix | 修订号 | bug 修复 |
BREAKING CHANGE | 主版本号 | 破坏性变更 |
其他提交类型 | 无 | 其他类型的提交 |
举个例子
让我们以一个例子来理解这种规则:
对于一个当前版本为 1.0.0 的项目,我们有以下几个提交:
- feat(api): 新增了一个新功能
- fix(api): 修复了一个 bug
- docs: 修正了文档中的错误
我们现在想要发布一个新版本,由于新的提交包括了新功能的增加,但是没有引入破坏性的变更,所以我们可以按照语义化版本的规则,将次版本号递增,版本号变为 1.1.0。
但是如果我们有以下几个提交:
- feat(api)!: 新增了一个新功能, 引入了破坏性变更
- fix(api): 修复了一个 bug
- docs: 修正了文档中的错误
由于引入了破坏性变更,如果我们此时想要发布新版本,我们需要将主版本号递增,版本号变为 2.0.0。
使用约定式提交的好处
使用约定式提交有以下几个好处:
- 便于建立流水线自动化生成 CHANGELOG 、自动化触发对应的构建与部署流程,从而方便地进行版本发布。
- 便于基于提交的类型,自动决定语义化的版本变更,从而方便地进行版本管理。
- 便于形成结构化的提交历史,向同事、公众与其他利益关系者传达变化的性质,降低贡献者了解项目发展历程的难度。
后记
语义化版本和约定式提交是两个相互关联的概念,它们都可以帮助我们更好地管理软件版本。语义化版本规定了版本号的递增规则,以及当软件发生不兼容的API修改时,版本号的变化。约定式提交规定了提交信息的结构,以及如何根据提交信息自动决定版本号的变化。
在实际的项目开发与项目管理过程中,建立和遵守这些规范可以提高软件的质量和可维护性,同时也可以方便地进行版本管理和发布,提高开发效率与协作效率。
参考文章
除另有说明外,本博客的所有文章均使用 CC BY-NC-SA 4.0 许可, 作者保留所有权利, 如需转载请注明出处。