跳至主要內容

约定式提交

LincZero大约 5 分钟

约定式提交

约定式提交

概念

一种用于给提交信息增加人机可读含义的规范

为什么使用约定式提交

根据 Conventional Commits 里的说法:

  • 自动化生成 CHANGELOG
  • 基于提交的类型,自动决定语义化的版本变更
  • 向同事、公众与其他利益关系者传达变化的性质
  • 触发构建和部署流程
  • 让人们探索一个更加结构化的提交历史,以便降低对你的项目做出贡献的难度

Angular 的 Conventional Commits

当然也有其他的规范,但感觉这个比较泛用

网站:https://www.conventionalcommits.org/zh-hans/v1.0.0/(有中文)

这是一个社区驱动的项目,旨在为Git提交消息制定标准。Angular遵循这个标准

提交结构

提交说明的结构如下所示:

原文:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

译文:

<类型>[可选 范围]: <描述>

[可选 正文]

[可选 脚注]

提交说明包含了下面的结构化元素,以向类库使用者表明其意图:

  • 常用
    1. fix: 类型 为 fix 的提交表示在代码库中修复了一个 bug(这和语义化版本中的 PATCH 相对应)。
    2. feat: 类型 为 feat 的提交表示在代码库中新增了一个功能(这和语义化版本中的 MINOR 相对应)。
    3. BREAKING CHANGE: 在脚注中包含 BREAKING CHANGE: 或 <类型>(范围) 后面有一个 ! 的提交,表示引入了破坏性 API 变更(这和语义化版本中的 MAJOR 相对应)。 破坏性变更可以是任意 类型 提交的一部分。
  • 除 fix: 和 feat: 之外,也可以使用其它提交 类型 ,例如 @commitlint/config-conventional(基于 Angular 约定)中推荐的 build:、chore:、 ci:、docs:、style:、refactor:、perf:、test:,等等。
    1. build: 用于修改项目构建系统,例如修改依赖库、外部接口或者升级 Node 版本等;
    2. chore: 用于对非业务性代码进行修改,例如修改构建流程或者工具配置等;
    3. ci: 用于修改持续集成流程,例如修改 Travis、Jenkins 等工作流配置;
    4. docs: 用于修改文档,例如修改 README 文件、API 文档等;
    5. style: 用于修改代码的样式,例如调整缩进、空格、空行等;
    6. refactor: 用于重构代码,例如修改代码结构、变量名、函数名等但不修改功能逻辑;
    7. perf: 用于优化性能,例如提升代码的性能、减少内存占用等;
    8. test: 用于修改测试用例,例如添加、删除、修改代码的测试用例等。
  • 脚注中除了 BREAKING CHANGE: <description> ,其它条目应该采用类似 git trailer format 这样的惯例。

其它提交类型在约定式提交规范中并没有强制限制,并且在语义化版本中没有隐式影响(除非它们包含 BREAKING CHANGE)。 可以为提交类型添加一个围在圆括号内的范围,以为其提供额外的上下文信息。例如 feat(parser): adds ability to parse arrays.。

示例

包含了描述并且脚注中有破坏性变更的提交说明

feat: allow provided config object to extend other configs

BREAKING CHANGE: `extends` key in config file is now used for extending other config files

包含了 ! 字符以提醒注意破坏性变更的提交说明

feat!: send an email to the customer when a product is shipped

包含了范围和破坏性变更 ! 的提交說明

feat(api)!: send an email to the customer when a product is shipped

包含了 ! 和 BREAKING CHANGE 脚注的提交说明

chore!: drop support for Node 6

BREAKING CHANGE: use JavaScript features not available in Node 6.

不包含正文的提交说明

docs: correct spelling of CHANGELOG

包含范围的提交说明

feat(lang): add polish language

包含多行正文和多行脚注的提交说明

fix: prevent racing of requests

Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.

Remove timeouts which were used to mitigate the racing issue but are
obsolete now.

Reviewed-by: Z
Refs: #123

如何生成对应的ChangeLog

见后文

  • 官方使用效果:https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog/CHANGELOG.md
  • 官方开源仓库:https://github.com/conventional-changelog/conventional-changelog

【补充】个人补充

需要注意,这里并不包括有时我们很常用的 addremovechange 这种

另外如果团队没约束或不自动生成 ChangeLog 的情况下还是怎么舒服怎么来吧

启发:

另外,我们会发现这约定式提交其实还有一个特点:面向ChangeLog约定。

搞清楚了这点就会发现他的设计逻辑原理,以及搞清楚这套设定和我们平时弄的有什么实质区别。

  • 自用更关心在自己所认为的添加,而约定式通常将用户关心的内容抽象出来。例如用户最关心的 Top3:

    • feat
    • fix
    • BREAKING CHANGE

    这几个意外的大多是用户没那么关心的。通常也不会被自动收集到ChangeLog中

【扩展】如何通过插件强制约定式提交

Husky我见过,其他目前没怎么听过

  • Git Hooks
  • Husky
    • 一个流行的Git hooks管理工具
  • lint-staged
  • Commitizen
  • IDE集成工具

【扩展】如何自动生成 ChangeLog

【以下内容来源GPT】

可以使用一些工作来自动生成 ChangeLog:

Conventional Changelog

一个基于Conventional Commits规范的工具,它可以生成结构化的变更日志。通常与standard-versionsemantic-release一起使用

使用方法:

安装依赖

npm install conventional-changelog-cli --save-dev

生成Changelog

npx conventional-changelog -p angular -i -o CHANGELOG.md -s

配置文件(可选)

module.exports = {
  preset: 'angular',
  // 其他配置项...
};

集成到发布流程

可以将此步骤集成到CI/CD流程中,自动在每次发布时更新Changelog

使用效果:

  • 官方使用效果:https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog/CHANGELOG.md
  • 官方开源仓库:https://github.com/conventional-changelog/conventional-changelog

Semantic Release

Semantic Release 是一个持续交付工具,它可以根据你的提交消息自动推断出新的版本号,并自动生成变更日志

Git-Release

对于简单的项目,也可以考虑使用一些轻量级的脚本或工具,如git-release,它可以根据提交信息生成简单的变更日志

自定义脚本

#!/bin/bash

echo "# Changelog" > CHANGELOG.md
git log --format="%H %s%n%an%n%ad%n%b" --date=short --reverse | awk '
BEGIN { print "## [Unreleased]" >> "CHANGELOG.md" }
{ if ($1 ~ /^[0-9a-f]{40}$/) { printf("\n\n## [%s]", $2); next } 
    else if ($1 ~ /^feat/) { printf("\n- %s", $0) } 
    else if ($1 ~ /^fix/) { printf("\n- %s", $0) }
}
END { print "\n" >> "CHANGELOG.md" }
' >> CHANGELOG.md