本文目录导读:

- 场景一:阻止直接提交到
main/master分支 - 场景二:检查 Commit Message 中包含 Issue/PR 编号
- 场景三:在
pre-push中检查 CI 构建状态 - 场景四:检查文件是否经过安全扫描或格式校验
- 如何让团队强制使用这些钩子?
Git钩子脚本可以通过多种方式检查“透明性门禁”,这里“透明性门禁”通常指代码审查的透明性(如:必须经过Code Review、不得直接提交到主分支)或CI/CD流程的透明性(如:必须在通过所有测试、安全扫描后,才能合并或发布)。
下面是几个常见场景的Git钩子脚本实现方案,主要使用 pre-commit、commit-msg 和 pre-push 钩子。
阻止直接提交到 main / master 分支
这是最基础的“门禁”,确保所有代码必须通过PR(Pull Request)并经过审查后才能合入主分支。
脚本位置: .git/hooks/pre-commit 或 .git/hooks/pre-push
Bash):**
#!/bin/bash
# 获取当前分支名
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
# 定义受保护分支(可以扩展到多个)
PROTECTED_BRANCHES=("main" "master" "develop" "release/")
for PROTECTED in "${PROTECTED_BRANCHES[@]}"; do
if [[ "$BRANCH_NAME" == "$PROTECTED" || "$BRANCH_NAME" == $PROTECTED* ]]; then
echo "❌ 错误:不允许在 '$BRANCH_NAME' 分支上直接提交。"
echo "请创建一个功能分支(feature/xxx),提交并推送,然后通过 Pull Request 合并。"
exit 1
fi
done
exit 0
设置:
chmod +x .git/hooks/pre-commit # 或 chmod +x .git/hooks/pre-push
检查 Commit Message 中包含 Issue/PR 编号
要求每次提交都关联到一个任务或评审单,增加可追溯性(透明性)。
脚本位置: .git/hooks/commit-msg
Bash):**
#!/bin/bash
# 读取提交信息文件
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
# 检查是否包含 JIRA/Redmine Issue 编号(如 PROJ-123)
if ! echo "$COMMIT_MSG" | grep -qiE "\[(PROJ|FEAT|BUG)-[0-9]+\]|#[0-9]+"; then
echo "❌ 错误:提交信息必须包含 Issue/PR 编号([PROJ-123] 或 #42)。"
echo "示例:git commit -m \"[PROJ-456] 修复登录超时问题\""
exit 1
fi
exit 0
注意: 这只能检查格式,无法验证编号是否真实存在,如需更严格的“透明性”,可结合API(如JIRA、GitHub Issues)在 pre-push 中验证编号有效性。
在 pre-push 中检查 CI 构建状态
这是最强大的“透明性门禁”,在推送代码到远程仓库前,自动查询CI服务(如Jenkins、GitLab CI、GitHub Actions)的最近一次构建状态。
脚本位置: .git/hooks/pre-push
以GitHub API为例):**
#!/bin/bash
# 需要设置环境变量或从git配置中读取
# GITHUB_TOKEN 需要提前设置
# GIT_REPO 可以从git remote获取
REMOTE_URL=$(git remote get-url origin)
if [[ "$REMOTE_URL" == *"github.com"* ]]; then
# 解析 owner/repo
REPO=$(echo "$REMOTE_URL" | sed -n 's/.*github.com[:\/]\(.*\)\.git/\1/p')
BRANCH=$(git rev-parse --abbrev-ref HEAD)
# 查询最新commit的check suite状态(简化示例,可能需要更多处理)
LAST_COMMIT=$(git rev-parse HEAD)
STATUS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$REPO/commits/$LAST_COMMIT/check-suites" \
| jq -r '.check_suites[0].conclusion // "none"')
if [[ "$STATUS" != "success" ]]; then
echo "❌ 错误:当前 commit 的 CI 构建状态为 '$STATUS',未通过。"
echo "必须确保代码在所有门禁(测试、lint、安全扫描)通过后才能推送。"
exit 1
fi
else
echo "⚠️ 非GitHub仓库,跳过CI状态检查(或实现其他CI的检查)。"
fi
exit 0
注意:
- 需要安装
curl和jq。 - 需要配置 API Token(建议通过环境变量,不要硬编码)。
- 对性能有影响(网络请求),但能有效确保“透明性”。
检查文件是否经过安全扫描或格式校验
确保所有的 .env 文件不被提交,或所有 .java 文件都通过了格式化。
脚本位置: .git/hooks/pre-commit
检查正在暂存的文件):**
#!/bin/bash
# 获取暂存区中的文件列表(只检查新增/修改的文件)
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
# 检查是否包含敏感文件
for FILE in $STAGED_FILES; do
if [[ "$FILE" == *.env || "$FILE" == *.p12 || "$FILE" == *credential* ]]; then
echo "❌ 错误:检测到敏感文件 '$FILE',请移除后重新提交。"
exit 1
fi
done
# 检查Java文件是否符合格式化(如果没有格式化工具,可跳过)
if command -v google-java-format &> /dev/null; then
for FILE in $STAGED_FILES; do
if [[ "$FILE" == *.java ]]; then
# 检查格式(这里只是示例,实际可能需要更复杂的逻辑)
google-java-format --dry-run "$FILE"
if [ $? -ne 0 ]; then
echo "❌ 错误:文件 '$FILE' 未通过 google-java-format 检查,请执行格式化后再提交。"
exit 1
fi
fi
done
fi
exit 0
如何让团队强制使用这些钩子?
手动复制 .git/hooks/ 很麻烦且容易绕过,推荐以下方法:
-
使用工具(推荐):
- Husky(Node.js项目):通过
package.json管理钩子。 - pre-commit(Python项目):通过
.pre-commit-config.yaml配置。 - lefthook:跨语言、快、支持
pre-commit、commit-msg、pre-push。
示例(使用
lefthook的配置):# lefthook.yml pre-commit: commands: check-branch: run: bash scripts/check-branch.sh lint: run: npx eslint --fix {staged_files} pre-push: commands: check-ci: run: bash scripts/check-ci.sh - Husky(Node.js项目):通过
-
在CI中强制执行: Git钩子是本地的,用户可以
--no-verify跳过,真正的“门禁”必须放在远端(CI/CD流程中)强制执行。- GitHub: 在仓库设置中启用分支保护规则。
- GitLab: 设置“成员合并请求批准”规则。
- Jenkins/Bamboo: 在构建Job中检查提交信息、分支名称。
| 钩子类型 | 作用 | |
|---|---|---|
pre-commit |
、格式、敏感信息 | 本地快速拦截 |
commit-msg |
提交信息格式(关联Issue) | 保证可追溯性 |
pre-push |
分支名称、CI状态、API验证 | 推送前的最后一道“硬”门禁 |
最佳实践: 本地钩子做前置提醒和快速检查(格式、分支名、提交信息),真正的“透明性门禁”应在CI/CD的Pull Request/Merge Check中强制执行,两者结合,才能实现不可绕过的透明性保障。