第85课:Git Hooks 与 Husky——提交前检查、commitlint、规范提交信息
版本控制是现代软件工程的基石,而 Git Hooks 是嵌入在 Git 工作流中的自动化脚本机制。利用 Git Hooks,团队可以在代码提交、推送、合并等关键节点自动执行检查——如运行 linter、执行测试、校验提交信息格式。然而,原生 Git Hooks 配置繁琐(需手动编写 shell 脚本并放入 .git/hooks 目录),且无法纳入版本控制与团队共享。Husky 解决了这一问题:它将 Git Hooks 的管理抽象为简单的 npm 配置,使 hooks 可以随项目代码一起提交、安装和更新。结合 commitlint 和 Conventional Commits 规范,Husky 还能强制团队使用统一的提交信息格式,为自动化版本发布和 CHANGELOG 生成奠定基础。本节课将系统讲解 Git Hooks 的工作原理、Husky 的安装与配置、commitlint 的规则定制,以及一套完整的提交规范工作流。
1. Git Hooks 的工作原理
Git 在执行特定操作(如 commit、push、merge)前后会查找并执行对应名称的脚本文件。这些脚本分为两类:
- 客户端 Hooks:在本地执行,如
pre-commit(提交前)、prepare-commit-msg(准备提交信息时)、commit-msg(提交信息编辑完成后)、pre-push(推送前)。 - 服务端 Hooks:在远程仓库执行,如
pre-receive(接收推送前)、post-receive(接收推送后)。
当执行 git commit 时,Git 会在 .git/hooks/ 目录下查找名为 pre-commit 的可执行文件,如果存在则执行它。若脚本以非零状态码退出,提交操作将被中止。这一机制允许开发者在提交前对代码做最后的自动检查。
1.1 原生 Git Hooks 的问题
- 存放在
.git/hooks/目录下,该目录不在版本控制范围内——团队成员无法共享同一套 hooks。 - 编写和调试 shell 脚本对前端开发者不够友好。
- 每个项目需要手动复制 hooks 脚本。
Husky 通过 npm 的 prepare 生命周期和 Git 的 core.hooksPath 配置,将 hooks 的管理转移到项目根目录的 .husky/ 文件夹中,从而可以被 Git 跟踪和共享。
2. Husky:现代化的 Git Hooks 管理
2.1 安装与初始化
安装(Husky v9+,当前推荐版本):
1 | npm install -D husky |
npx husky init 会完成两件事:
- 在项目根目录创建
.husky/文件夹,内含一个pre-commit示例文件。 - 在
package.json的scripts中添加"prepare": "husky",确保每次npm install后自动启用 Git Hooks。
启用原理:prepare 是 npm 的生命周期脚本,会在 npm install(无参数)时自动执行。husky 命令将 Git 的 core.hooksPath 设置为 .husky/,此后 Git 将从该目录读取 hooks 脚本,而非 .git/hooks/。
2.2 添加 Hooks
使用 husky 命令行添加新的 hook:
1 | npx husky add .husky/pre-commit "npm test" |
或者直接编辑 .husky/ 目录下的文件。Hooks 文件是可执行的 shell 脚本。例如,.husky/pre-commit 内容:
1 |
|
- 第一行指定 shell 解释器。
- 第二行加载 Husky 的内部辅助函数(可选)。
- 后续行是实际要执行的命令。
2.3 常用 Hooks 场景
| Hook 名称 | 触发时机 | 典型用途 |
|---|---|---|
pre-commit |
git commit 之前,提交信息编辑前 |
运行 lint-staged,检查代码格式、运行单元测试。 |
commit-msg |
提交信息编辑完成后,提交真正发生前 | 使用 commitlint 校验提交信息是否符合规范。 |
pre-push |
git push 之前 |
运行完整的测试套件,防止推送失败代码。 |
post-merge |
git merge 成功后 |
自动安装依赖(npm install),或通知开发者依赖已变更。 |
在 commit-msg hook 中,Git 会将保存提交信息的临时文件路径作为第一个参数传递。commitlint --edit $1 中的 $1 即引用该参数,读取提交信息进行校验。
3. 规范提交信息:Conventional Commits 与 commitlint
3.1 Conventional Commits 规范
Conventional Commits 是一种轻量级的提交信息约定,格式为:
1 | <type>[optional scope]: <description> |
常用 type 值:
| type | 说明 | 示例 |
|---|---|---|
feat |
新功能。 | feat: add user login API |
fix |
Bug 修复。 | fix: correct date format in report |
docs |
文档变更。 | docs: update API reference |
style |
不影响代码含义的格式变更(空格、分号等)。 | style: format code with prettier |
refactor |
代码重构(既非新功能也非 Bug 修复)。 | refactor: extract common validation logic |
perf |
性能优化。 | perf: reduce bundle size by 30% |
test |
添加或修改测试。 | test: add unit tests for auth module |
chore |
构建过程或辅助工具的变更。 | chore: update dependency to v2 |
ci |
CI/CD 配置变更。 | ci: add GitHub Actions workflow |
build |
影响构建系统或外部依赖的变更。 | build: upgrade webpack to v5 |
规则要点:
- 提交信息的第一行(主题行)不能超过 72 个字符。
type和description之间用:(冒号+空格)分隔。description使用祈使语气(如add而非added)。- 末尾不加句号。
- 破坏性变更(BREAKING CHANGE)可以在
footer中用BREAKING CHANGE: description标记,或直接在type/scope后加!(如feat!: drop support for Node 12)。
为什么需要规范提交信息?
- 自动化版本管理:工具(如
semantic-release)可根据提交类型自动确定下一个版本号(feat→ 次版本号递增,fix→ 修订号递增,BREAKING CHANGE→ 主版本号递增)。 - 自动生成 CHANGELOG:从提交历史中提取
feat和fix条目,生成版本发布说明。 - 提升可读性:任何团队成员或未来的维护者都能快速理解每次提交的意图。
3.2 commitlint:校验提交信息
commitlint 是一个检查提交信息是否符合配置规则的工具,通常与 Husky 的 commit-msg hook 结合使用。
安装:
1 | npm install -D @commitlint/cli @commitlint/config-conventional |
创建配置文件 commitlint.config.js(或 .commitlintrc.js):
1 | // commitlint.config.js |
在 Husky 中添加 commit-msg hook:
1 | npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1' |
或直接编辑 .husky/commit-msg:
1 |
|
--no 参数告诉 npm 不使用已有的 node_modules(可省略,但作为习惯保留),--edit $1 表示读取 Git 传入的提交信息文件并校验。
现在,当执行 git commit -m "add login" 时,commitlint 会报错:
1 | ⧗ input: add login |
正确的提交:git commit -m "feat: add login page" 将通过校验。
4. 综合实战:搭建完整的提交规范工作流
以下步骤在一个已初始化 Git 的前端项目中完成全部配置:
4.1 安装依赖
1 | npm install -D husky lint-staged @commitlint/cli @commitlint/config-conventional |
4.2 初始化 Husky
1 | npx husky init |
4.3 配置 lint-staged
在 package.json 中添加:
1 | { |
4.4 创建 pre-commit hook
编辑 .husky/pre-commit:
1 |
|
4.5 创建 commit-msg hook
编辑 .husky/commit-msg:
1 |
|
4.6 配置 commitlint
创建 commitlint.config.js:
1 | module.exports = { |
4.7 验证
1 | git add . |
5. 最佳实践与常见陷阱
5.1 使用 git commit --no-verify 跳过 Hooks
在某些紧急情况下(如快速修复后立即部署),可以使用 --no-verify 跳过 pre-commit 和 commit-msg hooks。但这不应成为习惯——跳过检查意味着将未验证的代码推送到仓库。
5.2 Husky 在 CI 环境中的行为
在 CI 环境中,prepare 脚本可能不会运行(取决于 NODE_ENV 和包管理器)。可以通过环境变量 HUSKY=0 禁用 Husky:
1 | HUSKY=0 npm install |
大多数 CI 平台(如 GitHub Actions、GitLab CI)会自动设置 CI=true,Husky 会检测该变量并跳过安装。
5.3 commitlint 规则不能过于严苛
规则是服务于团队的。如果提交类型列表过于严格(仅允许 3 种 type),会导致开发者在紧急提交时频繁使用 --no-verify。建议保留常用的 6-8 个 type,允许团队在实际需要时扩展。
5.4 Husky 目录必须被 Git 跟踪
确保 .husky/ 目录被提交到 Git 仓库(不要加入 .gitignore),否则其他团队成员在克隆仓库后无法启用 hooks。
课后练习
一、概念自测(选择题 / 填空题)
(单选) Git Hooks 中,
pre-commithook 的触发时机是?
A. 提交信息编辑完成后,提交真正发生前。
B. 执行git commit命令后,提交信息编辑器打开之前。
C. 代码推送到远程仓库后。
D. 合并请求完成后。(单选) Husky 的主要作用是什么?
A. 自动生成 CHANGELOG。
B. 将 Git Hooks 的配置纳入项目版本控制,简化 hook 管理。
C. 代替 ESLint 进行代码检查。
D. 生成提交信息的统计报告。(填空) Conventional Commits 规范中,新增一个登录功能,正确的提交信息格式为:
______: add login functionality。(多选) 以下哪些属于 Conventional Commits 推荐的
type?
A.feat
B.bug
C.docs
D.chore
二、AI 编程任务:编写面向 AI 的提示词
场景:你需要为一个已有的 Vue 3 项目配置 Husky + commitlint + lint-staged 工作流。要求如下:
- 安装所需依赖:
husky、lint-staged、@commitlint/cli、@commitlint/config-conventional。 - 初始化 Husky,创建
.husky/pre-commit(运行npx lint-staged)和.husky/commit-msg(运行npx commitlint --edit $1)。 - 配置
lint-staged:对.js/.ts/.vue文件运行eslint --fix和prettier --write,对.css/.scss/.md运行prettier --write。 - 创建
commitlint.config.js,继承@commitlint/config-conventional,并自定义header-max-length: 100。 - 在
package.json中添加"prepare": "husky"脚本。
任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成所有需要的配置文件(package.json 相关字段、.husky/pre-commit、.husky/commit-msg、commitlint.config.js)。提示词中需明确指定每个 hook 的命令和 lint-staged 的匹配规则。
三、Agent 模式下的提示词示例
你是一个资深前端开发 Agent。请为一个 Vue 3 项目配置 Git Hooks 工作流。需要生成/修改以下内容:
- 安装
husky、lint-staged、@commitlint/cli、@commitlint/config-conventional为 devDependencies。- 运行
npx husky init初始化(或直接在.husky/目录下生成文件)。.husky/pre-commit:内容为npx lint-staged。.husky/commit-msg:内容为npx --no -- commitlint --edit $1。package.json中添加"lint-staged"字段("*.{js,ts,vue}": ["eslint --fix", "prettier --write"]、"*.{css,scss,md}": ["prettier --write"])和"scripts": { "prepare": "husky" }。commitlint.config.js:extends: ['@commitlint/config-conventional'],rules: { 'header-max-length': [2, 'always', 100] }。- 在
README.md中简要说明提交规范和如何跳过 hook(git commit --no-verify)。
所有文件添加必要注释。完成后列出所有生成的文件内容。
四、面试真题与参考答案
题目(滴滴前端面试题):
请解释 Git Hooks 和 Husky 的关系,以及为什么在现代前端工程中推荐使用 Husky 而非手动编写 Git Hooks?结合
commitlint说明如何通过commit-msghook 强制执行提交信息规范。如果开发者使用git commit --no-verify跳过了 hook,团队应如何看待和处理?
参考答案:
Git Hooks 是 Git 提供的自动化脚本机制,可以在特定 Git 操作前后自动执行。Husky 是基于 Git Hooks 的封装工具,它将 hooks 脚本从 .git/hooks/ 迁移到项目根目录的 .husky/ 文件夹中,使其可以被 Git 版本控制,团队成员克隆仓库后通过 npm install 自动启用。与手动编写 shell 脚本相比,Husky 配置简单、可共享、与 Node.js 生态无缝集成。
通过 commit-msg hook 配合 commitlint,可以在提交信息保存后、提交真正发生前执行规则校验。如果提交信息不符合 Conventional Commits 格式(如缺少 type、type 不在允许列表中),commitlint 以非零状态码退出,Git 会中止提交,并输出明确的错误提示,引导开发者修正提交信息。
git commit --no-verify 是一个合理的应急逃生口(如紧急修复需要立即部署),但它绕过了所有检查,可能导致不符合规范的代码或提交信息进入仓库。团队应在分支保护规则中在远程端设置二次验证(如 GitHub Branch Protection Rules 或 CI 流水线检查提交信息),并定期审计使用 --no-verify 的提交记录。对于频繁滥用的情况,应进行团队规范和自动化工具使用的宣导,而非简单地禁用该选项。
课后练习答案
一、概念自测答案
B
- 解析:
pre-commit在git commit执行后、提交信息编辑器打开前触发,用于检查暂存区代码。commit-msg是提交信息编辑完成后触发(A 描述的是commit-msg)。
- 解析:
B
- 解析:Husky 将 Git Hooks 的配置纳入项目仓库,通过 npm scripts 自动启用和管理,解决原生 hooks 难以版本控制和共享的问题。A 是
semantic-release等工具的职责;C、D 错误。
- 解析:Husky 将 Git Hooks 的配置纳入项目仓库,通过 npm scripts 自动启用和管理,解决原生 hooks 难以版本控制和共享的问题。A 是
feat- 解析:Conventional Commits 中,新功能使用
feat类型。
- 解析:Conventional Commits 中,新功能使用
A、C、D
- 解析:
feat、docs、chore都是标准 type。bug不是,应该用fix。
- 解析:
二、AI 编程任务参考答案(提示词示例)
示例提示词:
“请为我的 Vue 3 项目配置 Husky + commitlint + lint-staged。要求:
- 安装
husky、lint-staged、@commitlint/cli、@commitlint/config-conventional。husky init后生成.husky/pre-commit(内容npx lint-staged)和.husky/commit-msg(内容npx --no -- commitlint --edit $1)。package.json添加"lint-staged"配置(JS/TS/Vue 文件运行eslint --fix+prettier --write,CSS/SCSS/MD 文件运行prettier --write),添加"prepare": "husky"。commitlint.config.js继承@commitlint/config-conventional,设置header-max-length: 100。- 直接输出所有配置文件的内容,附简要注释。”