Angular Commit Message 格式规范详解:从提交信息结构到 Changelog 自动生成
Angular 官方仓库(angular/angular)对每一条 Git 提交信息都有着非常严格的格式约束,这套被称作 Angular Commit Message Format 的约定不仅让整个仓库的历史更易读、更易回溯,还使得提交历史可以被程序化解析,从而自动生成每个版本的 CHANGELOG。本文以仓库内规范文档 contributing-docs/commit-message-guidelines.md 为骨架,结合仓库内的实际提交样例、Husky 钩子脚本与 @angular/ng-dev 校验配置,完整讲解 header / body / footer 三段的写法、type、scope、summary 的取值规则,以及 BREAKING CHANGE、DEPRECATED、revert 提交的正确写法。读完本文,你将能写出符合 Angular 仓库标准、可直接通过 CI 校验并被 changelog 工具正确归类的提交信息,这套规范同样适用于任何采用 Conventional Commits 风格的多包仓库。
为什么需要"精确到几乎严苛"的提交格式
规范文档开篇即强调:Angular 对 Git 提交信息的格式有着**非常精确(very precise)**的规则,其收益是双重的:
- 更易读的提交历史(easier to read commit history):统一的
<type>(<scope>): <summary>结构让每次改动在 log 中一目了然,是修 bug、加功能、还是重构一目可辨。 - 可分析的 changelog 生成(analyzable for changelog generation):结构化信息能够被脚本解析,直接驱动自动化 changelog 输出。
第二点在仓库中有直接实证:查看根目录的 CHANGELOG.md,每一个版本号下都按 scope(对应 npm 包)分组,以 "Commit | Type | Description" 的表格呈现,例如 22.2.0-next.4 版本下 common、compiler、compiler-cli、core、forms、language-service、migrations、platform-server 各占一节。表格中的 type 与描述,正是从各提交的 header 部分解析出来的:
| Commit | Type | Description |
| -- | -- | -- |
| 862a0c8ab3 | fix | avoid prototype member collisions |
| 58b0cb4735 | fix | scope animations declared in minified nested rules |
也就是说,commit message 中的每个字段都会直接反映在面向用户与下游开发者的版本发布说明里——格式一旦不统一,changelog 的归类与可读性就会被破坏,这正是 Angular 对提交格式如此严格的根本原因。
提交信息的整体三段式结构
Angular 的每条 commit message 由 header(头部)、body(正文) 与 footer(页脚) 三段组成,彼此之间以空行(BLANK LINE)分隔:
<header>
<空行>
<body>
<空行>
<footer>
- header 是强制项,必须符合下述 "Commit Message Header" 格式;
- body 是强制项,唯一例外是
type为docs的提交;当 body 存在时,其长度必须至少 20 个字符,且需符合 "Commit Message Body" 规范; - footer 为可选项,用于承载 breaking changes、deprecation 信息,以及关联的 GitHub issue / PR 引用。
上述三段式模板在仓库的 .gitmessage 文件中被配置为 Git 的默认提交消息模板,开发者执行 git commit 时编辑器会自动填充 <type>(<scope>): <summary>、提示撰写动机并给出 100 字符参考刻度线。因此,理解该文件的内容就是理解这套规范的最直接入口。
Commit Message Header:<type>(<scope>): <short summary>
header 是提交的"门面",格式如下:
<type>(<scope>): <short summary>
│ │ │
│ │ └─⫸ Summary 用一般现在时,首字母不大写,句末不加句号
│ │
│ └─⫸ Commit Scope:包名(见 scope 章节)
│
└─⫸ Commit Type:build|ci|docs|feat|fix|perf|refactor|test
其中 <type> 与 <short summary> 是必填字段,(<scope>) 可选。注意 type 与 scope 之间、scope 与 summary 之间没有空格,冒号后有一个空格。
Type:必须从这 8 个枚举值中取值
type 描述改动的性质,只能是下表中的一个:
| Type | 含义 |
|---|---|
| build | 影响构建系统或外部依赖的改动(示例 scope:gulp、broccoli、npm) |
| ci | CI 配置文件与脚本的改动(示例:GitHub Actions 工作流) |
| docs | 仅涉及文档的改动 |
| feat | 新功能 |
| fix | 缺陷修复 |
| perf | 提升性能的代码改动 |
| refactor | 既不修 bug 也不加功能的代码重构 |
| test | 补充缺失的测试或修正既有测试 |
仓库最近的提交历史是这些 type 最直观的样例。例如当前分支最新提交 ci: move packages/private under fw-general(无 scope 的 ci 提交,因改动跨包),而 CHANGELOG.md 中 fix 与 feat 在几乎每个包下都大量出现。
注意:header 中出现的 type/scope 只是消息文本层面的枚举约束,真正的强制执行在 CI 与本地钩子层完成,见下文"提交校验是如何落地的"一节。
Scope:以受影响 npm 包为准
scope 应当是受影响的 npm 包名(从阅读 changelog 的读者视角来感知)。支持的全部 scope 为:
animationsbenchpresscommoncompilercompiler-clicoredev-infradevtoolsdocs-infraelementsformshttplanguage-servicelanguage-serverlocalizemigrationsplatform-browserplatform-browser-dynamicplatform-serverrouterservice-workerupgradevscode-extensionzone.js
上述 24 个 scope 并非任意命名,而是与仓库 .ng-dev/commit-message.mjs 中配置的 scopes 数组一一对应:
export const commitMessage = {
maxLineLength: Infinity,
minBodyLength: 20,
minBodyLengthTypeExcludes: ['docs'],
scopes: ['animations', 'benchpress', 'common', /* ... */ 'zone.js'],
};
配置中还保留了一条醒目的同步约定注释:"If you update this, also update the docs."——即修改此处 scope 列表的同时必须同步更新本文档的 scope 清单,防止文档与校验规则脱节。对照可见文档 header 示意图中出现的 bazel、packaging、changelog、ngcc、ve 等名称属于历史演进中被移除或合并的旧 scope,当前以文档 scope 清单与 .ng-dev/commit-message.mjs 配置为准。
文档同时给出了"以包名为准"这一通用规则的少数例外:
dev-infra:用于/scripts与/tools目录内与 dev-infra 相关的改动;docs-infra:用于 adev 目录(angular.dev 文档应用)内的基础设施改动,如应用代码、工具链或配置;但若要修改文档内容本身(例如编辑.md文件),应使用不带 scope 的docs:,而不是docs-infra;migrations:用于ng update迁移逻辑的改动;devtools:用于浏览器扩展 devtools/README.md 的改动;- 空字符串 / 不写 scope:适合跨所有包统一进行的
test与refactor改动(如test: add missing unit tests),以及不与任何特定包相关的 docs 改动(如docs: fix typo in tutorial)。
Summary:一句话说清"做了什么"
summary 是 header 的收尾部分,写作时要遵守三条硬性规则:
- 使用命令式、一般现在时:写
change,不要写changed或changes; - 首字母不要大写;
- 句末不要加句号(
.)。
从仓库真实提交可以看出这些规则的落地形态:
fix(vscode-extension): handle escaped delimiters in inline template and styles highlighting
feat(core): add utility for testing directives
refactor(core): remove old `deferredImports` structure
docs: highlight the search tutorial @for block as Angular
Commit Message Body:解释"为什么"而非"做什么"
与 summary 相同,body 也使用命令式、一般现在时(fix 而非 fixed/fixes)。body 的核心作用是解释做出该改动的动机(why)——规范原文要求 "This commit message should explain why you are making the change",并推荐通过对比旧行为与新行为来呈现改动的影响面。
例如仓库最近一条 fix(forms) 提交的 body,先说明假设前提、再阐述推理、最后澄清边界,结构非常适合作为样板:
Signal forms can safely assume {readOnlyHint: false, untrustedContentHint: false}
given their context. A form _must_ alter page DOM ... We also assume returned
content is trusted ...
In the future, we might want to consider cases where application errors are
explicitly untrusted ... For now, that's out of scope ...
docs 类型的提交是唯一可以省略 body 的类型(对应 .ng-dev/commit-message.mjs 中的 minBodyLengthTypeExcludes: ['docs'])。而对于其他所有类型,body 一旦存在就不得短于 20 个字符——这个下限并非文档的软性建议,而是 minBodyLength: 20 这一真实校验阈值。
仓库为开发者提供了一个极佳的书写辅助:.gitmessage 中包含多个完整示例,包括简单重构(refactor(core): rename refreshDynamicEmbeddedViews to refreshEmbeddedViews)、文档改动、bug 修复与 breaking change 提交,并提示 body 每行应在 100 字符内换行。开发者写提交时可直接参考这些"标准答案"调整自己的措辞。
Commit Message Footer:breaking changes、deprecations 与 issue 关联
footer 是可选段,承载三类信息:breaking changes(破坏性变更)、deprecations(废弃声明),以及本提交 close / 关联的 GitHub issue 与 PR。footer 中的注释引用通常以 Fixes #<issue number>、Closes #<pr number> 的形式书写——仓库大量提交正文末尾都带 Fixes #65493、Fixes #54164. 这样的引用。
BREAKING CHANGE 区块
标准结构如下:
BREAKING CHANGE: <breaking change 摘要>
<空行>
<破坏性变更的详细说明 + 迁移指引>
<空行>
<空行>
Fixes #<issue number>
写作要点:BREAKING CHANGE: 前缀之后先跟一行简短摘要,空一行后给出包含迁移指引(migration instructions)的详细说明。在 .gitmessage 中有一个教科书式的 feat(bazel): simplify ng_package by dropping esm5 and fesm5 完整示例——摘要一句点明 "esm5 and fesm5 format is no longer distributed",随后分场景(Angular CLI 用户无需操作、自行构建的用户需自行降级)撰写迁移指引,最后以 Fixes #1234 结尾。由于此类提交会影响所有下游使用者,Angular 仓库对 BREAKING CHANGE 提交的评审也格外严格。
DEPRECATED 区块
结构与 BREAKING CHANGE 对称:
DEPRECATED: <被废弃的内容>
<空行>
<废弃说明 + 推荐的替代升级路径>
<空行>
<空行>
Closes #<pr number>
要点:DEPRECATED: 后接一句简短的"什么被废弃了",空行后详细说明废弃原因,并明确给出推荐的更新路径(recommended update path),让使用者在版本升级前就能知晓替代方案。
Revert 提交:撤销类提交的特殊规则
如果某次提交用于撤销前一次提交,则:
- header 必须以
revert:开头,后跟被撤销提交的 header; - body 中必须包含被撤销提交的 SHA,格式为
This reverts commit <SHA>; - 同时清楚说明撤销该提交的理由。
这种"保留原始 header + 附 SHA"的做法,使撤销与被撤销之间可双向追溯,自动化工具与人工 review 都能快速定位一组"提交—回滚"对。
提交校验如何落地:Husky 钩子与 ng-dev
理解规范只是第一步,Angular 仓库用工具将这套规范"固化"为日常不可绕过的约束:
- 依赖安装时启用钩子:根目录 package.json 中声明了
"prepare": "husky"与huskydevDependency,clone 仓库并安装依赖后 Git hooks 自动就位。 - 提交信息校验钩子:
.husky/commit-msg中调用pnpm --silent ng-dev commit-message pre-commit-validate --file $1,在提交前根据 .ng-dev/commit-message.mjs 的配置(type 枚举、24 个 scope、minBodyLength: 20、docs 豁免)校验待提交的消息。 - 草稿恢复钩子:
.husky/prepare-commit-msg调用ng-dev commit-message restore-commit-message-draft,用于在提交前从暂存区恢复上次未写完的提交信息草稿,减少格式化负担。 - CI 层的兜底:上述钩子在校验工具异常时会输出 WARNING 而非直接 fail(
set +e包裹),真正的强制性校验主要由 CI / pull request 阶段完成,确保任何绕过本地钩子的提交也无法蒙混过关。
常见错误速查与实践建议
结合规范与仓库样例,编写 commit message 时最容易踩的坑包括:
- summary 大写或带句号:
Fix: update readme.是错误的,应为fix: update readme; - type 超出枚举:
chore、style、improvement等不在 8 个合法 type 内(.gitmessage中的历史注释含style,但当前规范与校验配置只接受build|ci|docs|feat|fix|perf|refactor|test); - scope 与包名不符:改动
packages/core却写fix(common): ...会误导 changelog 分组,scope 应使用 .ng-dev/commit-message.mjs 中列出的受支持包名; - body 不足 20 字符:非
docs类型提交若带 body,过短将无法通过校验; - breaking change 无迁移指引:
BREAKING CHANGE:之后必须给出可执行的迁移说明,否则下游使用者无从升级; - revert 不带 SHA:务必在 body 中写
This reverts commit <SHA>。
对任何希望长期维护的开源仓库,这套"枚举 type + 包名 scope + 强制 body + 结构化 footer"的 Angular Commit Message 规范都值得直接复用:它让 git log 变得可扫读,让 changelog 可以机械生成,也让每一次版本发布背后的改动边界清晰可见。仓库中 CHANGELOG.md 那洋洋万行的版本记录,正是这套规范长期运转的最好证明。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00