10. 提交代码自动检查:Git Hooks 实战,质量不再靠人工

「Git」系列已发布: 00. 改崩代码不敢回退?Git 版本控制 3 分钟救你狗命 | 01. Git 装完不会配?Windows/macOS/Linux 三端配置一次到位 | 02. Git 配置总踩坑?全局/仓库/SSH 密钥一次搞懂 | 03. add/commit/push 别再死记:Git 工作流核心概念实操 | 04. Git 分支管理:5 步彻底搞懂 branch 与 merge | 05. clone/fetch/pull/push 分不清?Git 远程协作实战 | 06. 给版本打上永久印记:Git 标签管理与发布实战 | 07. 切分支怕丢改动?Git stash 暂存恢复保护现场 | 08. rebase 不是 merge 的替代品:Git 故事线重构进阶 | 09. Git 日志与调试:log/blame/diff 让历史无所遁形 | 10. 提交代码自动检查:Git Hooks 实战,质量不再靠人工(本文) | 11. 多仓库依赖怎么管?Git 子模块统一版本一键更新

pre-commit 拦截坏代码、commit-msg 规范信息、pre-push 跑测试。Git Hooks 把质量门禁前移到提交那一刻,人工 review 省一半力。

Git 基础操作(第 03 篇)要熟练。下面这些 Hooks 脚本在任意 Git 仓库都能测试,建议拿个测试仓库练手。

一、Hooks 是什么

Git Hooks 就是在特定 Git 事件发生时自动触发的脚本。它们躺在.git/hooks/目录里,是 Git 内置的自动化扩展点。


# 看看仓库里有哪些示例 Hooks

ls -la .git/hooks/

# applypatch-msg.sample  pre-applypatch.sample  pre-rebase.sample

# commit-msg.sample      pre-commit.sample      update.sample

# post-update.sample     pre-push.sample

这些
.sample文件只是示例模板,默认不会执行。激活一个 Hook 很简单:去掉.sample后缀,赋予可执行权限。


cp .git/hooks/pre-commit.sample .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

1.1 Hooks 分类与触发时机

按作用域分成客户端和服务端两类:

1.1.1 客户端 Hooks

Hook 什么时候触发 能干什么
pre-commit git commit执行前 代码风格检查、单元测试、拦截调试代码
prepare-commit-msg 提交信息编辑器启动前 动态生成提交信息模板
commit-msg 提交信息编辑完成后 校验提交信息格式
post-commit 提交完成后 通知、日志
post-checkout git checkout后 环境初始化、依赖安装
post-merge git merge后 依赖更新、通知
pre-rebase git rebase前 禁止在主分支上变基
pre-push git push前 跑完整测试套件

1.1.2 服务端 Hooks

Hook 什么时候触发 能干什么
pre-receive 接收推送前 权限检查、提交信息校验
update 每个分支推送时 分支权限控制
post-receive 接收推送后 触发 CI/CD、发通知

1.2 实战 Hooks

1.2.1 pre-commit:提交前自动检查


#!/bin/bash

# .git/hooks/pre-commit

# 提交前自动跑代码检查

set -e
echo "=== 执行 pre-commit 检查 ==="

# 1. 拦截调试代码

if git diff --cached --name-only | xargs grep -n "console\.log\|debugger\|print(" 2>/dev/null; then
echo "错误:提交里有调试代码(console.log / debugger / print),去掉再提交。"
exit 1
fi

# 2. 拦截敏感文件

SENSITIVE_PATTERNS="\.env$|\.pem$|\.key$|credentials\.json$"
if git diff --cached --name-only | grep -E "$SENSITIVE_PATTERNS" 2>/dev/null; then
echo "错误:检测到敏感文件,确认是否该加到 .gitignore。"
exit 1
fi

# 3. Python 代码格式检查

if git diff --cached --name-only | grep "\.py$" > /dev/null; then
echo "检查 Python 代码格式..."
if ! command -v ruff &> /dev/null; then
echo "警告:没装 ruff,跳过格式检查。"
else
git diff --cached --name-only | grep "\.py$" | xargs ruff check
fi
fi
echo "pre-commit 检查通过 ✓"

1.2.2 commit-msg:强制提交信息格式

#!/bin/bash

# .git/hooks/commit-msg

# 校验提交信息是否符合约定式提交

COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
CONVENTIONAL_PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\)?: .{1,50}"
if ! echo "$COMMIT_MSG" | head -1 | grep -qE "$CONVENTIONAL_PATTERN"; then
echo "========================================="
echo "提交信息格式不对!"
echo ""
echo "请用约定式提交格式:"
echo "  <type>(<scope>): <简短描述>"
echo ""
echo "type 取值:feat / fix / docs / style / refactor / test / chore / perf / ci / build / revert"
echo ""
echo "示例:"
echo "  feat(auth): 添加双因素认证"
echo "  fix(api): 修复超时重试逻辑"
echo "========================================="
exit 1
fi

1.2.3 pre-push:推送前跑测试

#!/bin/bash

# .git/hooks/pre-push

# 推送前自动跑测试

echo "=== 执行 pre-push 检查 ==="
BRANCH=$(git rev-parse --abbrev-ref HEAD)

# main/master 分支:跑完整测试

if [[ "$BRANCH" == "main" || "$BRANCH" == "master" ]]; then
echo "检测到 main 分支推送,运行完整测试套件..."
make test-full
if [ $? -ne 0 ]; then
echo "测试没过,推送已取消。"
exit 1
fi
fi

# feature 分支:跑快速测试

if [[ "$BRANCH" == feature/* ]]; then
echo "检测到 feature 分支推送,运行快速测试..."
make test-quick
if [ $? -ne 0 ]; then
echo "测试没过,推送已取消。"
exit 1
fi
fi
echo "pre-push 检查通过 ✓"

1.2.4 post-merge:自动装依赖

#!/bin/bash

# .git/hooks/post-merge

# 合并后发现依赖文件变了就自动装

if git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD | grep -qE "package\.json|yarn\.lock|requirements\.txt|Pipfile"; then
echo "检测到依赖文件变更,自动安装..."
if [ -f "package.json" ]; then
npm install
fi
if [ -f "requirements.txt" ]; then
pip install -r requirements.txt
fi
echo "依赖安装完成 ✓"
fi

1.3 团队共享 Hooks

Hooks 存在.git/hooks/里,不会被 push 到远程。要让团队用同一套 Hooks,有几种办法:

1.3.1 方案一:项目级脚本 + 安装脚本

在项目根目录建.githooks/放 Hooks,再给个安装脚本:

#!/bin/bash

# scripts/install-hooks.sh

HOOKS_DIR="$(git rev-parse --git-dir)/hooks"
PROJECT_HOOKS="$(git rev-parse --show-toplevel)/.githooks"
for hook in "$PROJECT_HOOKS"/*; do
hook_name=$(basename "$hook")
cp "$hook" "$HOOKS_DIR/$hook_name"
chmod +x "$HOOKS_DIR/$hook_name"
echo "已安装 Hook: $hook_name"
done

然后在
README 或贡献指南里注明克隆后要跑bash scripts/install-hooks.sh。

1.4 方案二:core.hooksPath

Git 2.9+ 支持自定义 Hooks 路径:

git config core.hooksPath .githooks

这样团队成员直接指向项目中版本控制的
.githooks/目录就行。

1.4.1 方案三

Husky(Node.js 生态)


npm install --save-dev husky
npx husky init
echo "npm test" > .husky/pre-commit

1.5 Hooks 调试与小贴士

1.5.1 调试


# 手动跑 Hook 脚本

GIT_DIR=.git .git/hooks/pre-commit

# 开详细输出

#!/bin/bash
set -x  # 每条命令都打印
set -e  # 遇错就停

# 记录执行日志

exec 1> >(tee -a /tmp/git-hook.log) 2>&1
echo "$(date): pre-commit hook triggered"

1.5.2 跳过 Hooks

紧急情况需要临时跳过:


# 跳过 pre-commit 和 commit-msg

git commit --no-verify -m "紧急修复"

# 跳过 pre-push

git push --no-verify

1.5.3 性能注意

  • pre-commit Hook 要快(2 秒内),耗时的检查放 pre-push。
  • 用git diff --cached --name-only只检查暂存区,别扫整个工作目录。
  • 能缓存的结果就缓存,别每次都来一遍。

1.6 文章引用

📌 本文首发于微信公众号 【运维知识点滴】——专注运维实战干货,微信搜索“运维知识点滴”或扫下方二维码关注,更新不迷路。

微信公众号【运维知识点滴】 微信小程序【A5单据合并&大试卷拆分转A4打印】
运维实战干货 · 关注追更 Word 转 PDF · 试卷/发票打印神器 · 免费
运维实战干货 · 关注追更 Word 转 PDF · 试卷/发票打印神器 · 免费

💡 小程序【A5单据合并&大试卷拆分转A4打印】是干嘛的? 一个 Word 转 PDF 的打印神器,专治“文档版式和打印机对不上”:

  • A3 试卷打不出来? 学校发的电子版试卷多是 A3,家用打印机只吃 A4——上传 Word 一键转换:可缩到一页 A4(页数不变),也可对半拆成两张 A4(1:1 原大、字更清晰),两种模式自选;
  • 版式不乱:真实渲染引擎原样转换、绝不重排内容,转出的 PDF 和原 Word 排版一致,公式/表格/图片不跑版;
  • 两张 A5 单据拼一张 A4:出租车票、火车票等报销凭证拼版后,一次打印、整齐粘贴;
  • 隐私省心:无需注册,转换结果留存 5 天、最多下载 5 次即自动删除,不留任何历史记录;
  • 微信扫码即用,免费、免安装,支持 doc / docx(≤50MB)。

上一篇:09. Git 日志与调试:log/blame/diff 让历史无所遁形 | 下一篇:11. 多仓库依赖怎么管?Git 子模块统一版本一键更新