本文目录导读:

- 核心思路
- 实战案例:客户端
pre-commit钩子 - 实战案例:客户端
commit-msg钩子 - 实战案例:服务器端
pre-receive钩子 (以 GitLab/GitHub 为例) - 如何管理和分发钩子脚本?
Git 钩子脚本是检查合规门禁(例如代码规范、敏感信息、提交信息格式等)的最佳工具,它能在代码进入远程仓库之前(客户端钩子)或合并到主分支之前(服务器端钩子)自动执行检查。
下面详细讲解如何通过 Git 钩子脚本实现常见的合规门禁检查。
核心思路
-
选择钩子类型:不同的门禁检查点对应不同的 Git 事件。
pre-commit:在用户输入git commit之后、生成提交对象之前触发。最常用的客户端门禁,用于检查暂存区(Staging Area)的代码。commit-msg:在用户输入提交信息后触发,用于检查提交信息格式(如是否符合 Conventional Commits 规范)。pre-push:在用户输入git push之后、实际推送之前触发,可以执行更耗时的检查(如完整构建、集成测试)。pre-receive或update:服务器端钩子,部署在 Git 服务器(如 GitLab, Gitea)上,用于强制执行全局门禁,例如禁止向master分支推送不符合规范的代码。
-
编写脚本:钩子本质上是可执行的脚本文件(Shell, Python, Node.js 等均可)。
-
通用逻辑:
- 如果检查通过,脚本
exit 0,操作继续。 - 如果检查失败,脚本
exit 1,操作立即被中止(commit 或 push 会被拒绝),并输出错误信息。
- 如果检查通过,脚本
实战案例:客户端 pre-commit 钩子
这个钩子用于在提交前检查代码质量、格式和安全性。
文件位置:.git/hooks/pre-commit(在你的本地仓库目录中),注意:.git/hooks 下的文件不会被 git push 同步到远程仓库,如果需要在团队间共享,通常将钩子脚本存储在项目目录中(如 .githooks/pre-commit),然后通过 git config core.hooksPath .githooks 让 Git 指向那里。
示例脚本 (Shell):检查代码格式 (ESLint) 和 禁止提交敏感信息
#!/bin/bash
# .githooks/pre-commit
echo "🏁 执行 Pre-commit 合规检查..."
# --- 配置区 ---
# 1. 代码格式检查 (以 JavaScript/TypeScript 的 ESLint 为例)
run_eslint_check() {
echo " 1) 检查代码格式 (ESLint)..."
# 获取所有暂存 (staged) 的 .js, .ts, .jsx, .tsx 文件
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|jsx|tsx)$')
if [ -z "$STAGED_FILES" ]; then
echo " ✅ 没有需要检查的 JS/TS 文件"
return 0
fi
# 运行 ESLint 只检查这些暂存文件
npx eslint --format compact $STAGED_FILES
# 检查 ESLint 的退出码
if [ $? -ne 0 ]; then
echo " ❌ 代码格式不通过,请修复后重新 add/commit"
return 1 # 检查失败
fi
echo " ✅ 代码格式通过"
return 0
}
# 2. 安全/合规检查:禁止提交敏感信息 (如密码、API Key)
run_secret_scan() {
echo " 2) 扫描敏感信息..."
# 获取所有暂存文件的内容并传递给 grep
git diff --cached --diff-filter=ACM --name-only | while read -r file; do
# 使用 git show 获取暂存区文件内容,而不是工作区
if git show :"$file" | grep -qiE '(password|secret|api_key|token|aws_secret_key)'; then
echo " ❌ 错误: 文件 '$file' 可能包含敏感信息 (password/secret/token)!"
echo " 请移除这些信息后再提交。"
return 1
fi
done
echo " ✅ 无敏感信息泄漏"
return 0
}
# --- 执行检查 ---
# 依次运行所有检查,任一失败则整体失败
echo "运行检查..."
run_eslint_check
ESLINT_RESULT=$?
run_secret_scan
SECRET_RESULT=$?
if [ $ESLINT_RESULT -ne 0 ] || [ $SECRET_RESULT -ne 0 ]; then
echo ""
echo "❌ 合规门禁检查失败,commit 已被拒绝!"
echo "请解决上述问题后重新 git add 和 git commit"
exit 1 # 拒绝提交
fi
echo ""
echo "🏁 ✅ 所有门禁检查通过!继续提交..."
exit 0 # 允许提交
使用场景:
- 禁止调试代码:检查暂存文件中是否包含
console.log,debugger,print()等。 - 禁止大文件:检查暂存文件大小是否超过 1MB。
- 禁止特定文件:检查是否有人企图提交
.env或*.config.local文件。 - 运行单元测试:只运行与暂存文件相关的测试用例。
实战案例:客户端 commit-msg 钩子
这个钩子用于强制执行提交信息格式。
示例脚本 (Shell):检查 Conventional Commits 格式
#!/bin/bash
# .githooks/commit-msg
echo "📝 检查 Commit Message 格式..."
# 从提交信息文件中读取内容 (Git 将提交信息临时存放在 $1 参数中)
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
# 定义正则表达式:类型(可选作用域): 描述
# 类型: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
REGEX="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-zA-Z0-9._-]+\))?: .{1,100}$"
if ! echo "$COMMIT_MSG" | grep -qE "$REGEX"; then
echo ""
echo "❌ 错误: 提交信息格式不符合规范!"
echo " 格式: <type>(<scope>): <subject>"
echo " 示例: feat(user-auth): add login module"
echo " 类型: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert"
echo ""
echo " 你的提交信息是: $COMMIT_MSG"
exit 1
fi
echo "✅ 提交信息格式正确"
exit 0
使用场景:
- 要求团队统一使用
feat: xxx、fix: xxx格式。 - 要求提交信息不为空。
- 要求不包含非 ASCII 字符。
实战案例:服务器端 pre-receive 钩子 (以 GitLab/GitHub 为例)
这是最严格的全局门禁,代码必须通过服务器检查才能合并到受保护分支。
在 GitLab/GitHub 上,通常使用 pre-receive hook 文件或平台自带的 CI/CD 功能来实现。
GitLab 上的 Shell 脚本示例 (部署在服务器 /opt/gitlab/embedded/service/gitlab-shell/hooks/pre-receive 或自定义钩子目录):
#!/bin/bash
# pre-receive 钩子
# 读取标准输入,格式为: <旧修订> <新修订> <分支名>
while read oldrev newrev refname; do
# 只检查推送到 master 分支的代码
if [ "$refname" = "refs/heads/master" ]; then
echo "🔒 服务器端检查: 推送到 master 分支的代码..."
# 获取本次推送的所有新提交列表
COMMITS=$(git rev-list $oldrev..$newrev)
for commit in $COMMITS; do
# 检查提交信息是否包含 Jira Issue 编号
COMMIT_MSG=$(git log --format=%B -n 1 $commit)
if ! echo "$COMMIT_MSG" | grep -qE '(JIRA-[0-9]+|\[PROJECT-[0-9]+\])'; then
echo " ❌ 提交 $commit 缺少 Jira Issue 编号!"
echo " 提交信息: $COMMIT_MSG"
exit 1 # 拒绝推送
fi
done
# 检查是否存在泄露的密码
if git log -p $oldrev..$newrev | grep -qiE '(password=|secret=|api_key=|token=)'; then
echo " ❌ 检测到代码历史中包含敏感信息! 请修改历史后重试。"
exit 1
fi
echo " ✅ master 分支检查通过"
fi
done
exit 0
作用:
- 黄金镜像:防止任何未遵守规范的代码进入
master或main分支。 - 安全性:阻止包含密码或密钥的代码被推送到远程。
- 审计:强制要求每次提交都关联任务单号。
如何管理和分发钩子脚本?
由于 .git/hooks 不会被 git push,团队协作时需要用以下方法共享:
-
存储在项目目录中(推荐):
- 在项目根目录创建文件夹:
mkdir .githooks - 将以上脚本放入
.githooks/。 - 每个开发者运行一次:
git config core.hooksPath .githooks(此设置会被克隆到本地.git/config,但不会自动同步,需要团队约定)。 - 更优雅的方式:使用
git init的--template参数或写一个setup.sh脚本让开发者运行一次。
- 在项目根目录创建文件夹:
-
使用第三方工具管理:
- Husky (前端项目最常见):通过
package.json定义钩子,npm install时自动配置。 - pre-commit (Python):一个框架,可以在
.pre-commit-config.yaml中配置各种检查器,自动下载和安装。
- Husky (前端项目最常见):通过
| 门禁类型 | 推荐钩子 | 检查侧重点 |
|---|---|---|
| 代码质量 | pre-commit |
ESLint, Prettier, 静态分析 |
| 安全性 | pre-commit |
敏感信息扫描, 大文件检查, .env 检查 |
| 提交信息 | commit-msg |
格式规范 (Conventional Commits), 关联 Issue |
| 集成测试 | pre-push |
完整构建, 集成测试 (耗时较长) |
| 全局合规 | pre-receive (服务器端) |
强制执行所有团队规范, 阻止违规代码入库 |
最佳实践:
- 客户端钩子 (
pre-commit,commit-msg) 负责快速反馈,减少大家等待 CI/CD 的时间。 - 服务器端钩子 (
pre-receive) 作为最终防线,防止任何绕过客户端检查的情况发生。 - 脚本应尽可能快,没人愿意等一个慢吞吞的钩子。
- 提供明确的错误信息,告诉开发者哪里错了以及如何修复。