本文目录导读:

我来详细说明如何在脚本中实现 Pre-commit 钩子:
基础 Pre-commit 钩子脚本
基础示例(Shell脚本)
#!/bin/bash
# .git/hooks/pre-commit
echo "Running pre-commit checks..."
# 获取暂存区文件
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
if [ -z "$STAGED_FILES" ]; then
echo "No files staged for commit"
exit 0
fi
# 检查是否有调试代码
for FILE in $STAGED_FILES; do
if grep -n "console.log\|debugger\|TODO" "$FILE" 2>/dev/null; then
echo "Warning: Found debug code in $FILE"
exit 1
fi
done
# 检查文件大小
for FILE in $STAGED_FILES; do
if [ -f "$FILE" ]; then
FILESIZE=$(stat -c%s "$FILE" 2>/dev/null || stat -f%z "$FILE")
if [ $FILESIZE -gt 5242880 ]; then # 5MB
echo "Error: $FILE is too large ($FILESIZE bytes)"
exit 1
fi
fi
done
echo "All checks passed!"
exit 0
语言特定的 Pre-commit 钩子
Python 项目
#!/bin/bash
# .git/hooks/pre-commit
# Python代码检查
echo "Running Python checks..."
STAGED_PY_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$')
if [ -n "$STAGED_PY_FILES" ]; then
# 语法检查
python -m py_compile $STAGED_PY_FILES 2>&1
if [ $? -ne 0 ]; then
echo "Python syntax check failed"
exit 1
fi
# Flake8代码风格检查
flake8 $STAGED_PY_FILES
if [ $? -ne 0 ]; then
echo "Flake8 style check failed"
exit 1
fi
# 运行测试
python -m pytest tests/ --quick
fi
JavaScript/Node.js 项目
#!/bin/bash
# .git/hooks/pre-commit
echo "Running JavaScript checks..."
STAGED_JS_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.js$')
if [ -n "$STAGED_JS_FILES" ]; then
# ESLint检查
npx eslint $STAGED_JS_FILES
if [ $? -ne 0 ]; then
echo "ESLint check failed"
exit 1
fi
# Prettier格式化检查
npx prettier --check $STAGED_JS_FILES
if [ $? -ne 0 ]; then
echo "Prettier formatting check failed"
exit 1
fi
fi
完整的 Pre-commit 钩子框架
#!/bin/bash
# .git/hooks/pre-commit
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 配置
MAX_LINE_LENGTH=120
BANNED_PATTERNS=("console\.log" "debugger" "TODO" "FIXME" "xxx")
BANNED_EXTENSIONS=(".min.js" ".min.css" ".map")
# 获取暂存区文件
get_staged_files() {
git diff --cached --name-only --diff-filter=ACM
}
# 检查禁止的模式
check_banned_patterns() {
local files=$1
local has_error=0
for FILE in $files; do
if [ -f "$FILE" ]; then
for pattern in "${BANNED_PATTERNS[@]}"; do
if grep -qn "$pattern" "$FILE" 2>/dev/null; then
log_error "Found '$pattern' in $FILE"
has_error=1
fi
done
fi
done
return $has_error
}
# 检查文件扩展名
check_extension() {
local files=$1
local has_error=0
for FILE in $files; do
for ext in "${BANNED_EXTENSIONS[@]}"; do
if [[ "$FILE" == *"$ext" ]]; then
log_error "Banned extension $ext found: $FILE"
has_error=1
fi
done
done
return $has_error
}
# 检查行长度
check_line_length() {
local files=$1
local has_error=0
for FILE in $files; do
if [[ "$FILE" =~ \.(py|js|ts|java|rb|go)$ ]]; then
local long_lines=$(awk "length > $MAX_LINE_LENGTH" "$FILE" 2>/dev/null | wc -l)
if [ $long_lines -gt 0 ]; then
log_warn "$FILE has $long_lines lines exceeding $MAX_LINE_LENGTH characters"
fi
fi
done
return 0 # 只是警告,不阻止提交
}
# 检查大文件
check_large_files() {
local files=$1
local max_size_mb=10
local has_error=0
for FILE in $files; do
if [ -f "$FILE" ]; then
local size_kb=$(du -k "$FILE" | cut -f1)
if [ $size_kb -gt $(($max_size_mb * 1024)) ]; then
log_error "$FILE is larger than ${max_size_mb}MB"
has_error=1
fi
fi
done
return $has_error
}
# 主函数
main() {
local staged_files=$(get_staged_files)
if [ -z "$staged_files" ]; then
log_info "No files staged for commit"
exit 0
fi
log_info "Running pre-commit checks on $(echo "$staged_files" | wc -l) files..."
# 运行所有检查
check_extension "$staged_files" || exit 1
check_banned_patterns "$staged_files" || exit 1
check_line_length "$staged_files"
check_large_files "$staged_files" || exit 1
# 运行语言特定的检查
local py_files=$(echo "$staged_files" | grep '\.py$')
local js_files=$(echo "$staged_files" | grep '\.js$')
if [ -n "$py_files" ]; then
log_info "Running Python checks..."
python -m py_compile $py_files 2>&1 || exit 1
flake8 $py_files 2>/dev/null || true # 只是警告
fi
if [ -n "$js_files" ]; then
log_info "Running JavaScript checks..."
npx eslint $js_files 2>/dev/null || exit 1
fi
log_info "All pre-commit checks passed!"
exit 0
}
# 执行主函数
main
使用 pre-commit 框架
安装和配置
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- id: check-case-conflict
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
hooks:
- id: flake8
安装框架钩子
# 安装 pre-commit pip install pre-commit # 或者使用 npm npm install -g pre-commit # 在项目中安装钩子 pre-commit install # 手动运行检查 pre-commit run --all-files # 更新钩子版本 pre-commit autoupdate
高级功能
跳过某些检查
# 使用环境变量跳过检查 git commit -m "message" --no-verify # 或者设置环境变量 SKIP=flake8 git commit -m "message"
阶段性检查
#!/bin/bash
# .git/hooks/pre-commit
# 只检查修改的部分(不是整个文件)
check_staged_lines() {
local file=$1
local staged_lines=$(git diff --cached -U0 "$file" | grep "^@@" | sed 's/.*+\([0-9]*\).*/\1/')
for line_num in $staged_lines; do
local content=$(sed -n "${line_num}p" "$file")
if echo "$content" | grep -q "console.log"; then
echo "Debug code found at line $line_num in $file"
return 1
fi
done
}
自定义检查脚本
#!/bin/bash
# scripts/pre-commit-custom.sh
run_custom_checks() {
local files=$1
# 检查文件命名规范
for file in $files; do
if [[ "$file" =~ [A-Z] ]] && [[ "$file" != *"Config"* ]]; then
echo "Warning: $file contains uppercase characters"
fi
done
# 检查提交信息格式
local commit_msg=$(git log --format=%B -n1 HEAD)
if [[ ! "$commit_msg" =~ ^(feat|fix|docs|style|refactor|test|chore): ]]; then
echo "Commit message should follow conventional commit format"
exit 1
fi
}
最佳实践
- 快速执行:钩子应该快速执行,不要运行完整的测试套件
- 清晰反馈:提供明确的错误信息和修复建议
- 可跳过:允许紧急情况下跳过检查
- 逐步增强:先添加宽松检查,逐步严格
- 团队共享:将钩子脚本保存在项目中
- 自动修复:尽可能自动修复格式问题
这些脚本可以根据项目需求进行调整和扩展,建议先在测试环境中验证钩子,然后再应用到生产环境。