Git Hooks 实战:用 pre-commit 和 commit-msg 提升代码质量

代码质量从来不只是代码审查阶段的事。很多问题——格式不一致、调试语句残留、提交信息混乱——如果在提交那一刻就被拦截,团队能省下大量返工和沟通成本。Git Hooks 正是实现这一目标的核心机制,而 pre-commitcommit-msg 是其中使用频率最高、收益最直接的两个钩子。

为什么选择 Git Hooks 而非 CI

CI 流水线当然也能做检查,但它有两个天然短板:反馈慢和修复成本高。开发者推送代码后等几分钟才看到 lint 失败,此时上下文已经切换,修复动力大打折扣。Git Hooks 在本地提交时立即执行,问题在离开开发者机器之前就被解决。

另一个现实因素是 CI 资源。把格式检查、调试语句扫描这类轻量级校验放在本地钩子中完成,CI 只负责集成测试和构建验证,流水线会跑得更快、更稳定。

pre-commit:提交前的质量闸门

pre-commit 钩子在 git commit 执行时、提交信息编辑器打开之前触发。如果脚本以非零状态退出,提交会被中止。这意味着它是拦截问题代码的最后一道本地防线。

常见的 pre-commit 检查项

一个实用的 pre-commit 通常包含以下几类检查:

  • 代码格式:运行 Prettier、Black、gofmt 等工具,确保提交的代码符合团队规范
  • 静态分析:ESLint、Flake8、RuboCop 等 linter 快速扫描潜在错误
  • 调试残留:搜索 console.logdebuggerbinding.pry 等调试语句
  • 敏感信息:检测是否误提交了 .env 文件、API Key 或私钥
  • 大文件:阻止超过阈值的大文件进入仓库历史

手写一个 pre-commit 脚本

.git/hooks/ 目录下创建 pre-commit 文件(去掉 .sample 后缀),写入以下内容:

#!/bin/sh

# 获取暂存区的文件列表
staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|jsx|tsx)$')

if [ -z "$staged_files" ]; then
  exit 0
fi

# 检查调试语句
if echo "$staged_files" | xargs grep -n 'console\.log\|debugger' 2>/dev/null; then
  echo "❌ 检测到调试语句,请移除后再提交。"
  exit 1
fi

# 运行 ESLint
echo "$staged_files" | xargs npx eslint --quiet
if [ $? -ne 0 ]; then
  echo "❌ ESLint 检查未通过,请修复后重新提交。"
  exit 1
fi

echo "✅ pre-commit 检查通过"
exit 0

别忘了赋予执行权限:

chmod +x .git/hooks/pre-commit

这个脚本只检查暂存区的文件,而不是整个项目,这样在大型仓库中也能保持快速响应。

commit-msg:规范提交信息

commit-msg 钩子在开发者编写完提交信息后触发,接收一个参数——包含提交信息的临时文件路径。它最常见的用途是强制团队遵循统一的提交信息格式,比如 Conventional Commits。

为什么提交信息值得强制规范

  • 自动化 changelogfeat:fix:BREAKING CHANGE: 等前缀可以被工具解析,自动生成版本变更日志
  • 语义化版本:结合工具可以根据提交类型自动决定版本号递增策略
  • 可追溯性:规范的提交信息让 git log 真正成为可读的项目历史

实现 commit-msg 校验

创建 .git/hooks/commit-msg

#!/bin/sh

commit_msg_file=$1
commit_msg=$(head -1 "$commit_msg_file")

# 允许 Merge 和 Revert 提交跳过检查
if echo "$commit_msg" | grep -qE '^(Merge|Revert)'; then
  exit 0
fi

# 定义允许的类型
pattern='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,72}$'

if ! echo "$commit_msg" | grep -qE "$pattern"; then
  echo "❌ 提交信息格式不符合规范。"
  echo ""
  echo "格式:<type>(<scope>): <subject>"
  echo "示例:feat(auth): 添加 OAuth2 登录支持"
  echo ""
  echo "允许的类型:feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert"
  exit 1
fi

exit 0

同样需要执行权限:

chmod +x .git/hooks/commit-msg

这个正则还限制了主题行不超过 72 个字符,这是 Git 社区的广泛共识,能保证 git log --oneline 的显示效果。

团队协作中的关键问题:钩子如何共享

.git/hooks/ 目录不会被 Git 跟踪,这意味着你写的钩子无法通过 git clone 分发给团队成员。解决这个问题有三种主流方案:

方案一:Husky(Node.js 生态)

Husky 是目前前端项目中最流行的方案。安装后,它会把钩子配置写入 package.json,随项目一起版本控制:

npx husky init
echo "npx lint-staged" > .husky/pre-commit

配合 lint-staged,可以只对暂存文件运行检查,速度极快。

方案二:pre-commit 框架(Python 生态)

pre-commit 这个工具本身与 Git 的 pre-commit 钩子同名但不同概念。它通过 .pre-commit-config.yaml 管理钩子,支持多语言,安装简单:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-added-large-files

方案三:自定义 hooks 目录

通过 git config core.hooksPath .githooks 将钩子目录指向项目内的 .githooks/ 文件夹,然后把这个目录提交到仓库。这是最轻量的方案,不依赖任何额外工具。

性能与体验的平衡

钩子太慢会严重影响开发体验。几个实用建议:

  • 只检查暂存文件,不要全量扫描
  • 并行执行独立检查项
  • 提供跳过机制:允许通过 git commit --no-verify 绕过(紧急情况用),但要在团队规范中明确使用条件
  • 失败信息要具体:告诉开发者哪个文件、哪一行出了问题,以及如何修复

总结

pre-commitcommit-msg 两个钩子覆盖了代码质量的两个关键维度:代码本身的质量和提交历史的质量。前者通过自动化检查拦截低级错误,后者通过规范约束让项目历史保持可读和可追溯。

落地时建议循序渐进:先加一个最简单的格式检查钩子,让团队适应这种工作方式,再逐步增加检查项。工具的选择上,Node.js 项目用 Husky + lint-staged,Python 项目用 pre-commit 框架,其他场景用 core.hooksPath 自定义方案即可。关键是让钩子成为团队工作流的一部分,而不是一个被 --no-verify 频繁绕过的摆设。

未经允许不得转载:任鹏个人博客 » Git Hooks 实战:用 pre-commit 和 commit-msg 提升代码质量

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏