本文目录导读:

Git 钩子脚本可以通过在本地仓库执行预定义的操作来检查代码的可追溯性门禁,其核心逻辑通常是:在代码提交或推送之前,自动解析本次变更涉及的内容,并验证这些内容是否与对应的需求、缺陷单或任务单(如 JIRA Issue、GitHub Issue)关联。
以下是一个完整的方案设计,主要包括 prepare-commit-msg 或 commit-msg 钩子(检查提交信息)以及 pre-receive 钩子(在服务端强制检查,更严格)。
核心检查逻辑
可追溯性门禁检查通常验证以下至少一项:
- 提交信息 (Commit Message) 格式:必须包含特定的
Issue-ID或Ticket-ID,[PROJECT-123] 修复了xxx。 - 分支命名规则:分支名必须包含 Issue 号,
feature/PROJECT-123-fix-bug。 - 代码注释/文件头:要求修改过的文件头部包含与该代码相关的 Issue 链接(较严格,维护成本高)。
使用 commit-msg 钩子(客户端检查)
这是最常见的实现方式,它会在提交者写入提交信息后触发。
脚本位置:<项目根目录>/.git/hooks/commit-msg
示例脚本(Bash 脚本):
#!/bin/bash
# commit-msg 钩子:检查提交信息是否符合可追溯性格式
# 获取提交信息文件路径
COMMIT_MSG_FILE="$1"
# 读取提交信息
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
# 定义规则:必须以 [项目前缀-数字] 开头,[PROJECT-123]
# 修改此处正则表达式以匹配你团队的格式
PATTERN="^\[[A-Z]+-[0-9]+\]"
# 检查是否匹配
if ! echo "$COMMIT_MSG" | head -n 1 | grep -qE "$PATTERN"; then
echo ""
echo "ERROR: 提交信息不符合可追溯性门禁要求!"
echo "提交信息第一行必须是有效的 Issue/Ticket ID, [PROJECT-123] 修复了 xxx"
echo "当前提取的第一行内容: $(echo "$COMMIT_MSG" | head -n 1)"
echo "请执行: git commit --amend -m '[你的Issue号] 新的提交描述'"
exit 1
fi
# 可选:进一步检查 Issue ID 是否在允许的白名单中(如查询 JIRA API)
# 以下为伪代码示例(需安装 jq 和 curl)
# ISSUE_ID=$(echo "$COMMIT_MSG" | grep -oP '\[([A-Z]+-[0-9]+)\]' | head -1 | tr -d '[]')
# RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -u username:token "https://your-jira.com/rest/api/2/issue/$ISSUE_ID")
# if [ "$RESPONSE" -ne 200 ]; then
# echo "ERROR: Issue $ISSUE_ID 在 JIRA 中不存在或无法访问。"
# exit 1
# fi
exit 0
使用说明:
- 将此脚本保存到
.git/hooks/commit-msg。 - 执行
chmod +x .git/hooks/commit-msg赋予执行权限。 - 开发者提交时,如果信息不满足格式,提交会被阻止。
使用 pre-receive 钩子(服务端强制检查)
为了防止开发者绕过客户端钩子,应在远程仓库(如 GitLab、Gitea、GitHub 企业版)的服务端部署 pre-receive 钩子,这是最可靠的检查方式。
脚本位置(以 Git 原生服务端为例):<仓库路径>/hooks/pre-receive
示例脚本(Bash 脚本):
#!/bin/bash
# pre-receive 钩子:拒绝不符合可追溯性门禁的推送
# 读取标准输入,格式为: <旧版本> <新版本> <分支名>
while read oldrev newrev refname; do
# 跳过删除分支的推送
if [ "$newrev" = "0000000000000000000000000000000000000000" ]; then
continue
fi
# 获取本次推送的所有新提交
for commit in $(git rev-list "$oldrev..$newrev"); do
COMMIT_MSG=$(git log --format=%B -n 1 "$commit")
FIRST_LINE=$(echo "$COMMIT_MSG" | head -n 1)
# 检查提交信息格式
PATTERN="^\[[A-Z]+-[0-9]+\]"
if ! echo "$FIRST_LINE" | grep -qE "$PATTERN"; then
echo ""
echo "ERROR: 提交 $commit 不符合可追溯性门禁!"
echo "提交信息: $FIRST_LINE"
echo "要求格式: [项目前缀-数字] 描述"
echo "请使用 'git rebase -i' 修正历史提交信息。"
# 拒绝整个推送
exit 1
fi
# 可选:分支名和提交信息 issue id 是否一致
BRANCH="$refname" # 格式: refs/heads/feature/PROJECT-123
COMMIT_ID=$(echo "$FIRST_LINE" | grep -oP '\[([A-Z]+-[0-9]+)\]' | head -1 | tr -d '[]')
if ! echo "$BRANCH" | grep -q "$COMMIT_ID"; then
echo "WARNING (非阻断): 提交 $commit 的 Issue ID 与分支名不匹配。"
echo "分支: $BRANCH, 提交 Issue: $COMMIT_ID"
# 可根据需要取消注释下一行来强制匹配
# exit 1
fi
done
done
exit 0
高级技巧与注意事项
忽略特定分支(如 main、develop)
在钩子开头判断当前分支,如果是保护分支或合并请求的基础分支,可以放宽检查(通常在合并时已有其他检查)。
集成企业 API
可以通过在钩子中调用 API 来验证「Issue 状态」(是否已关闭、是否需要 Code Review):
- 使用
curl调用 JIRA / Redmine / GitLab Issue API。 - 检查 API 返回的
status.name或resolution字段。
跳过和重试机制
- 客户端钩子可以通过设置
HUSKY=0或--no-verify跳过(不推荐,应让服务端钩子兜底)。 - 服务端钩子无法跳过,是最终防线。
脚本维护
- 将钩子脚本纳入版本管理,通过
git config core.hooksPath或工具如husky、pre-commit分发。 - 使用工具如
pre-commit(Python)或lefthook可以简化多语言环境下的钩子管理。
性能考虑
- 如果钩子中需要调用外部 API(如验证 Issue 号),建议对 API 响应进行缓存(例如存到
/tmp/目录),避免每次提交都查询。
完整示例:使用 pre-commit 框架管理
如果你使用 pre-commit 框架,可以创建一个本地钩子:
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: traceability-check
name: "Check commit message for traceability"
entry: ./scripts/check_commit_msg.sh
language: script
stages: [commit-msg]
always_run: true
对应的 scripts/check_commit_msg.sh 内容与上文的 commit-msg 脚本类似。
| 钩子类型 | 位置 | 优点 | 缺点 |
|---|---|---|---|
commit-msg |
客户端 | 快速反馈,不依赖网络 | 可被绕过,不保证强制执行 |
pre-receive |
服务端 | 强制执行,不可跳过 | 需要服务端权限,反馈较慢 |
pre-push |
客户端 | 在推送前执行,覆盖范围大 | 仍可被绕过 |
最佳实践:客户端 commit-msg + 服务端 pre-receive 双层检查,客户端给予早期反馈,服务端确保门禁最终被执行。
请务必根据你们团队的项目管理工具格式([JIRA-123] 或 #123 或 User Story:...) 调整正则表达式。