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

微信图片_20260709113954_111_863

团队开发中,代码规范靠"自觉"等于没有规范。有人在提交里混进 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 辅助整理与撰写,内容已经过人工审校与调整。

posted @ 2026-07-09 11:42  PC修复电脑医生  阅读(19)  评论(0)    收藏  举报