Git钩子脚本如何检查合规门禁

wen 实用脚本 1

本文目录导读:

Git钩子脚本如何检查合规门禁

  1. 核心思路
  2. 实战案例:客户端 pre-commit 钩子
  3. 实战案例:客户端 commit-msg 钩子
  4. 实战案例:服务器端 pre-receive 钩子 (以 GitLab/GitHub 为例)
  5. 如何管理和分发钩子脚本?

Git 钩子脚本是检查合规门禁(例如代码规范、敏感信息、提交信息格式等)的最佳工具,它能在代码进入远程仓库之前(客户端钩子)或合并到主分支之前(服务器端钩子)自动执行检查。

下面详细讲解如何通过 Git 钩子脚本实现常见的合规门禁检查。

核心思路

  1. 选择钩子类型:不同的门禁检查点对应不同的 Git 事件。

    • pre-commit:在用户输入 git commit 之后、生成提交对象之前触发。最常用的客户端门禁,用于检查暂存区(Staging Area)的代码。
    • commit-msg:在用户输入提交信息后触发,用于检查提交信息格式(如是否符合 Conventional Commits 规范)。
    • pre-push:在用户输入 git push 之后、实际推送之前触发,可以执行更耗时的检查(如完整构建、集成测试)。
    • pre-receiveupdate服务器端钩子,部署在 Git 服务器(如 GitLab, Gitea)上,用于强制执行全局门禁,例如禁止向 master 分支推送不符合规范的代码。
  2. 编写脚本:钩子本质上是可执行的脚本文件(Shell, Python, Node.js 等均可)。

  3. 通用逻辑

    • 如果检查通过,脚本 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: xxxfix: 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

作用

  • 黄金镜像:防止任何未遵守规范的代码进入 mastermain 分支。
  • 安全性:阻止包含密码或密钥的代码被推送到远程。
  • 审计:强制要求每次提交都关联任务单号。

如何管理和分发钩子脚本?

由于 .git/hooks 不会被 git push,团队协作时需要用以下方法共享:

  1. 存储在项目目录中(推荐):

    • 在项目根目录创建文件夹:mkdir .githooks
    • 将以上脚本放入 .githooks/
    • 每个开发者运行一次:git config core.hooksPath .githooks (此设置会被克隆到本地 .git/config,但不会自动同步,需要团队约定)。
    • 更优雅的方式:使用 git init--template 参数或写一个 setup.sh 脚本让开发者运行一次。
  2. 使用第三方工具管理

    • Husky (前端项目最常见):通过 package.json 定义钩子,npm install 时自动配置。
    • pre-commit (Python):一个框架,可以在 .pre-commit-config.yaml 中配置各种检查器,自动下载和安装。
门禁类型 推荐钩子 检查侧重点
代码质量 pre-commit ESLint, Prettier, 静态分析
安全性 pre-commit 敏感信息扫描, 大文件检查, .env 检查
提交信息 commit-msg 格式规范 (Conventional Commits), 关联 Issue
集成测试 pre-push 完整构建, 集成测试 (耗时较长)
全局合规 pre-receive (服务器端) 强制执行所有团队规范, 阻止违规代码入库

最佳实践

  • 客户端钩子 (pre-commit, commit-msg) 负责快速反馈,减少大家等待 CI/CD 的时间。
  • 服务器端钩子 (pre-receive) 作为最终防线,防止任何绕过客户端检查的情况发生。
  • 脚本应尽可能快,没人愿意等一个慢吞吞的钩子。
  • 提供明确的错误信息,告诉开发者哪里错了以及如何修复。

抱歉,评论功能暂时关闭!