Git 钩子(Hooks)教程

Git钩子(Hooks)

掌握自动化工作流程、代码质量检查和部署自动化的核心技术

开始学习

什么是Git钩子?

Git钩子执行时机

pre-commit
提交前运行
commit-msg
提交信息验证
pre-push
推送前检查
pre-receive
服务器接收前
post-receive
服务器接收后

钩子的定义

Git钩子是在Git操作的特定时间点自动执行的脚本。它们允许你在关键工作流程节点注入自定义逻辑。

  • 在特定Git事件发生时触发
  • 可以中断操作或执行额外任务
  • 支持多种脚本语言(Bash、Python等)
  • 存储在.git/hooks目录中

钩子的作用

Git钩子主要用于自动化工作流程和强制执行策略:

  • 代码质量检查(linting、测试)
  • 提交信息验证
  • 自动部署和构建
  • 通知和提醒
  • 安全性检查

钩子的工作原理

  • 事件驱动:在特定Git操作(提交、推送等)时触发
  • 可中断:某些钩子可以阻止操作继续执行
  • 环境变量:Git提供相关环境变量供钩子使用
  • 退出代码:非零退出代码通常表示失败并中止操作
  • 参数传递:钩子接收与当前操作相关的参数

注意: Git钩子默认存储在.git/hooks目录中,该目录不会随仓库一起推送。每个钩子都是独立的可执行文件。

重要: 客户端钩子只影响本地仓库,而服务器端钩子影响所有用户。服务器钩子通常用于强制执行团队策略。

Git钩子类型

客户端钩子

在本地仓库执行的钩子:

pre-commit

在提交前运行,用于代码检查

可中断提交
prepare-commit-msg

在提交信息编辑器启动前运行

用于修改默认提交信息
commit-msg

在提交信息保存后运行

验证提交信息格式
post-commit

在提交完成后运行

用于通知或日志记录
pre-rebase

在变基前运行

可中断变基操作
post-checkout

在检出后运行

用于环境设置

服务器端钩子

在远程仓库执行的钩子:

pre-receive

在推送操作开始时运行

可拒绝整个推送
update

为每个被推送的分支运行

可拒绝特定分支更新
post-receive

在推送操作完成后运行

用于部署或通知
pre-auto-gc

在自动垃圾回收前运行

可取消垃圾回收

其他重要钩子

pre-push

在推送到远程前运行

客户端钩子,可中断推送
post-merge

在合并成功后运行

用于依赖安装等

钩子执行流程

# 提交工作流程
1. pre-commit → 代码检查
2. prepare-commit-msg → 准备提交信息
3. commit-msg → 验证提交信息
4. post-commit → 提交后操作

# 推送工作流程
1. pre-push → 推送前检查(客户端)
2. pre-receive → 服务器接收前检查
3. update → 每个分支更新检查
4. post-receive → 推送后操作

可中断的钩子

以下钩子可以中止Git操作:

  • pre-commit:中止提交
  • commit-msg:中止提交(如果提交信息无效)
  • pre-push:中止推送
  • pre-rebase:中止变基
  • pre-receive:中止整个推送
  • update:中止特定分支更新

提示: 可中断的钩子通过返回非零退出代码来中止操作。返回0表示成功,操作继续。

钩子实战示例

pre-commit 代码检查

在提交前运行代码质量检查:

#!/bin/bash

# 运行ESLint检查
echo "Running ESLint..."
npx eslint .

# 如果ESLint失败,中止提交
if [ $? -ne 0 ]; then
  echo "ESLint检查失败,请修复错误后再提交"
  exit 1
fi

# 运行测试
echo "Running tests..."
npm test

# 如果测试失败,中止提交
if [ $? -ne 0 ]; then
  echo "测试失败,请修复测试后再提交"
  exit 1
fi

echo "所有检查通过!"
exit 0
commit-msg 提交信息验证

验证提交信息符合约定式提交格式:

#!/bin/bash

commit_msg_file=$1
commit_msg=$(cat "$commit_msg_file")

# 约定式提交正则表达式
pattern='^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+'

if ! echo "$commit_msg" | grep -qE "$pattern"; then
  echo "错误:提交信息不符合约定式提交格式"
  echo "格式: <类型>(<范围>): <描述>"
  echo "类型: feat, fix, docs, style, refactor, test, chore"
  echo "示例: feat(auth): 添加用户登录功能"
  exit 1
fi

echo "提交信息格式正确"
exit 0
pre-push 推送前检查

在推送前确保所有测试通过:

#!/bin/bash

remote="$1"
url="$2"

echo "推送前检查..."

# 确保在正确的分支上
current_branch=$(git symbolic-ref --short HEAD)
if [ "$current_branch" = "main" ]; then
  echo "警告:你正在推送到main分支"
  read -p "确认继续?(y/N) " -n 1 -r
  echo
  if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    echo "推送已取消"
    exit 1
  fi
fi

# 运行完整测试套件
echo "运行完整测试套件..."
npm run test:ci

if [ $? -ne 0 ]; then
  echo "测试失败,请修复测试后再推送"
  exit 1
fi

echo "所有检查通过,可以推送"
exit 0
post-receive 自动部署

在服务器接收推送后自动部署:

#!/bin/bash

while read oldrev newrev refname
do
  branch=$(git rev-parse --symbolic --abbrev-ref $refname)
  
  if [ "$branch" = "main" ]; then
    echo "检测到main分支更新,开始部署..."
    
    # 切换到工作目录
    cd /var/www/myapp || exit
    
    # 拉取最新代码
    git pull origin main
    
    # 安装依赖
    npm install --production
    
    # 构建应用
    npm run build
    
    # 重启服务
    systemctl restart myapp
    
    echo "部署完成!"
  fi
done

Python钩子示例

使用Python编写的pre-commit钩子:

#!/usr/bin/env python3

import subprocess
import sys

def run_command(command):
  """运行命令并返回结果"""
  try:
    result = subprocess.run(
      command, shell=True,
      capture_output=True, text=True
    )
    return result.returncode, result.stdout, result.stderr
  except Exception as e:
    return 1, "", str(e)

def main():
  print("运行代码质量检查...")
  
  # 检查Python代码风格
  print("检查代码风格...")
  returncode, stdout, stderr = run_command("flake8 .")
  if returncode != 0:
    print("代码风格检查失败:")
    print(stderr)
    sys.exit(1)
  
  # 运行测试
  print("运行测试...")
  returncode, stdout, stderr = run_command("pytest")
  if returncode != 0:
    print("测试失败:")
    print(stderr)
    sys.exit(1)
  
  print("所有检查通过!")
  sys.exit(0)

if __name__ == "__main__":
  main()

钩子安装步骤

  1. 创建钩子脚本文件
  2. 确保文件具有可执行权限
  3. 将文件放置在.git/hooks目录
  4. 使用正确的文件名(如pre-commit
# 1. 创建钩子文件
touch .git/hooks/pre-commit

# 2. 添加脚本内容
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
echo "Running pre-commit checks..."
# 你的检查逻辑
EOF

# 3. 设置可执行权限
chmod +x .git/hooks/pre-commit

# 4. 测试钩子
git add .
git commit -m "测试提交"

注意: .git/hooks目录不会被推送到远程仓库。团队成员需要各自安装这些钩子,或者使用像Husky这样的工具来管理钩子。

钩子管理与工具

手动管理钩子

基本的钩子管理命令:

查看钩子
ls .git/hooks

列出所有可用的钩子

创建钩子
touch .git/hooks/pre-commit

创建新的钩子文件

设置权限
chmod +x .git/hooks/pre-commit

使钩子可执行

禁用钩子
git commit --no-verify

跳过钩子执行

钩子模板

Git提供钩子模板,可以初始化新仓库时自动安装:

# 设置钩子模板目录
git config --global init.templatedir '~/.git-template'

# 创建模板目录结构
mkdir -p ~/.git-template/hooks

# 添加预定义的钩子到模板
cp my-hooks/* ~/.git-template/hooks/

# 现在所有新的git init都会使用这些钩子

钩子管理工具

Husky (JavaScript)

最流行的Git钩子管理工具:

# 安装
npm install husky --save-dev

# 启用Git钩子
npx husky install

# 创建钩子
npx husky add .husky/pre-commit "npm test"
npx husky add .husky/commit-msg 'npx commitlint --edit "$1"'
pre-commit (Python)

Python项目的钩子框架:

# 安装
pip install pre-commit

# 创建配置文件 .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.2.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer

# 安装钩子
pre-commit install
Lefthook (多语言)

快速且强大的Git钩子管理器:

# 安装
npm install @arkweid/lefthook --save-dev

# 创建配置文件 lefthook.yml
pre-commit:
  commands:
    lint:
      run: npm run lint
    test:
      run: npm test

# 安装钩子
npx lefthook install

跳过钩子执行

在某些情况下需要跳过钩子:

# 跳过所有钩子
git commit --no-verify -m "紧急修复"
git push --no-verify

# 环境变量控制
# 在钩子脚本中检查环境变量
if [ "$SKIP_HOOKS" = "true" ]; then
  exit 0
fi

# 使用git config
git config hooks.skip true
git commit -m "跳过钩子"
git config --unset hooks.skip

钩子调试技巧

  • 添加调试输出:在脚本中使用echoset -x
  • 检查退出代码:使用echo $?检查上一个命令的退出代码
  • 手动测试:直接运行钩子脚本进行测试
  • 日志记录:将输出重定向到日志文件
  • 环境变量:检查Git提供的环境变量
# 调试pre-commit钩子
./.git/hooks/pre-commit

# 查看环境变量
env | grep GIT

# 添加调试信息到钩子
echo "钩子开始执行"
set -x # 开启调试模式
# 你的代码
set +x # 关闭调试模式
echo "钩子执行完成"

最佳实践与注意事项

钩子设计最佳实践

保持快速

钩子应该快速执行,不阻塞开发流程:

  • 避免在pre-commit中运行长时间的任务
  • 对于耗时检查,使用pre-push而不是pre-commit
  • 并行运行独立的任务
  • 缓存检查结果(如适用)
提供清晰的反馈

钩子应该提供有用的错误信息:

  • 明确指出什么失败了
  • 提供修复建议
  • 显示相关文件和信息
  • 使用颜色和格式化提高可读性
可跳过机制

提供跳过钩子的方法:

  • 支持--no-verify标志
  • 提供环境变量控制
  • 允许选择性禁用特定检查
  • 文档说明跳过的方法和风险

安全注意事项

安全风险

钩子可能带来的安全风险:

  • 恶意钩子可能执行任意代码
  • 不要运行不受信任的钩子
  • 代码审查所有钩子脚本
  • 限制服务器钩子的权限
团队协作

在团队中使用钩子的建议:

  • 使用Husky等工具共享钩子配置
  • 文档化所有钩子的目的和行为
  • 提供简单的安装和设置指南
  • 定期审查和更新钩子
维护策略

钩子的长期维护:

  • 版本控制钩子配置
  • 定期测试钩子功能
  • 监控钩子性能
  • 收集用户反馈并改进

常见陷阱与解决方案

解决方案:

  • 将耗时检查移到pre-push钩子
  • 只检查暂存的文件,而不是所有文件
  • 使用增量检查或缓存
  • 并行运行独立任务

解决方案:

  • 提供--no-verify选项
  • 实现紧急模式(环境变量控制)
  • 允许跳过特定检查
  • 文档说明紧急情况下的处理方法

解决方案:

  • 使用Husky等工具自动安装
  • 在CI/CD流水线中运行相同的检查
  • 提供一键安装脚本
  • 文档化安装步骤和重要性

完整工作流示例

# 项目钩子配置示例

# pre-commit - 快速检查
- 代码格式化 (Prettier)
- 简单语法检查
- 阻止提交大文件

# commit-msg - 提交信息
- 验证约定式提交格式
- 检查提交信息长度

# pre-push - 推送前检查
- 运行完整测试套件
- 构建检查
- 集成测试

# post-merge - 合并后
- 安装新的依赖
- 数据库迁移

# 使用Husky管理
"husky": {
  "hooks": {
    "pre-commit": "lint-staged",
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
    "pre-push": "npm run test:ci"
  }
}

专业建议: 结合使用客户端钩子和CI/CD服务器检查,既保证了开发体验,又确保了代码质量。客户端钩子提供即时反馈,服务器检查提供最终保障。