← 中文资源

简体中文版本

Git 钩子与 CI 流水线门禁

用 pre-commit 钩子做本地质量门禁:只查暂存区变更文件、shellcheck 静态检查、密钥模式粗筛,CI 流水线镜像同样的阶段。

阅读英文原文

示例说明

质量门禁越靠近提交越便宜——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