示例说明
质量门禁越靠近提交越便宜——pre-commit 钩子在本地几秒拦截,比 CI 十分钟后打回省一个来回。钩子脚本的关键细节:只检查暂存区文件(diff-filter 过滤增改),提交里的部分暂存不会被误检;密钥粗筛用常见凭据前缀模式在 diff 上匹配,防泄漏的第一道闸。CI 里跑同样的检查保证绕过钩子的提交(–no-verify)也会被拦。
技术要点
- diff-filter 只查真实变更的暂存文件
- 密钥模式在 diff 上粗筛
- 本地钩子与 CI 阶段双保险
代码解读
staged 数组来自 git diff –cached 的文件清单;lint_shell 过滤出 shell 脚本交给 shellcheck,失败即阻止提交;check_secrets 逐文件在暂存 diff 里匹配三类高风险模式——AWS 密钥、支付测试密钥、私钥头。末尾注释展示 GitLab CI 的镜像阶段,本地钩子可绕但 CI 不可。
使用建议
钩子要随仓库分发——pre-commit 框架的配置文件方案优于手工 .git/hooks。密钥扫描生产用 gitleaks 等专业工具。跳过钩子的提交在 CI 侧必须同样受阻。
代码示例
#!/usr/bin/env bash
# .git/hooks/pre-commit —— 提交前的本地质量门禁
set -euo pipefail
fail() { echo "Error: $*" >&2; exit 1; }
command -v shellcheck >/dev/null || fail "shellcheck 未安装"
# 只检查暂存区里真实变更的文件
mapfile -t staged < <(git diff --cached --name-only --diff-filter=ACM)
lint_shell() {
local scripts=()
for f in "${staged[@]}"; do
[[ "$f" == *.sh ]] && scripts+=("$f")
done
(( ${#scripts[@]} == 0 )) && return 0
shellcheck "${scripts[@]}" || fail "shellcheck 未通过,请修复后重提"
}
check_secrets() {
# 密钥模式粗筛:防凭据误入版本库
local pattern='(AKIA[0-9A-Z]{16}|sk-live-[a-zA-Z0-9]{20,}|BEGIN RSA PRIVATE KEY)'
for f in "${staged[@]}"; do
[[ -f "$f" ]] || continue
if git diff --cached -- "$f" | grep -E "${pattern}" >/dev/null; then
fail "疑似密钥泄漏: ${f}"
fi
done
}
lint_shell
check_secrets
echo "pre-commit 检查通过:${#staged[@]} 个文件"
# CI 流水线中的镜像阶段(.gitlab-ci.yml 节选):
# stages: [lint, test]
# lint:shellcheck:
# stage: lint
# script:
# - shellcheck scripts/*.sh
# lint:secrets:
# stage: lint
# script:
# - gitleaks detect --source . --staged