假设你住在一栋 12 人合租的大房子里,玄关有一块公共大白板。谁换了客厅的灯泡、修好了浴室的漏水水龙头,都要在白板上贴一张便签做记录。一开始大家很随意:有人写“今天弄了点东西”,有人写“update“,还有人画了三个句号”。。。“。三个月后,新搬来的室友想搞明白:浴室那个水龙头是什么时候、被谁、怎么修好的?他站在白板前看了十分钟,得到的全部信息只有一句——”反正有人修过”。这就是没有 Commit Message(提交信息)规范时的日常。 本文要介绍的 Conventional Commits(约定式提交),就是一套给开发社区定制的“便签公约”。它规定:每张便签必须写清“变更类型、影响范围、一句话描述”;遇到“拆墙级别”的重大改动,还必须贴上醒目的警示标志。读完你会发现,一行小小的提交信息,其实是撑起自动 Changelog、语义化版本和高效 Code Review 的整个体系的基石。

岗位关联

对 AI 从业者来说,规范的 Commit Message 是 PR 被快速合并的“隐形敲门砖”:它能帮 reviewer 快速信任你的每次变更,也能让 AI 编程助手更好地理解你的项目历史。

目录
  1. 可视化演示
  2. 提交信息意义
  3. 约定式提交格式
  4. 类型分类体系
  5. Scope范围标注
  6. 破坏性变更
  7. PR流程落地
  8. 自动化工具链
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Conventional Commits结构化提交、Breaking Change标注与CI自动生成Changelog的过程,可切换三种模式观察提交规范落地。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Conventional Commits格式 结构化type前缀对提交分类,如feat、fix、docs 格式为type(scope): description,type必填 feat(api): add endpoint 结构化提交提升可追溯性。

提交信息意义

合租屋里的便签,如果不是写给三个月后的自己看的,那它写来做什么?真正需要追溯历史时,白板上只有“update“和”。。。“——这就是无规范提交的日常。你也许记得昨天改了什么,但一个月后呢?三个月后呢?Git 把每一次修改都永久记在了”白板”上,可如果每张便签都语焉不详,这个白板就只是一堆乱码。

放到技术语境里,Commit Message(提交信息)是一种异步沟通:你写下它的时间点,和有人阅读它的时间点,往往相隔数周甚至数年。阅读者可能是未来的你、接手代码的同事、审阅 PR 的维护者,也可能是自动生成 Changelog 的机器人。一篇好的提交信息,应当回答五个问题:改了什么、为什么改、影响哪里、谁改的、什么时候改的。其中“谁”和“什么时候”由 Git 自动记录,真正需要你亲手写的,是前三个。

一份有意义的提交记录,本质上由三块信息构成:

从此,合租屋的白板不再是“鬼故事合集”。每个人写便签时多花三十秒,未来所有人省下三小时。这正是 Commit Message 规范存在的第一层理由:让历史真正可读、可追溯。

提交信息=类型+主题(一句话)+理由(背景)\text{提交信息} = \text{类型} + \text{主题(一句话)} + \text{理由(背景)}
这条式子告诉我们:提交信息不是“顺手写一行”,而是“结构化地交代一次变更”。
  1. 类型(Type)先用一个动词标签说明“这是哪种变更”,让机器和人都能快速分类。
    type∈{feat, fix, docs, … }\text{type} \in \{\text{feat},\ \text{fix},\ \text{docs},\ \dots\}
  2. 主题(Subject)用一句话说清“做了什么”,控制在约 50 字符内,像便签标题一样醒目。
    subject: 不超过 50 字的一句话\text{subject}:\ \text{不超过 50 字的一句话}
  3. 理由(Body)在正文里补充“为什么这么做”,包括背景、动机与注意事项。
    body: 为什么这么做、有什么取舍\text{body}:\ \text{为什么这么做、有什么取舍}

约定式提交格式

“便签公约”说起来很简单,难的是所有人写法一致。有人写“修了一下”,有人写“fix bug“,有人写”fix:bug”(冒号后居然没有空格)。于是社区在 2018 年前后定下了一份公开的公约——Conventional Commits(约定式提交)。它把便签模板固定成一行,谁都能照着写,机器也能照着读。

约定式提交把一条提交信息分成三部分:头部(header)、正文(body)、页脚(footer)。其中 header 是必填的,也是最核心的:

写完 header 后,如果这次变更需要交代背景,就在空一行后写正文(body);如果涉及破坏性变更或需要引用 Issue,就在末尾写页脚(footer)。这样一张“三层便签”就完整了:黄纸条写摘要,白纸区写原因,蓝角贴引用。合租屋从此告别“便签百家争鸣”的时代,扫一眼白板就知道每张便签在说什么。

⟨type⟩(⟨scope⟩)?(!)?: ⟨subject⟩\langle \text{type} \rangle(\langle \text{scope} \rangle)?(!)?:\ \langle \text{subject} \rangle
这就是整篇文章最重要的“公式”:类型(+可选范围)(+可选破坏标记)+ 冒号空格 + 一句话描述。
  1. type(类型)必填,小写英文单词,例如 feat、fix。
    type: 必填,小写动词分类\text{type}:\ \text{必填,小写动词分类}
  2. (scope)(范围)可选,用英文括号包裹影响的功能模块。
    (⟨scope⟩): 可省略,如 (auth)(\langle \text{scope} \rangle):\ \text{可省略,如 } (\text{auth})
  3. !(破坏标记)可选,写在右括号后或 type 后,表示不兼容变更。
    (!): 可选,出现即表示 Breaking Change(!):\ \text{可选,出现即表示 Breaking Change}
  4. 冒号与空格半角冒号后必须跟一个空格,这是机器解析的分界符。
    : 半角冒号 + 一个空格:\ \text{半角冒号 + 一个空格}
  5. subject(描述)一句话说清变更内容,一般小写开头,句末不加句号。
    subject: 祈使语气的简短描述\text{subject}:\ \text{祈使语气的简短描述}

类型分类体系

既然便签要分类,那标签总共分几种?约定式提交最常用的是 feat 和 fix,其他还有 docs、refactor、perf、test、chore、style、build、ci 等。你可以把它们理解为合租屋里的不同颜色贴纸:绿色代表“添了新东西”,红色代表“修好了坏东西”,蓝色代表“更新了说明书”。

选择类型时有一个核心判断标准:这次变更对“使用代码的人”来说,外部行为变了吗? 是新增了能力,还是纠正了错误,还是纯粹内部整理?这个判据可以用一个分段函数表达:

这就像管家按颜色整理周报:新增的家具列表归一类,修好的水龙头归一类,重摆的沙发归一类。类型选对了,后续的自动归类、版本推断、历史检索才全部成立。

type={feat对外行为新增功能fix对外行为被纠正refactor外部行为不变docs仅文档修改\text{type} = \begin{cases}\text{feat} & \text{对外行为新增功能} \\\text{fix} & \text{对外行为被纠正} \\\text{refactor} & \text{外部行为不变} \\\text{docs} & \text{仅文档修改}\end{cases}
选 type 不是看“改了多少代码”,而是看“外部使用者感知到了什么”。
  1. feat 的判定用户获得了前所未有的能力,比如新增登录接口。
    feat: 对使用者可见的新功能\text{feat}:\ \text{对使用者可见的新功能}
  2. fix 的判定行为与预期不符,被修正回正确状态。
    fix: 修复缺陷或错误行为\text{fix}:\ \text{修复缺陷或错误行为}
  3. refactor 的判定重写内部结构,但外部调用方式完全不变。
    refactor: 不改变外部行为\text{refactor}:\ \text{不改变外部行为}
  4. 分类互斥一次提交只选一个最贴切的类型,不要写成“feat 同时修复了 bug”这种四不像便签。
    type: 同一提交选且只选一个\text{type}:\ \text{同一提交选且只选一个}

Scope范围标注

便签上只写“fix: 修好了”,室友还是会追问:“你修好了哪里?浴室还是厨房?” 这就是 Scope(范围)要解决的问题。在约定式提交中,scope 写在 type 后面的括号里,用来标注这次变更影响的模块或区域。

例如 fix(auth): 登录成功后跳转回原页面,括号里的 auth 就是认证模块。有了它,谁动了“浴室”一目了然,住在隔壁的室友(维护其他模块的同事)立刻警觉:这次改动会不会波及我?

scope 不是必填项。如果一次变更横跨多个模块,写不写某个具体模块都不准确,这时可以省略括号——含义是“影响范围较大”。但团队一旦约定用 scope 区分模块,就能用 git log --grep="fix(auth)" 这样的命令快速过滤某个模块的全部修复。这就像给每张便签贴了“浴室”“客厅”的标签,将来检索某个空间的历史时,一抽目录就全出来了。

fix(auth): 登录成功后跳转回原页面\text{fix}(\texttt{auth}):\ \text{登录成功后跳转回原页面}
一个带 scope 的提交信息,等于在便签上额外贴了“影响区域”的索引标签。
  1. fix(类型)这里是缺陷修复,所以用 fix。
    type=fix\text{type} = \text{fix}
  2. 括号(范围容器)英文圆括号,紧跟在 type 后面,中间无空格。
    ( ): 英文圆括号({\ }):\ \text{英文圆括号}
  3. auth(范围名)用简短的小写单词或短横线表示模块,如 auth、ui、api。
    scope=auth(认证模块)\text{scope} = \text{auth(认证模块)}
  4. 冒号空格右括号后紧跟半角冒号和一个空格,再接描述。
    : 分隔符:\ \text{分隔符}
  5. subject(描述)一句话说清这次修复的具体效果。
    登录成功后跳转回原页面\text{登录成功后跳转回原页面}

破坏性变更

合租屋不是没出现过“大事”:有人把客厅的墙拆了改成了健身房。这种事如果只贴一张绿色便签“feat(客厅): 新增健身器材”,第二天全屋人都会懵——昨天还能在客厅跑步,今天跑步机都放不下。这类“旧用法被推翻”的变更,就是 Breaking Change(破坏性变更)。

约定式提交为破坏性变更设计了两个动作。第一,在 header 的类型或 scope 后面加一个感叹号 !,相当于在便签正面盖了一个“危险”红章;第二,在页脚(footer)里必须以 BREAKING CHANGE: 开头,详细说明“旧用法如何迁移到新用法”。

为何要如此大费周章?因为破坏性变更直接影响版本号——它对应语义化版本中的 MAJOR(主版本号) 升级。一个 ! 加上一段迁移说明,既能让使用者第一时间评估风险,又能让发布工具自动把版本从 1.4.2 跳到 2.0.0。拆墙之前先广播,全屋人才不至于在健身房门口撞个头破血流。

feat(api)!: 移除 v1 查询接口\text{feat}(\texttt{api})!:\ \text{移除 v1 查询接口}
感叹号负责提醒“这里变了”,页脚负责告诉人们“旧代码该怎么改”。
  1. feat(类型)表面看是新增能力,实际是移除旧接口,因此仍可选 feat。
    type=feat\text{type} = \text{feat}
  2. (api)(范围)影响的是 API 模块。
    scope=api\text{scope} = \text{api}
  3. !(破坏标记)右括号后紧跟感叹号,表示“这不是普通更新,是拆墙级变更”。
    (!): Breaking Change 标记(!):\ \text{Breaking Change 标记}
  4. 冒号空格与 subject标记之后正常写冒号空格和描述。
    : 移除 v1 查询接口:\ \text{移除 v1 查询接口}
  5. footer 迁移说明正文后空行,写 BREAKING CHANGE: 加迁移指南,告诉使用者如何从 v1 升级到 v2。
    BREAKING CHANGE: 迁移到 v2 接口\texttt{BREAKING CHANGE:}\ \text{迁移到 v2 接口}

PR流程落地

在开源项目中,便签不是贴完就完事的,它要先经过“议事会”审阅——这就是 Pull Request(PR,拉取请求)。当你在 GitHub 上提交 PR 时,PR 的标题、描述、以及里面的每个 commit message,共同组成一份“变更提案”。提案写得越规范,维护者审阅的速度就越快,被合并的概率也越高。

PR 流程中践行 Commit Message 规范,有四个关键动作。第一,PR 标题遵循同一套格式,比如 feat(ui): 新增深色模式,让维护者在 PR 列表中一眼识别类型;第二,在描述中关联 Issue,写 Closes #123,让需求、讨论和代码闭环;第三,把提交拆分成原子化的小步,每一张便签只做一件事;第四,如果采用 Squash 合并(把多个提交压缩成一条),最终那条提交信息也必须是规范的。

在议事会上,reviewer 看到一个标题清晰、描述完整、提交历史条理分明的 PR,就像拿到一份装订整齐的施工方案,而不是一摞乱纸。规范的 Commit Message 不只是写给自己看,更是整个 PR 审阅流程的“沟通基础设施”。

合格PR=规范标题⏟feat(ui): 新增深色模式+背景描述⏟解决问题、测试情况+∑i=1n规范提交i⏟原子化小步\text{合格PR} = \underbrace{\text{规范标题}}_{\text{feat(ui): 新增深色模式}} + \underbrace{\text{背景描述}}_{\text{解决问题、测试情况}} + \underbrace{\sum_{i=1}^{n}\text{规范提交}_i}_{\text{原子化小步}}
PR 的交付物不只有代码,还有一套让人能顺畅阅读的“变更叙述”。
  1. 规范标题(Title)PR 标题沿用 type(scope): subject 格式,作为整个 PR 的“封面便签”。
    PR Title: ⟨type⟩(⟨scope⟩)?: ⟨subject⟩\text{PR Title}:\ \langle \text{type} \rangle(\langle \text{scope} \rangle)?:\ \langle \text{subject} \rangle
  2. 背景描述(Description)用自然段说明问题背景与测试结果,帮助 reviewer 快速进入上下文。
    Body: 动机、方案、测试情况\text{Body}:\ \text{动机、方案、测试情况}
  3. 关联 Issue使用 Closes #123 让 PR 合并时自动关闭对应 Issue。
    Closes #123\text{Closes } \#\text{123}
  4. 原子化提交(Commits)把大改动拆成多个单一意图的 commit,每个 commit 本身也是一条规范便签。
    ∑i=1nCommiti: 每次提交只做一件事\sum_{i=1}^{n}\text{Commit}_i:\ \text{每次提交只做一件事}

自动化工具链

规范还有一个隐藏红利:机器可读。当每张便签都遵循 type(scope): subject 的统一格式时,管家机器人就能自动帮你干活了。合租屋的公告栏编辑(Changelog 生成工具)会每周自动把便签整理成周报:新增了什么、修复了什么、有什么重大变更——不需要任何人手工汇总。

整个自动化链条的枢纽是语义化版本(Semantic Versioning),形式为 主版本号.次版本号.修订号(MAJOR.MINOR.PATCH)。约定式提交和它之间有一条黄金映射规则:

现实中,commitlint 会在 CI(持续集成)里检查提交信息格式,格式不对直接让流水线变红;semantic-release 会在合并到主干后自动发版;Changelog 则按"Added / Fixed / Breaking Changes"分门别类生成。合租屋从此有一个不知疲倦的管家,每周自动贴出公告。人类要做的只剩一件事:好好写好每一张便签。

fix↦PATCH,feat↦MINOR,breaking↦MAJOR\text{fix} \mapsto \text{PATCH},\qquad \text{feat} \mapsto \text{MINOR},\qquad \text{breaking} \mapsto \text{MAJOR}
提交信息的类型直接决定下一个版本号跳哪一位:修 bug 涨修订、加功能涨次版本、破坏性变更涨主版本。
  1. fix → PATCH缺陷修复发布后,只递增修订号,如 1.2.4 → 1.2.5。
    1.2.4→1.2.5: PATCH\texttt{1.2.4} \to \texttt{1.2.5}:\ \text{PATCH}
  2. feat → MINOR向后兼容的新功能,递增次版本号并清空修订号。
    1.2.5→1.3.0: MINOR\texttt{1.2.5} \to \texttt{1.3.0}:\ \text{MINOR}
  3. breaking → MAJOR不兼容变更,递增主版本号。
    1.3.0→2.0.0: MAJOR\texttt{1.3.0} \to \texttt{2.0.0}:\ \text{MAJOR}
  4. 工具执行semantic-release 这类工具读取每次提交的类型,自动计算新版本号、生成 Changelog 并发布。
    Commits→版本推断→发布\text{Commits} \to \text{版本推断} \to \text{发布}

AI 应用场景

MLOps 训练仓库的版本发布

阶段:模型实验与发布管理

解决的问题:AI 项目里训练代码、数据处理逻辑、模型配置频繁变动,提交信息一乱,就说不清“模型指标掉两个点”到底是哪次改动引起的。规范提交后,每次实验的关键变更都有据可查。

具体案例:某 MLOps 团队用约定式提交管理训练仓库,提交像 perf(trainer): 将 batch size 调至 128、feat(data): 新增数据增强 pipeline。发布新 checkpoint 时,脚本自动扫描提交历史,把 feat 和 fix 归类生成简洁的 Changelog,直接发给合作算法团队。

PR 审阅时的机器人把关

阶段:代码审阅与 CI 自动化

解决的问题:AI 工程师的 PR 里常混有实验性代码、格式化改动和临时调试输出,reviewer 很难分辨重点。机器人在合并前拦截不合规的提交信息,让审阅回归代码本身。

具体案例:团队在 GitHub Actions 中配置 commitlint,PR 标题不符合 type(scope): subject 格式时自动标记为失败,并要求作者修改后再审。PR 列表按 feat / fix 过滤,维护者能优先处理紧急修复。

线上事故的快速回溯

阶段:线上问题排查与回溯

解决的问题:模型服务上线后效果下滑,传统做法是逐个看提交 diff,大海捞针。有了规范提交,可以按类型和模块精确检索历史。

具体案例:服务指标异常后,工程师执行 git log --grep="feat(data)" --oneline,秒级定位到最近一次数据模块的功能变更,结合回滚操作快速恢复服务。

Python 演示

python56 行
import refrom collections import defaultdict PATTERN = re.compile(    r"^(?P<type>feat|fix|docs|refactor|perf|test|chore|build|ci|style)"    r"(?:\((?P<scope>[^()]+)\))?(?P<breaking>!)?: (?P<subject>.+)$") COMMITS = [    "feat(auth): 新增短信验证码登录",    "fix(cart): 修复结算金额精度丢失",    "feat(api)!: 移除v1查询接口",    "docs(readme): 更新快速开始文档",    "refactor(utils): 抽取日期格式化函数",    "update 乱七八糟的提交",] SECTION_MAP = {    "breaking": "Breaking Changes",    "feat": "Added",    "fix": "Fixed",    "docs": "Docs",    "refactor": "Refactor",    "perf": "Performance",}  def parse_commit(message):    m = PATTERN.match(message.strip())    if m is None:        return None    return m.groupdict()  def build_changelog(commits):    groups = defaultdict(list)    for msg in commits:        info = parse_commit(msg)        if info is None:            print("ignored:", msg)            continue        key = "breaking" if info["breaking"] else info["type"]        scope = "(" + info["scope"] + ") " if info["scope"] else ""        groups[key].append("- " + scope + info["subject"])    lines = ["# Changelog", ""]    for key in ("breaking", "feat", "fix", "docs", "refactor", "perf"):        if key not in groups:            continue        lines.append("## " + SECTION_MAP[key])        lines.extend(groups[key])        lines.append("")    return "\n".join(lines)  if __name__ == "__main__":    print(build_changelog(COMMITS))
目的:用正则解析一批符合约定式提交的 Commit Message,自动归类并生成 Markdown 格式的 Changelog,演示“规范驱动自动化”的完整思路。
  1. r"^(?P<type>feat|fix|docs|refactor|perf|test|chore|build|ci|style)"用正则锚定字符串开头,通过命名分组抓取类型
  2. r"(?:\((?P<scope>[^()]+)\))?"匹配括号包裹的 scope,整个分组可省略
  3. r"(?P<breaking>!)?"匹配可选的感叹号,用于识别破坏性变更
  4. r": (?P<subject>.+)$"冒号加一个空格后,捕获剩余内容作为描述
  5. if info is None:解析失败说明提交不合规,打印 ignored 后跳过
  6. key = "breaking" if info["breaking"] else info["type"]优先把破坏性变更单独归为一类
  7. groups[key].append("- " + scope + info["subject"])按类别把内容追加到对应分桶
  8. if __name__ == "__main__":直接运行时输出排版好的 Changelog 文本

常见误区

误区我觉得提交信息就要把改动的来龙去脉全写进去,所以经常一口气写三四百字,恨不得把每个变量的变化都交代清楚。

正解提交信息不是论文,而是有结构的便签。header 保持一句话摘要(约 50 字符),背景放在 body,Issue 引用和破坏性说明放 footer。就像合租屋便签:正面写“修好浴室水龙头”,背面才写“用了什么型号的垫圈”。信息详细不等于信息有序,无序的长文反而没人读。

为何常见:新手怕“说不够”导致未来看不懂,于是用堆字数来缓解焦虑,却忽略了结构化表达才是可读性的关键。

误区看到开源项目都是英文 commit,觉得写中文不专业,于是中英混杂地写“fix 了 bug,优化 login 逻辑”。

正解约定式提交规范只约束格式和类型词(如 feat、fix),不约束描述语言。公司内部仓库完全可以用中文写 subject,关键是格式一致、表意清晰。就像合租屋的便签,大家约定好格式模板,内容用全屋人都懂的语言即可。

为何常见:把“开源惯例”误解为“必须用英文”,忽略了规范的本质是沟通效率而非语言形式。

误区我学会了 `feat(xxx): 描述` 的模板后,每次提交都套格式,但经常一条提交里塞了三四个改动,类型也随便选。

正解规范的核心是“一次提交只做一件事”,格式只是它的外壳。如果一条提交既改了 bug 又加功能还动了文档,哪怕格式满分,未来的 git bisect(二分定位 bug)和代码回滚都会痛苦不堪。就像一张便签既写“修水龙头”又写“买新沙发”,执行时根本没法单独撤销其中一件事。

为何常见:人天生倾向于“攒一批改动一起提交”以减少操作次数,却忽略了变更的可独立回溯性。

误区破坏性变更不就是在 header 里加个感叹号吗?加完就大功告成,页脚不用写。

正解! 是“警示灯”,BREAKING CHANGE: 页脚才是“使用说明书”。没有迁移说明,使用者只知道“坏了”,不知道“怎么改”。项目维护者看到不带迁移指南的 breaking commit,通常直接打回。就像你在门口贴“健身房改造,勿入”,却不告诉邻居去哪里跑步,这不算负责任的通知。

为何常见:写 ! 只需一秒,写迁移说明需要思考,人性倾向于用最省力的方式完成“看似合规”的提交。

误区代码写得好才是本事,commit message 只是给 git 看的备注,反正功能正常就行。

正解一旦项目经历数月迭代,提交历史就是团队唯一的“变更记忆”。Changelog 由它生成,版本号由它推断,线上事故靠它回溯。代码写得好是“做对了事”,提交信息写得好是“说清了事”。合租屋的白板若是天书,房子再豪华,住客也会迷路。

为何常见:人在编码时注意力集中在功能实现上,容易把“收尾记录”视为低价值工作,低估了长期协作中的信息成本。

面试题

Q1. Conventional Commits 中 header 的标准格式是?

单选题 · easy

  • A.<type>(<scope>): <subject>← 正确答案
  • B.<scope>(<type>): <subject>
  • C.<type>: <scope>(<subject>)
  • D.<subject>(<scope>): <type>

答案:A。header 的顺序是 type 开头,可选 scope 用括号跟在 type 后,冒号空格后接 subject,即 type(scope): subject。其他选项要么类型和范围顺序颠倒,要么冒号位置错误,都不符合约定式提交规范。

Q2. 关于 feat 和 fix 的区别,正确的是?

单选题 · easy

  • A.feat 用于缺陷修复,fix 用于新增功能
  • B.fix 用于缺陷修复,feat 用于新增功能← 正确答案
  • C.两者可以随意混用
  • D.只有 fix 是标准规范类型

答案:B。feat 对应新增对使用者可见的功能,fix 对应修复与预期不符的缺陷。就像便签分类:绿色贴纸代表“添了新家具”,红色贴纸代表“修好了坏东西”,二者语义不同,不可混用。

Q3. 关于 scope(范围),下列说法错误的是?

单选题 · mid

  • A.可以描述影响的功能模块
  • B.每次提交都必须填写← 正确答案
  • C.写在 type 后面的括号里
  • D.省略时表示影响范围较大

答案:B。scope 是可选字段,不是必填项。如果变更横跨多个模块,写某个具体模块反而不准确,此时可以省略括号。B 说“必须填写”是错误的,其余三项均符合规范。

Q4. Breaking Change 的感叹号 ! 应该加在哪里?

单选题 · mid

  • A.在 type 或 scope 后面加!← 正确答案
  • B.在 subject 末尾加上!
  • C.在正文首行添加!
  • D.在提交说明中不需要

答案:A。! 加在 header 中 type 或 scope 的右括号之后、冒号之前,例如 feat(api)!: 移除v1接口。加在 subject 末尾或正文首行都不符合规范,D 更是忽略了破坏性变更必须醒目标注的要求。

Q5. 页脚(footer)中标注破坏性变更的固定关键词是?

单选题 · easy

  • A.BREAKING CHANGE:← 正确答案
  • B.BREAK CHANGE:
  • C.CHANGE BREAKING:
  • D.BREAKING:

答案:A。约定式提交规定 footer 中的固定短语必须是 BREAKING CHANGE:,后面跟迁移说明。它相当于拆墙公告上的“使用说明书”。其他词序写错或省略 CHANGE 都不被工具识别。

Q6. 以下哪些属于约定式提交的合法类型?

多选题 · easy

  • A.feat 和 fix← 正确答案
  • B.docs 和 style← 正确答案
  • C.good 和 nice
  • D.refactor← 正确答案

答案:ABD。标准类型包括 feat、fix、docs、style、refactor、perf、test、chore 等。C 选项“good“和”nice”不是规范中的类型词,工具不会识别。

Q7. 一条完整的约定式提交信息可以由哪些部分组成?

多选题 · mid

  • A.header 头部← 正确答案
  • B.body 正文← 正确答案
  • C.footer 页脚← 正确答案
  • D.必须包含二进制签名

答案:ABC。header 必填,body 和 footer 按需填写。header 是摘要,body 写背景,footer 放破坏性说明或 Issue 引用。D 的二进制签名不是约定式提交的组成部分。

Q8. 提交信息 feat(api)!: 移除 v1 查询接口 触发的新版本号变化是?

单选题 · hard

  • A.递增 PATCH 版本号
  • B.递增 MINOR 版本号
  • C.递增 MAJOR 版本号← 正确答案
  • D.保持版本号不变

答案:C。! 表示 Breaking Change,对应语义化版本中的 MAJOR(主版本号)升级,例如从 1.3.0 升到 2.0.0。如果只有 fix 则升 PATCH,只有 feat 则升 MINOR。

Q9. 规范 Commit Message 能带来哪些直接价值?

多选题 · mid

  • A.自动生成 Changelog← 正确答案
  • B.按类型筛选提交历史← 正确答案
  • C.驱动语义化版本升级← 正确答案
  • D.强制统一代码风格

答案:ABC。机器读取类型后能自动归类生成 Changelog、按 --grep 过滤提交、推断版本号递增。但提交信息规范与代码风格(如缩进、命名)无关,D 不属于它的直接价值。

Q10. 在 PR 采用 Squash 合并时,推荐的最终提交信息写法是?

单选题 · hard

  • A.直接沿用第一条提交信息
  • B.使用规范的汇总提交信息← 正确答案
  • C.拼接所有中间提交信息
  • D.只保留 PR 编号,不写描述

答案:B。Squash 合并会把多个提交压缩成一条,最终提交信息应是一条重新组织的规范信息,如 feat(ui): 新增深色模式,并通常引用 PR 编号。直接沿用第一条或机械拼接都会丢失整体上下文,不写描述则让历史无法追溯。