一、理解Hooks概念

1.1 Hooks的核心定义

Hooks(钩子) 是在软件开发流程的关键节点自动触发的脚本或程序,用于自动化执行特定任务。类似于工厂生产线的自动化机器人,在特定环节自动完成质量检查、包装等工作。

1.2 Hooks在开发工作流中的作用

传统开发:手动执行任务 → 容易遗漏 → 效率低下
Hook驱动:关键事件触发 → 自动执行 → 保证一致性

1.3 Hooks的触发时机

代码提交前 → 格式化、语法检查 → pre-commit
代码推送前 → 运行测试、构建检查 → pre-push
合并请求时 → 集成测试、安全检查 → pre-merge
发布部署时 → 版本标记、部署验证 → pre-release

1.4 Hooks的主要价值

  • 一致性保证:统一团队开发规范

  • 错误预防:提前发现潜在问题

  • 效率提升:自动化重复性任务

  • 质量保障:强制执行质量标准


二、常用Hooks类型详解

2.1 代码格式化Hook

2.1.1 格式化工具选择
# 语言特定格式化工具
Python:
  - black: 代码格式化
  - isort: 导入排序
  - flake8: 风格检查
  
JavaScript/TypeScript:
  - prettier: 代码格式化
  - eslint: 代码检查
  
Go:
  - gofmt: 官方格式化
  - goimports: 导入整理
  
Rust:
  - rustfmt: 官方格式化
  - clippy: 代码检查
2.1.2 pre-commit配置示例
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 23.9.1
    hooks:
      - id: black
        args: [--line-length=88]
        files: ^src/.*\.py$
  
  - repo: https://github.com/pycqa/isort
    rev: 5.12.0
    hooks:
      - id: isort
        args: [--profile=black]
        files: ^src/.*\.py$
  
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.0.3
    hooks:
      - id: prettier
        files: ^frontend/.*\.(js|ts|jsx|tsx|css|json)$
        args: [--write]
2.1.3 格式化Hook工作流程
# 自动化格式化流程
1. 开发者修改代码
2. 执行git add .
3. 触发pre-commit hook
4. 自动运行black格式化Python代码
5. 自动运行prettier格式化前端代码
6. 检查是否通过,不通过则阻止提交
7. 通过后允许提交

2.2 测试运行Hook

2.2.1 测试Hook策略
# 分层测试Hook配置
快速测试 (pre-commit):
  - 单元测试: 快速执行,验证核心逻辑
  - 静态类型检查: mypy/pyright
  - 代码覆盖率检查: 关键文件覆盖率
  
集成测试 (pre-push):
  - 数据库集成测试
  - API端点测试
  - 外部服务模拟测试
  
完整测试 (CI/CD):
  - 端到端测试
  - 性能测试
  - 安全扫描测试
2.2.2 测试Hook配置示例
# .pre-commit-config.yaml - 测试部分
repos:
  - repo: local
    hooks:
      - id: run-unit-tests
        name: Run Unit Tests
        entry: python -m pytest tests/unit -xvs
        language: system
        pass_filenames: false
        always_run: true
        stages: [commit]
        
      - id: check-types
        name: Type Check
        entry: python -m mypy src/
        language: system
        pass_filenames: false
        always_run: true
        stages: [commit]
        
      - id: run-integration-tests
        name: Run Integration Tests
        entry: python -m pytest tests/integration -xvs
        language: system
        pass_filenames: false
        always_run: true
        stages: [push]
2.2.3 智能测试选择
#!/usr/bin/env bash
# scripts/smart-test-hook.sh
# 智能选择运行相关测试

# 检测修改的文件类型
CHANGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)

# 根据文件类型运行对应测试
if echo "$CHANGED_FILES" | grep -q "\.py$"; then
    echo "Running Python tests..."
    python -m pytest tests/unit/python -xvs
fi

if echo "$CHANGED_FILES" | grep -q "\.tsx\?$"; then
    echo "Running TypeScript tests..."
    npm test -- --testPathPattern="$(echo "$CHANGED_FILES" | grep "\.tsx\?$" | tr '\n' '|')"
fi

2.3 构建部署Hook

2.3.1 构建Hook流程
# 构建阶段Hook配置
pre-build:
  - 环境变量验证
  - 依赖完整性检查
  - 配置文件验证
  
during-build:
  - 编译检查
  - 静态资源优化
  - 依赖树优化
  
post-build:
  - 构建产物验证
  - 性能基准测试
  - 安全扫描
2.3.2 GitHub Actions构建Hook示例
# .github/workflows/build.yml
name: Build and Validate

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Pre-Build Check
      run: |
        # 检查必需的环境变量
        if [ -z "${{ secrets.DATABASE_URL }}" ]; then
          echo "❌ DATABASE_URL is not set"
          exit 1
        fi
        
        # 检查依赖完整性
        pip install pip-audit
        pip-audit
        
    - name: Setup Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
        
    - name: Install Dependencies
      run: |
        pip install -r requirements.txt
        pip install -r requirements-dev.txt
        
    - name: Run Build
      run: |
        # 执行构建
        python -m build
        
    - name: Post-Build Validation
      run: |
        # 验证构建产物
        python -m twine check dist/*
        
        # 运行性能基准
        python scripts/benchmark.py
2.3.3 Docker构建Hook
# Dockerfile with build hooks
FROM python:3.11-slim as builder

# Pre-build hook: 清理和准备
RUN apt-get update && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

# During-build hook: 依赖安装和检查
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt && \
    pip check  # 验证依赖完整性

COPY . .

# Post-build hook: 构建验证
RUN python -m py_compile src/*.py && \
    find . -name "*.pyc" -delete

# 最终镜像
FROM python:3.11-slim
COPY --from=builder /app /app
WORKDIR /app
CMD ["python", "src/main.py"]

2.4 安全检查Hook

2.4.1 安全扫描工具矩阵
# 安全Hook工具选择
代码安全:
  - bandit: Python安全扫描
  - semgrep: 多语言安全扫描
  - gitleaks: 密钥泄露检测
  
依赖安全:
  - safety: Python依赖漏洞检查
  - npm audit: NPM包漏洞检查
  - snyk: 多语言依赖扫描
  
容器安全:
  - trivy: 容器镜像漏洞扫描
  - hadolint: Dockerfile安全检测
  
基础设施安全:
  - terrascan: Terraform安全扫描
  - checkov: 云基础设施安全检查
2.4.2 综合安全Hook配置
# .pre-commit-config.yaml - 安全部分
repos:
  - repo: https://github.com/PyCQA/bandit
    rev: 1.7.5
    hooks:
      - id: bandit
        args: ["-r", "src/", "-ll"]
        files: ^src/.*\.py$
  
  - repo: https://github.com/zricethezav/gitleaks
    rev: v8.17.0
    hooks:
      - id: gitleaks
        args: ["--verbose"]
  
  - repo: local
    hooks:
      - id: dependency-scan
        name: Dependency Vulnerability Scan
        entry: |
          # Python依赖扫描
          safety check -r requirements.txt
          # NPM依赖扫描
          if [ -f "package.json" ]; then
            npm audit --audit-level=high
          fi
        language: system
        pass_filenames: false
        always_run: true
        stages: [commit, push]
2.4.3 自定义安全规则
# scripts/security-hooks/custom-security.py
"""
自定义安全检查Hook
"""

import ast
import os
from pathlib import Path

class SecurityVisitor(ast.NodeVisitor):
    """AST访问器,检查安全问题"""
    
    def visit_Call(self, node):
        # 检查危险的函数调用
        dangerous_functions = {
            'eval', 'exec', 'os.system', 'subprocess.call'
        }
        
        if isinstance(node.func, ast.Name):
            if node.func.id in dangerous_functions:
                print(f"⚠️  发现潜在危险调用: {node.func.id}")
                print(f"   文件: {self.current_file}")
                print(f"   行号: {node.lineno}")
        
        self.generic_visit(node)
    
    def check_file(self, filepath):
        """检查单个文件"""
        self.current_file = filepath
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                tree = ast.parse(f.read())
                self.visit(tree)
        except SyntaxError as e:
            print(f"语法错误: {filepath}:{e.lineno}")

def check_hardcoded_secrets(filepath):
    """检查硬编码的密钥"""
    secret_patterns = [
        r'password\s*=\s*[\'"][^\'"]+[\'"]',
        r'api_key\s*=\s*[\'"][^\'"]+[\'"]',
        r'secret\s*=\s*[\'"][^\'"]+[\'"]',
    ]
    
    with open(filepath, 'r', encoding='utf-8') as f:
        content = f.read()
        for i, line in enumerate(content.splitlines(), 1):
            for pattern in secret_patterns:
                if re.search(pattern, line, re.IGNORECASE):
                    print(f"🔐 发现硬编码密钥: {filepath}:{i}")
                    print(f"   内容: {line.strip()}")

if __name__ == "__main__":
    import re
    import sys
    
    files_to_check = sys.argv[1:] if len(sys.argv) > 1 else []
    
    for filepath in files_to_check:
        if filepath.endswith('.py'):
            visitor = SecurityVisitor()
            visitor.check_file(filepath)
            check_hardcoded_secrets(filepath)

三、自定义Hooks创建

3.1 Hook开发框架

3.1.1 本地Hook开发结构
hooks/
├── scripts/
│   ├── pre-commit/
│   │   ├── format-code.sh
│   │   ├── run-tests.sh
│   │   └── check-types.sh
│   ├── pre-push/
│   │   ├── integration-tests.sh
│   │   └── security-scan.sh
│   └── post-merge/
│       └── update-deps.sh
├── templates/
│   └── custom-hook-template.py
└── config/
    └── hook-config.yaml
3.1.2 通用Hook模板
#!/usr/bin/env python3
"""
自定义Hook模板
用法: python hook-template.py [file1] [file2] ...
"""

import sys
import os
from pathlib import Path
from typing import List, Tuple

class CustomHook:
    """自定义Hook基类"""
    
    def __init__(self, config_path: str = None):
        self.config_path = config_path
        self.load_config()
    
    def load_config(self):
        """加载配置"""
        self.config = {}
        if self.config_path and os.path.exists(self.config_path):
            import yaml
            with open(self.config_path, 'r') as f:
                self.config = yaml.safe_load(f)
    
    def should_process_file(self, filepath: str) -> bool:
        """判断是否应该处理该文件"""
        extensions = self.config.get('extensions', [])
        if extensions:
            return any(filepath.endswith(ext) for ext in extensions)
        return True
    
    def process_files(self, files: List[str]) -> Tuple[bool, str]:
        """
        处理文件
        返回: (是否成功, 消息)
        """
        try:
            for filepath in files:
                if self.should_process_file(filepath):
                    result = self.process_single_file(filepath)
                    if not result[0]:
                        return result
            return True, "✓ 所有文件检查通过"
        except Exception as e:
            return False, f"❌ 处理失败: {str(e)}"
    
    def process_single_file(self, filepath: str) -> Tuple[bool, str]:
        """处理单个文件 - 子类需实现"""
        raise NotImplementedError

# 使用示例
class MyCustomHook(CustomHook):
    def process_single_file(self, filepath: str) -> Tuple[bool, str]:
        # 实现具体的检查逻辑
        with open(filepath, 'r') as f:
            content = f.read()
            
        # 示例:检查TODO注释
        if 'TODO:' in content:
            return False, f"发现未完成的TODO注释: {filepath}"
        
        return True, "✓ 检查通过"

if __name__ == "__main__":
    hook = MyCustomHook('config/hook-config.yaml')
    success, message = hook.process_files(sys.argv[1:])
    print(message)
    sys.exit(0 if success else 1)

3.2 业务特定Hook示例

3.2.1 API文档检查Hook
#!/usr/bin/env python3
"""
API文档完整性检查Hook
检查所有API端点是否有完整的文档
"""

import re
from pathlib import Path
import sys

class APIDocChecker:
    def __init__(self, source_dir="src"):
        self.source_dir = Path(source_dir)
        self.issues = []
    
    def check_python_endpoints(self):
        """检查Python FastAPI/Flask端点"""
        pattern = re.compile(r'@(app|router)\.(get|post|put|delete|patch)\("([^"]+)"\)')
        
        for py_file in self.source_dir.rglob("*.py"):
            with open(py_file, 'r', encoding='utf-8') as f:
                content = f.read()
                lines = content.splitlines()
                
                for i, line in enumerate(lines):
                    match = pattern.search(line)
                    if match:
                        # 检查下一行是否是文档字符串
                        has_docstring = False
                        for j in range(i+1, min(i+5, len(lines))):
                            if '"""' in lines[j] or "'''" in lines[j]:
                                has_docstring = True
                                break
                        
                        if not has_docstring:
                            self.issues.append({
                                'file': str(py_file),
                                'line': i+1,
                                'endpoint': match.group(3),
                                'method': match.group(2)
                            })
    
    def generate_report(self):
        """生成报告"""
        if not self.issues:
            print("✓ 所有API端点都有文档")
            return True
        
        print("❌ 发现未文档化的API端点:")
        for issue in self.issues:
            print(f"  文件: {issue['file']}:{issue['line']}")
            print(f"  端点: {issue['method'].upper()} {issue['endpoint']}")
            print()
        
        return False

if __name__ == "__main__":
    checker = APIDocChecker()
    checker.check_python_endpoints()
    success = checker.generate_report()
    sys.exit(0 if success else 1)
3.2.2 数据库迁移检查Hook
#!/usr/bin/env bash
# scripts/pre-deploy/db-migration-check.sh

# 检查数据库迁移是否完整
set -e

echo "🔍 检查数据库迁移..."

# 检查是否有未应用的迁移
if command -v alembic &> /dev/null; then
    echo "检查Alembic迁移..."
    if alembic current | grep -q "head"; then
        echo "✓ 所有迁移已应用"
    else
        echo "❌ 有未应用的迁移:"
        alembic current
        exit 1
    fi
fi

# 检查迁移文件命名规范
echo "检查迁移文件命名..."
MIGRATION_DIR="migrations/versions"
if [ -d "$MIGRATION_DIR" ]; then
    INVALID_FILES=$(find "$MIGRATION_DIR" -name "*.py" ! -name "__init__.py" \
        | xargs -I {} basename {} \
        | grep -vE '^[a-f0-9]{12}_.+\.py$')
    
    if [ -n "$INVALID_FILES" ]; then
        echo "❌ 迁移文件名不符合规范:"
        echo "$INVALID_FILES"
        echo "规范: [12位hash]_[描述].py"
        exit 1
    else
        echo "✓ 迁移文件名符合规范"
    fi
fi

# 检查迁移是否有回滚操作
echo "检查迁移回滚支持..."
for migration in "$MIGRATION_DIR"/*.py; do
    if [ -f "$migration" ]; then
        if ! grep -q "def downgrade" "$migration"; then
            echo "⚠️  警告: $migration 缺少downgrade函数"
        fi
    fi
done

echo "✅ 数据库迁移检查完成"

3.3 团队协作Hook

3.3.1 提交信息规范检查
#!/usr/bin/env python3
"""
提交信息规范检查Hook
确保提交信息符合团队规范
"""

import re
import sys
from typing import Tuple

class CommitMessageChecker:
    """提交信息检查器"""
    
    PATTERNS = {
        'feat': r'^feat(\([a-z-]+\))?: .+',
        'fix': r'^fix(\([a-z-]+\))?: .+',
        'docs': r'^docs(\([a-z-]+\))?: .+',
        'style': r'^style(\([a-z-]+\))?: .+',
        'refactor': r'^refactor(\([a-z-]+\))?: .+',
        'test': r'^test(\([a-z-]+\))?: .+',
        'chore': r'^chore(\([a-z-]+\))?: .+',
    }
    
    def __init__(self, commit_msg_file: str):
        self.commit_msg_file = commit_msg_file
        
    def check(self) -> Tuple[bool, str]:
        """检查提交信息"""
        with open(self.commit_msg_file, 'r', encoding='utf-8') as f:
            lines = f.readlines()
            
        if not lines:
            return False, "提交信息为空"
        
        # 获取标题行(第一行)
        title = lines[0].strip()
        
        # 检查标题长度
        if len(title) > 72:
            return False, f"标题过长 ({len(title)}/72)"
        
        # 检查类型前缀
        type_valid = False
        for pattern in self.PATTERNS.values():
            if re.match(pattern, title):
                type_valid = True
                break
        
        if not type_valid:
            valid_types = ", ".join(self.PATTERNS.keys())
            return False, f"无效的类型前缀。有效的类型: {valid_types}\n示例: feat: 添加新功能"
        
        # 检查是否有空行分隔标题和正文
        if len(lines) > 1 and lines[1].strip():
            return False, "标题和正文之间需要空行"
        
        # 检查正文行长度
        for i, line in enumerate(lines[2:], start=2):
            if len(line.rstrip()) > 72:
                return False, f"第{i}行过长 ({len(line.rstrip())}/72)"
        
        return True, "✓ 提交信息符合规范"

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python check-commit-msg.py <提交信息文件>")
        sys.exit(1)
    
    checker = CommitMessageChecker(sys.argv[1])
    success, message = checker.check()
    
    print(message)
    if not success:
        print("\n📝 提交信息规范:")
        print("格式: <类型>(<范围>): <描述>")
        print("类型: feat, fix, docs, style, refactor, test, chore")
        print("示例: feat(auth): 添加用户注册功能")
        print("      fix(api): 修复分页参数验证")
    
    sys.exit(0 if success else 1)
3.3.2 代码审查自动化Hook
#!/usr/bin/env bash
# scripts/pre-push/auto-review.sh

# 自动代码审查Hook
set -e

echo "🤖 开始自动代码审查..."

# 获取将要推送的提交
COMMITS=$(git log --oneline origin/$BRANCH_NAME..HEAD 2>/dev/null || git log --oneline HEAD~1..HEAD)

if [ -z "$COMMITS" ]; then
    echo "没有新的提交需要审查"
    exit 0
fi

echo "审查以下提交:"
echo "$COMMITS"
echo

# 检查提交是否包含大型文件
echo "🔍 检查大型文件..."
LARGE_FILES=$(git diff --cached --name-only | xargs -I {} sh -c '
    if [ -f "{}" ]; then
        size=$(stat -f%z "{}" 2>/dev/null || stat -c%s "{}")
        if [ $size -gt 1048576 ]; then
            echo "  {}: $(numfmt --to=iec $size)"
        fi
    fi
')

if [ -n "$LARGE_FILES" ]; then
    echo "⚠️  发现大型文件:"
    echo "$LARGE_FILES"
    echo "考虑使用.gitignore或git LFS"
fi

# 检查调试代码
echo "🔍 检查调试代码..."
DEBUG_PATTERNS=("console.log" "print(" "debugger" "TODO:" "FIXME:")
for pattern in "${DEBUG_PATTERNS[@]}"; do
    FILES_WITH_PATTERN=$(git diff --cached --name-only | xargs grep -l "$pattern" 2>/dev/null || true)
    if [ -n "$FILES_WITH_PATTERN" ]; then
        echo "⚠️  发现可能包含调试代码的文件 ($pattern):"
        echo "$FILES_WITH_PATTERN"
    fi
done

# 运行复杂性检查
echo "🔍 检查代码复杂度..."
if command -v radon &> /dev/null; then
    CHANGED_PY_FILES=$(git diff --cached --name-only | grep '\.py$')
    if [ -n "$CHANGED_PY_FILES" ]; then
        for file in $CHANGED_PY_FILES; do
            if [ -f "$file" ]; then
                COMPLEXITY=$(radon cc "$file" -s)
                if echo "$COMPLEXITY" | grep -q "[BC]"; then
                    echo "⚠️  $file 包含复杂函数:"
                    echo "$COMPLEXITY" | grep "[BC]"
                fi
            fi
        done
    fi
fi

echo "✅ 自动代码审查完成"
echo "提示: 确保所有测试通过,代码符合规范后再推送"

四、Hooks触发条件配置

4.1 Git Hooks配置

4.1.1 标准Git Hooks
# .git/hooks/ 目录结构
.git/hooks/
├── pre-commit          # 提交前触发
├── pre-merge-commit    # 合并提交前触发
├── pre-push            # 推送前触发
├── pre-rebase          # 变基前触发
├── commit-msg          # 提交信息验证
├── post-checkout       # 检出后触发
├── post-commit         # 提交后触发
├── post-merge          # 合并后触发
└── post-rewrite        # 重写提交历史后触发
4.1.2 多阶段Hook配置
#!/usr/bin/env bash
# .git/hooks/pre-commit
# 多阶段pre-commit hook

echo "🚀 开始pre-commit检查..."

# 阶段1: 快速检查(总是运行)
echo "阶段1: 快速语法检查..."
./hooks/scripts/quick-check.sh

if [ $? -ne 0 ]; then
    echo "❌ 快速检查失败"
    exit 1
fi

# 阶段2: 仅对暂存文件运行
echo "阶段2: 格式化检查..."
CHANGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)

if echo "$CHANGED_FILES" | grep -q '\.py$'; then
    echo "运行Python格式化检查..."
    ./hooks/scripts/format-python.sh --check-only
fi

if echo "$CHANGED_FILES" | grep -q '\.tsx\?$'; then
    echo "运行TypeScript检查..."
    ./hooks/scripts/check-typescript.sh
fi

# 阶段3: 自定义检查
echo "阶段3: 自定义检查..."
./hooks/scripts/custom-checks.sh "$CHANGED_FILES"

echo "✅ pre-commit检查全部通过"

4.2 CI/CD Pipeline Hooks

4.2.1 GitHub Actions工作流Hook
# .github/workflows/full-pipeline.yml
name: Full CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  # 手动触发
  workflow_dispatch:

jobs:
  # Hook 1: 代码质量检查
  code-quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Pre-Quality Hook
        run: ./scripts/hooks/pre-quality.sh
      
      - name: Run Linters
        run: |
          python -m flake8 src/
          python -m mypy src/
          npx eslint frontend/ --max-warnings=0
      
      - name: Post-Quality Hook
        run: ./scripts/hooks/post-quality.sh
        if: always()
  
  # Hook 2: 测试套件
  test:
    runs-on: ubuntu-latest
    needs: code-quality
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Pre-Test Hook
        run: ./scripts/hooks/pre-test.sh
      
      - name: Run Tests
        run: |
          python -m pytest tests/ --cov=src --cov-report=xml
      
      - name: Upload Coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.xml
      
      - name: Post-Test Hook
        run: ./scripts/hooks/post-test.sh
  
  # Hook 3: 构建和部署
  deploy:
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main'
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Pre-Deploy Hook
        run: ./scripts/hooks/pre-deploy.sh
      
      - name: Build and Push
        run: |
          docker build -t myapp:${{ github.sha }} .
          docker push myapp:${{ github.sha }}
      
      - name: Deploy to Staging
        run: ./scripts/deploy.sh staging
      
      - name: Post-Deploy Hook
        run: ./scripts/hooks/post-deploy.sh

4.3 条件触发配置

4.3.1 基于文件类型触发
#!/usr/bin/env bash
# 基于文件类型的条件触发

# 获取修改的文件
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)

# 初始化标志
RUN_PYTHON_CHECKS=false
RUN_JS_CHECKS=false
RUN_DOCS_CHECKS=false

# 检查文件类型
for file in $STAGED_FILES; do
    case $file in
        *.py)
            RUN_PYTHON_CHECKS=true
            ;;
        *.js|*.ts|*.jsx|*.tsx)
            RUN_JS_CHECKS=true
            ;;
        *.md|*.rst)
            RUN_DOCS_CHECKS=true
            ;;
        Dockerfile|docker-compose.yml)
            echo "⚠️  检测到Docker配置文件变更,建议运行容器测试"
            ;;
    esac
done

# 条件执行
if $RUN_PYTHON_CHECKS; then
    echo "运行Python检查..."
    ./scripts/checks/python-checks.sh
fi

if $RUN_JS_CHECKS; then
    echo "运行JavaScript检查..."
    ./scripts/checks/js-checks.sh
fi

if $RUN_DOCS_CHECKS; then
    echo "运行文档检查..."
    ./scripts/checks/docs-checks.sh
fi
4.3.2 基于分支策略触发
# .github/workflows/branch-specific.yml
name: Branch-Specific Hooks

on: [push, pull_request]

jobs:
  main-branch-hooks:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Production Safety Checks
        run: |
          ./scripts/hooks/production-safety.sh
          ./scripts/hooks/backup-verification.sh
  
  release-branch-hooks:
    if: startsWith(github.ref, 'refs/heads/release/')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Release Preparation
        run: |
          ./scripts/hooks/version-check.sh
          ./scripts/hooks/changelog-verification.sh
  
  feature-branch-hooks:
    if: startsWith(github.ref, 'refs/heads/feature/')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Feature Branch Checks
        run: |
          ./scripts/hooks/feature-tests.sh
          ./scripts/hooks/docs-required.sh

五、调试和维护Hooks

5.1 Hook调试技巧

5.1.1 调试模式配置
#!/usr/bin/env bash
# hooks/debug-mode.sh

# 启用调试模式
export HOOK_DEBUG=true

# 调试级别
# 1: 基本信息
# 2: 详细输出
# 3: 调试信息(包括命令执行)
export DEBUG_LEVEL=${DEBUG_LEVEL:-2}

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

debug() {
    if [ "$HOOK_DEBUG" = "true" ] && [ "$DEBUG_LEVEL" -ge 1 ]; then
        echo -e "${YELLOW}[DEBUG]${NC} $1"
    fi
}

info() {
    if [ "$HOOK_DEBUG" = "true" ] && [ "$DEBUG_LEVEL" -ge 2 ]; then
        echo -e "${GREEN}[INFO]${NC} $1"
    fi
}

error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 在Hook脚本开头引入
# source ./hooks/debug-mode.sh

5.2 Hook性能监控

5.2.1 性能跟踪工具
#!/usr/bin/env python3
"""
Hook性能监控
"""

import time
from functools import wraps
from collections import defaultdict
import statistics

class PerformanceMonitor:
    def __init__(self):
        self.records = defaultdict(list)
        self.current_hook = None
    
    def track(self, name=None):
        """性能跟踪装饰器"""
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                hook_name = name or func.__name__
                self.current_hook = hook_name
                
                start_time = time.perf_counter()
                try:
                    result = func(*args, **kwargs)
                    elapsed = time.perf_counter() - start_time
                    
                    # 记录执行时间
                    self.records[hook_name].append(elapsed)
                    
                    # 如果执行时间过长,发出警告
                    threshold = self.get_threshold(hook_name)
                    if elapsed > threshold:
                        print(f"⚠️  {hook_name} 执行时间较长: {elapsed:.2f}s (阈值: {threshold:.2f}s)")
                    
                    return result
                finally:
                    self.current_hook = None
            
            return wrapper
        return decorator
    
    def get_threshold(self, hook_name):
        """获取阈值"""
        thresholds = {
            'pre-commit': 5.0,      # 5秒
            'pre-push': 30.0,       # 30秒
            'test-suite': 60.0,     # 60秒
            'formatting': 10.0,     # 10秒
        }
        return thresholds.get(hook_name, 30.0)
    
    def generate_report(self):
        """生成性能报告"""
        print("📊 Hook性能报告")
        print("=" * 50)
        
        for hook_name, times in self.records.items():
            if times:
                avg = statistics.mean(times)
                median = statistics.median(times)
                stddev = statistics.stdev(times) if len(times) > 1 else 0
                
                print(f"{hook_name}:")
                print(f"  执行次数: {len(times)}")
                print(f"  平均时间: {avg:.2f}s")
                print(f"  中位数: {median:.2f}s")
                print(f"  标准差: {stddev:.2f}s")
                print(f"  最长: {max(times):.2f}s")
                print(f"  最短: {min(times):.2f}s")
                print()

# 使用示例
monitor = PerformanceMonitor()

@monitor.track("code-formatting")
def format_code():
    time.sleep(0.5)  # 模拟耗时操作
    return "formatted"

@monitor.track("test-execution")
def run_tests():
    time.sleep(2.0)  # 模拟耗时操作
    return "tests passed"

if __name__ == "__main__":
    # 模拟多次执行
    for _ in range(5):
        format_code()
        run_tests()
    
    monitor.generate_report()

5.3 Hook维护策略

5.3.1 Hook版本管理
# hooks/version-manifest.yaml
version: "1.2.0"
last_updated: "2024-01-15"
maintainer: "devops-team@company.com"

hooks:
  pre-commit:
    version: "1.1.0"
    dependencies:
      - python >= 3.8
      - pre-commit >= 3.0.0
    checks:
      - name: code-formatting
        enabled: true
        command: black --check
      - name: linting
        enabled: true
        command: flake8
      - name: type-checking
        enabled: false  # 临时禁用
        command: mypy
  
  pre-push:
    version: "1.0.3"
    dependencies:
      - docker
      - pytest
    checks:
      - name: integration-tests
        enabled: true
  
  custom:
    - name: api-docs-check
      version: "0.9.1"
      enabled: true
    - name: security-scan
      version: "1.2.1"
      enabled: true

update_policy:
  auto_update_minor: true
  require_review_major: true
  backup_before_update: true
5.3.2 Hook健康检查
#!/usr/bin/env bash
# scripts/maintain/hook-health-check.sh

echo "🩺 检查Hook健康状况..."

# 检查Hook文件存在性
echo "1. 检查Hook文件..."
for hook in pre-commit pre-push commit-msg; do
    if [ -f ".git/hooks/$hook" ]; then
        echo "  ✓ $hook 存在"
        
        # 检查可执行权限
        if [ -x ".git/hooks/$hook" ]; then
            echo "  ✓ $hook 可执行"
        else
            echo "  ⚠️  $hook 不可执行,正在修复..."
            chmod +x ".git/hooks/$hook"
        fi
    else
        echo "  ❌ $hook 不存在"
    fi
done

# 检查依赖
echo "2. 检查依赖..."
REQUIRED_TOOLS=("python" "node" "docker" "git")
for tool in "${REQUIRED_TOOLS[@]}"; do
    if command -v "$tool" &> /dev/null; then
        echo "  ✓ $tool 已安装"
    else
        echo "  ❌ $tool 未安装"
    fi
done

# 检查配置文件
echo "3. 检查配置文件..."
CONFIG_FILES=(".pre-commit-config.yaml" "package.json" "requirements.txt")
for config in "${CONFIG_FILES[@]}"; do
    if [ -f "$config" ]; then
        echo "  ✓ $config 存在"
        
        # 检查配置文件有效性
        case "$config" in
            ".pre-commit-config.yaml")
                if python -c "import yaml; yaml.safe_load(open('$config'))" &> /dev/null; then
                    echo "  ✓ $config 格式正确"
                else
                    echo "  ❌ $config 格式错误"
                fi
                ;;
            "package.json")
                if jq empty "$config" &> /dev/null; then
                    echo "  ✓ $config 格式正确"
                else
                    echo "  ❌ $config 格式错误"
                fi
                ;;
        esac
    else
        echo "  ⚠️  $config 不存在"
    fi
done

# 运行测试Hook
echo "4. 测试Hook执行..."
if [ -f ".git/hooks/pre-commit" ]; then
    echo "  测试pre-commit hook..."
    
    # 创建测试文件
    TEST_FILE="hook_test_$(date +%s).py"
    echo "# Test file" > "$TEST_FILE"
    git add "$TEST_FILE"
    
    # 运行pre-commit(不实际提交)
    if .git/hooks/pre-commit; then
        echo "  ✓ pre-commit hook 运行正常"
    else
        echo "  ❌ pre-commit hook 运行失败"
    fi
    
    # 清理
    git rm --cached "$TEST_FILE" &> /dev/null
    rm "$TEST_FILE"
fi

echo "✅ Hook健康检查完成"
5.3.3 Hook迁移和升级
#!/usr/bin/env python3
"""
Hook迁移工具
用于升级和迁移Hook配置
"""

import shutil
import yaml
from pathlib import Path
from datetime import datetime
import json

class HookMigrator:
    def __init__(self, backup_dir=".hook-backups"):
        self.backup_dir = Path(backup_dir)
        self.backup_dir.mkdir(exist_ok=True)
    
    def backup_current_hooks(self):
        """备份当前Hook配置"""
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_path = self.backup_dir / f"backup_{timestamp}"
        
        print(f"📦 备份当前Hook配置到: {backup_path}")
        
        # 备份.git/hooks
        hooks_dir = Path(".git/hooks")
        if hooks_dir.exists():
            shutil.copytree(hooks_dir, backup_path / "hooks")
        
        # 备份配置文件
        config_files = [".pre-commit-config.yaml", "package.json", "requirements.txt"]
        for config in config_files:
            config_path = Path(config)
            if config_path.exists():
                shutil.copy2(config_path, backup_path / config_path.name)
        
        # 创建备份元数据
        metadata = {
            "timestamp": datetime.now().isoformat(),
            "backup_path": str(backup_path),
            "files_backed_up": [str(p) for p in backup_path.rglob("*") if p.is_file()]
        }
        
        with open(backup_path / "backup_metadata.json", 'w') as f:
            json.dump(metadata, f, indent=2)
        
        return backup_path
    
    def migrate_pre_commit_config(self, old_config_path, new_config_path):
        """迁移pre-commit配置"""
        print(f"🔄 迁移pre-commit配置")
        
        with open(old_config_path, 'r') as f:
            old_config = yaml.safe_load(f)
        
        # 更新到新版本格式
        new_config = {
            'repos': [],
            'default_language_version': {
                'python': 'python3.11',
                'node': '18.17'
            }
        }
        
        # 转换旧格式到新格式
        for repo in old_config.get('repos', []):
            # 更新版本到最新稳定版
            if repo.get('repo') == 'https://github.com/psf/black':
                repo['rev'] = '23.9.1'
            elif repo.get('repo') == 'https://github.com/pycqa/flake8':
                repo['rev'] = '6.1.0'
            
            new_config['repos'].append(repo)
        
        # 添加新的检查
        new_config['repos'].append({
            'repo': 'https://github.com/pre-commit/mirrors-mypy',
            'rev': 'v1.5.1',
            'hooks': [{
                'id': 'mypy',
                'args': ['--ignore-missing-imports']
            }]
        })
        
        # 保存新配置
        with open(new_config_path, 'w') as f:
            yaml.dump(new_config, f, default_flow_style=False)
        
        print(f"✅ 配置已更新到新格式")
    
    def validate_migration(self):
        """验证迁移结果"""
        print("🔍 验证迁移结果...")
        
        # 检查所有Hook文件存在
        required_hooks = ['pre-commit', 'pre-push']
        for hook in required_hooks:
            hook_path = Path(f".git/hooks/{hook}")
            if not hook_path.exists():
                print(f"❌ 缺少Hook文件: {hook}")
                return False
        
        # 检查配置文件
        if not Path(".pre-commit-config.yaml").exists():
            print("❌ 缺少pre-commit配置文件")
            return False
        
        # 运行快速测试
        print("  运行快速测试...")
        import subprocess
        try:
            result = subprocess.run(
                ["pre-commit", "run", "--all-files"],
                capture_output=True,
                text=True,
                timeout=30
            )
            
            if result.returncode == 0:
                print("✅ 所有检查通过")
                return True
            else:
                print(f"❌ 检查失败: {result.stderr}")
                return False
                
        except subprocess.TimeoutExpired:
            print("❌ 测试超时")
            return False
    
    def rollback(self, backup_path):
        """回滚到备份版本"""
        print(f"↩️  回滚到备份: {backup_path}")
        
        backup_path = Path(backup_path)
        if not backup_path.exists():
            print("❌ 备份不存在")
            return False
        
        # 恢复Hook文件
        hooks_backup = backup_path / "hooks"
        if hooks_backup.exists():
            shutil.rmtree(".git/hooks", ignore_errors=True)
            shutil.copytree(hooks_backup, ".git/hooks")
        
        # 恢复配置文件
        for config_file in backup_path.glob("*"):
            if config_file.name != "hooks":
                shutil.copy2(config_file, ".")
        
        print("✅ 回滚完成")
        return True

if __name__ == "__main__":
    migrator = HookMigrator()
    
    # 备份
    backup = migrator.backup_current_hooks()
    
    # 迁移
    try:
        migrator.migrate_pre_commit_config(
            ".pre-commit-config.yaml",
            ".pre-commit-config.yaml.new"
        )
        
        # 替换配置文件
        Path(".pre-commit-config.yaml.new").rename(".pre-commit-config.yaml")
        
        # 验证
        if migrator.validate_migration():
            print("🎉 迁移成功")
        else:
            print("❌ 迁移失败,正在回滚...")
            migrator.rollback(backup)
    
    except Exception as e:
        print(f"❌ 迁移过程中出错: {e}")
        migrator.rollback(backup)

六、最佳实践总结

6.1 Hook设计原则

设计原则:
  快速失败: 尽早发现问题,减少等待时间
  渐进增强: 从基本检查开始,逐步增加复杂检查
  关注本地: 优先在本地运行,减少CI负担
  明确反馈: 提供清晰的错误信息和修复建议
  可配置性: 允许团队根据需求调整Hook行为
  向后兼容: 避免破坏现有工作流

6.2 团队协作指南

# Hook团队协作指南

## 1. 统一配置
- 使用统一的.pre-commit-config.yaml
- 共享Hook脚本仓库
- 统一的工具版本

## 2. 文档要求
每个Hook需要:
- README.md说明
- 配置示例
- 常见问题解答
- 维护者信息

## 3. 更新流程
1. 在特性分支开发新Hook
2. 提交Pull Request
3. 团队成员审查
4. 在开发环境测试
5. 合并到主分支
6. 通知团队更新

## 4. 故障处理
- 提供回滚机制
- 记录详细的日志
- 设置维护者值班
- 建立紧急禁用流程

6.3 性能优化建议

优化策略:
  缓存结果: 对未变更文件跳过检查
  并行执行: 同时运行独立的检查
  增量检查: 只检查变更的部分
  延迟加载: 按需加载工具和依赖
  智能排序: 快速检查在前,耗时检查在后

监控指标:
  - Hook执行时间
  - 成功率/失败率
  - 资源使用情况
  - 开发者满意度

6.4 工具生态系统

推荐工具栈:
- **管理框架**: pre-commit (Python)
- **代码格式化**: black, prettier
- **代码检查**: flake8, eslint, mypy
- **安全扫描**: bandit, npm audit, trivy
- **测试运行**: pytest, jest
- **CI/CD**: GitHub Actions, GitLab CI
- **监控**: Datadog, Prometheus
- **文档**: MkDocs, Docusaurus

通过系统化地实施Hooks自动化,团队可以:

  • 提升代码质量和一致性

  • 减少人为错误

  • 加速开发反馈循环

  • 建立可维护的自动化工作流

  • 提升团队协作效率

记住:好的Hook应该是透明的帮助者,而不是烦人的障碍。目标是让开发者专注于创造价值,而不是被工具阻碍。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐