Git Hook 实战:用 pre-commit 自动校验代码和提交信息规范

团队开发中,代码规范靠"自觉"等于没有规范。有人在提交里混进 console.log,有人写 "fix bug" 的提交信息,有人在 tab 和空格之间反复横跳。这些问题不应该靠 Code Review 来发现——它们应该在提交之前就被阻止。
Git Hook 就是做这件事的。这篇文章不讲 Hook 的理论概念,直接给三套生产环境可用的自动化脚本。
一、Hook 的工作原理
Git Hook 是放在 .git/hooks/ 目录下的可执行脚本。每个 Git 操作(commit、push、merge 等)都有对应的 Hook 触发点。本文聚焦 pre-commit——在 git commit 执行前触发,如果脚本返回非零退出码,提交就会被阻止。
前置条件:你的项目根目录下有一个 .git/hooks/ 目录。进入这个目录,你会看到一系列 .sample 后缀的示例文件。把 pre-commit.sample 重命名为 pre-commit(去掉 .sample),或者直接新建一个 pre-commit 文件。
注意:Hook 脚本默认不参与版本控制。如果团队需要共享 Hook 配置,需要把脚本放到项目目录中(比如 scripts/pre-commit.sh),然后通过 Makefile 或 npm scripts 做符号链接。
二、脚本1:拦截 console.log 和 debugger
这是最常见的需求——提交到生产分支的代码里不能有调试语句。下面的脚本检查暂存区中的所有 .js 和 .vue 文件:
#!/bin/bash
# 获取暂存区中新增或修改的文件
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|vue)$')
if [ -z "$STAGED_FILES" ]; then
exit 0
fi
ERRORS=0
for FILE in $STAGED_FILES; do
# 检查 console.log(排除 console.error 和 console.warn)
if grep -n "console\.log" "$FILE" > /dev/null 2>&1; then
echo "❌ $FILE 中发现 console.log"
grep -n "console\.log" "$FILE"
ERRORS=$((ERRORS + 1))
fi
# 检查 debugger
if grep -n "debugger" "$FILE" > /dev/null 2>&1; then
echo "❌ $FILE 中发现 debugger"
grep -n "debugger" "$FILE"
ERRORS=$((ERRORS + 1))
fi
done
if [ $ERRORS -gt 0 ]; then
echo ""
echo "提交已阻止。请移除以上调试语句后重新提交。"
echo "(如需保留日志,请使用 console.error 或自定义 logger)"
exit 1
fi
exit 0
注意 --diff-filter=ACM:只检查新增(Added)、复制(Copied)、修改(Modified)的文件,不检查删除的文件(因为删了的文件已经没有内容可检查)。
配置注意:新建的 pre-commit 文件必须赋予执行权限。Windows 环境下通过 Git Bash 提交时,Hook 脚本在 Git Bash 的 bash 环境中运行,文件需要 Unix 换行符(LF),CRLF 会导致脚本无法执行:
chmod +x .git/hooks/pre-commit
三、脚本2:强制提交信息规范
提交信息写成 "fix"、"update" 或者干脆 " . "——一天收到三次这样的提交,我就会在群里 @对应的人。但与其每次手动提醒,不如用 Hook 强制执行。
下面的脚本强制提交信息必须符合特定格式。这里用的是简化版规范——要求包含类型前缀和至少 10 个字符的描述:
#!/bin/bash
# 读取提交信息(从 .git/COMMIT_EDITMSG 文件)
COMMIT_MSG=$(cat "$1" 2>/dev/null || cat ".git/COMMIT_EDITMSG")
# 规则1:不允许空提交信息
if [ -z "$COMMIT_MSG" ]; then
echo "❌ 提交信息不能为空"
exit 1
fi
# 规则2:第一行至少 10 个字符
FIRST_LINE=$(echo "$COMMIT_MSG" | head -1)
if [ ${#FIRST_LINE} -lt 10 ]; then
echo "❌ 提交信息太短(${#FIRST_LINE}字符),至少需要 10 个字符"
echo "建议格式:<类型>: <描述>"
echo "示例:feat: 添加用户登录验证模块"
exit 1
fi
# 规则3:必须以指定前缀开头
PREFIX_PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci)(:|:)"
if ! echo "$FIRST_LINE" | grep -qE "$PREFIX_PATTERN"; then
echo "❌ 提交信息格式不正确"
echo "必须以以下前缀之一开头:"
echo " feat / fix / docs / style / refactor / test / chore / perf / ci"
echo "示例:fix: 修复登录页密码框无法粘贴的问题"
exit 1
fi
exit 0
这个脚本用的是 commit-msg Hook——它在 pre-commit 之后、提交信息被保存之前运行。脚本接收的第一个参数 $1 是存放提交信息的临时文件路径。
四、脚本3:ESLint 自动修复 + 拦截告警
前两个脚本是纯拦截——不符合规则就不让提交。ESLint 的定位不同:能自动修的自动修,修不了的才拦截。这需要两步配置。
第一步:安装依赖。项目需要有 ESLint 和 lint-staged:
npm install --save-dev eslint lint-staged
然后在 package.json 中添加:
{
"lint-staged": {
"*.{js,vue}": ["eslint --fix --max-warnings 0"]
}
}
--max-warnings 0 是关键参数——即使 ESLint 自动修复成功,只要还有无法自动修复的 warning,就返回非零退出码,从而阻止提交。
第二步:在 pre-commit 中调用 lint-staged。把以下内容追加到已有的 pre-commit 脚本中:
# 运行 lint-staged
if command -v npx &> /dev/null; then
npx lint-staged
else
echo "⚠ 跳过 ESLint 检查(npx 不可用)"
fi
五、Hook 的团队共享方案
.git/hooks/ 目录不会被 Git 跟踪,所以 Hook 脚本不会随仓库同步。团队共享 Hook 有三种方案:
方案1(推荐):把 Hook 脚本放在项目目录中(如 scripts/git-hooks/),在 package.json 中用 prepare 脚本自动链接:
{
"scripts": {
"prepare": "cp scripts/git-hooks/* .git/hooks/ && chmod +x .git/hooks/*"
}
}
prepare 是 npm 的特殊生命周期脚本,在 npm install 后自动运行。每个同事执行 npm install 时,Hook 脚本会自动复制到 .git/hooks/。
方案2:使用 husky 库。husky 在处理 Git Hook 方面比手动脚本更规范,尤其是跨平台兼容性上(Windows 的 Git Bash、WSL、macOS 都能正常触发)。缺点是多了一个依赖。
提一个细节容易忽略的点:如果团队里有人用 git commit --no-verify 绕过 Hook,你可以在 CI 流程中加上相同的检查作为第二道防线——Hook 是本地防线,CI 是服务端防线。
六、下载与总结
三套脚本的定位:pre-commit 拦截调试语句 → 保证输出质量;commit-msg 规范提交信息 → 保证历史可读;ESLint 自动修复风格问题 → 减少人工 Review 负担。三者在一次 git commit 中按顺序执行,任何一个失败都会阻止提交。
Git 下载地址:Git 最新下载
AI 辅助创作声明:本文由 AI 辅助整理与撰写,内容已经过人工审校与调整。

浙公网安备 33010602011771号