Git hook pre-commit
文章目录
- Git hook
 - Git Hook 的核心功能
 - Git Hook 的分类
 - 客户端钩子(Client-side Hooks)
 - 服务端钩子(Server-side Hooks)
 
- 典型应用场景
 - 提交前检查
 - 规范提交信息
 - 禁止推送敏感信息
 - 自动部署(服务端 post-receive)
 
- 如何使用 Git Hooks?
 - 默认位置
 - 自定义目录(推荐团队协作)
 
- 总结
 
- Git pre-commit
 - `pre-commit` 钩子的核心作用
 - 自动化质量检查
 - 自动修复(可选)
 
- 关于 `.githooks/` 目录
 - `pre-commit` 脚本示例
 - 注意事项
 
Git hook
- Git Hooks(Git 钩子)是 Git 提供的一种强大机制,允许你在 Git 操作的特定关键节点自动执行自定义脚本。这些脚本可以用于自动化检查、验证、通知、部署等任务,从而提升开发效率、保障代码质量、加强协作规范。
 
Git Hook 的核心功能
| 功能类别 | 说明 | 
|---|---|
| 自动化检查 | 在提交、推送前自动运行测试、代码风格检查、安全扫描等 | 
| 质量保障 | 阻止不符合规范的代码进入仓库(如含敏感信息、未通过测试) | 
| 流程控制 | 强制执行团队约定(如提交信息格式、分支命名规则) | 
| 自动修复 | 自动格式化代码、修复简单问题(如 Prettier、ESLint --fix) | 
| 通知与日志 | 提交后发邮件、记录日志、触发 CI/CD 等 | 
| 环境准备/清理 | 在操作前后设置或清理临时文件、依赖等 | 
Git Hook 的分类
Git Hooks 分为两大类:
客户端钩子(Client-side Hooks)
运行在开发者本地机器上,影响个人操作。
| 钩子名称 | 触发时机 | 常见用途 | 
|---|---|---|
pre-commit | git commit 之前 | 代码检查、格式化、测试 | 
prepare-commit-msg | 默认提交信息生成后、编辑器打开前 | 自动生成提交模板 | 
commit-msg | 提交信息确定后、提交完成前 | 校验提交信息格式(如符合 Conventional Commits) | 
post-commit | 提交完成后 | 发通知、记录日志 | 
pre-push | git push 之前 | 运行完整测试套件、检查是否遗漏文件 | 
post-checkout / post-merge | 切换分支或合并后 | 重新安装依赖、清理缓存 | 
客户端钩子不会随仓库自动生效,需开发者手动启用(如配置
core.hooksPath或使用工具如 Husky)。
服务端钩子(Server-side Hooks)
运行在远程 Git 服务器(如自建 Git 服务器、GitLab、Gitea),用于控制接收推送的行为。
| 钩子名称 | 触发时机 | 常见用途 | 
|---|---|---|
pre-receive | 接收到推送请求、但未写入 refs 前 | 全局检查(如禁止 force push、扫描恶意代码) | 
update | 对每个被推送的分支/标签单独检查 | 细粒度权限控制(如只允许特定人推送到 main) | 
post-receive | 推送完成后 | 自动部署、发 webhook、通知团队 | 
GitHub/GitLab 等托管平台不支持自定义服务端钩子,但提供 Webhooks 和 Protected Branches 等替代方案。
典型应用场景
提交前检查
# pre-commit
npm run lint && npm run test:unit
 
阻止有语法错误或测试失败的代码提交。
规范提交信息
# commit-msg
if ! head -n1 "$1" | grep -qE '^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+'; thenecho "Commit message must follow Conventional Commits format!"exit 1
fi
 
禁止推送敏感信息
# pre-push
if git diff origin/main..HEAD | grep -i "password\|secret\|token"; thenecho "🚫 检测到敏感词!禁止推送!"exit 1
fi
 
自动部署(服务端 post-receive)
#!/bin/bash
cd /var/www/myapp
git --git-dir=/var/repo/myapp.git --work-tree=/var/www/myapp checkout -f
npm install && pm2 reload app
 
如何使用 Git Hooks?
默认位置
.git/hooks/
 
包含 .sample 示例文件(如 pre-commit.sample),去掉后缀并赋予执行权限即可启用。
自定义目录(推荐团队协作)
# 将钩子放在项目内(如 .githooks/)
git config core.hooksPath .githooks
 
注意:
- 钩子脚本必须有 可执行权限(
 chmod +x)- 客户端钩子 不会自动在 clone 后生效,需文档或脚本引导设置
 - 钩子不能被 Git 跟踪(除非你用
 core.hooksPath指向项目内目录)- 不要在钩子中做耗时过长的操作(会影响开发者体验)
 
总结
Git Hooks = Git 操作生命周期中的“自动化守门员”
它们让你在代码进入仓库的每一步都能自动执行检查、修复或通知,是构建高质量、规范化开发流程的关键工具。
如果你有具体需求(比如“如何在提交前自动格式化 Python 代码”),欢迎告诉我,我可以给出完整配置示例!
Git pre-commit
在 Git 中,.githooks/pre-commit(更准确地说,是 被配置为钩子目录的 .githooks/ 中的 pre-commit 脚本)的核心功能是:在每次执行 git commit 命令、但尚未真正创建提交之前,自动运行一段自定义脚本,用于检查或处理即将提交的代码。
pre-commit 钩子的核心作用
 
- 如果 
pre-commit脚本以 非零状态退出(例如exit 1),Git 会 中止本次提交。 - 如果脚本成功退出(
exit 0或正常结束),提交继续进行。 
自动化质量检查
在代码进入版本历史前,自动执行:
- 代码风格检查(如 ESLint、Prettier)
 - 静态类型检查(如 TypeScript 的 
tsc --noEmit) - 单元测试运行
 - 禁止提交调试代码(如 
console.log、debugger) - 检查敏感信息(如密码、密钥)是否误提交
 
自动修复(可选)
- 有些工具(如 Prettier)可以在 
pre-commit中自动格式化暂存区的文件,并重新git add,实现“提交前自动美化代码”。 
关于 .githooks/ 目录
 
-  
Git 默认的钩子目录是
.git/hooks/,但该目录 不会被 Git 跟踪(无法共享给团队)。 -  
为了便于协作,很多项目将钩子脚本放在项目根目录下的
.githooks/(或其他名称如.git-hooks/)中,并通过以下命令启用:git config core.hooksPath .githooks -  
设置后,Git 会从
.githooks/而不是.git/hooks/加载钩子脚本。 -  
.githooks/可以加入版本控制(git add .githooks),实现团队共享。 
pre-commit 脚本示例
 
#!/bin/bash
#
# Pre-commit hook - Google C++ 代码风格检查
# 功能:检查代码风格,生成期望代码,用meld比较(左侧当前,右侧期望)
## 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'# 临时文件目录
TEMP_DIR="/tmp/git-precommit-$$"
mkdir -p "$TEMP_DIR"
trap "rm -rf $TEMP_DIR" EXIT# 获取要检查的提交基准
if git rev-parse --verify HEAD >/dev/null 2>&1; thenagainst=HEAD
elseagainst=$(git hash-object -t tree /dev/null)
fiexec 1>&2echo -e "${BLUE}=== C++ 代码风格检查 ===${NC}"# 代码风格修复函数
fix_style() {local file="$1"local has_issues=falselocal fixed_content=""local prev_line=""while IFS= read -r line; dooriginal_line="$line"fixed_line="$line"# 1. 流操作符空格: << (包括LOG) - 确保前后都有空格if echo "$line" | grep -q '<<'; then# 先处理 << 前面没有空格的情况fixed_line=$(echo "$fixed_line" | sed -E 's/([^[:space:]<])<<([[:space:]]|[^[:space:]<])/\1 << \2/g')# 再处理 << 后面没有空格的情况(但不是 <<< 或更多的<)fixed_line=$(echo "$fixed_line" | sed -E 's/<<([^[:space:]<])/<< \1/g')fi# 2. 赋值操作符空格: a=b -> a = bif echo "$line" | grep -qE '[^[:space:]!<>=]=[^[:space:]=]' && ! echo "$line" | grep -qE '==|!=|<=|>='; thenfixed_line=$(echo "$fixed_line" | sed -E 's/([^[:space:]!<>=])=([^[:space:]=])/\1 = \2/g')fi# 3. 比较和逻辑操作符空格: ==, !=, &&, ||if echo "$original_line" | grep -qE '[^[:space:]]==[^[:space:]]|[^[:space:]]!=[^[:space:]]|[^[:space:]]&&[^[:space:]]|[^[:space:]]\|\|[^[:space:]]'; thenfixed_line=$(echo "$fixed_line" | sed -E 's/([a-zA-Z0-9_])==([a-zA-Z0-9_])/\1 == \2/g;s/([a-zA-Z0-9_])!=([a-zA-Z0-9_])/\1 != \2/g;s/([a-zA-Z0-9_])&&([a-zA-Z0-9_])/\1 \&\& \2/g;s/([a-zA-Z0-9_])\|\|([a-zA-Z0-9_])/\1 || \2/g')fi# 4. 逗号后空格: a,b -> a, bif echo "$line" | grep -qE ',[^[:space:]]' && ! echo "$line" | grep -q '".*,.*"'; thenfixed_line=$(echo "$fixed_line" | sed -E 's/,([^[:space:]])/, \1/g')fi# 5. 删除代码块开头的空行local skip_line=falseif [ -n "$prev_line" ] && echo "$prev_line" | grep -qE '\{[[:space:]]*$'; thenif [ -z "$(echo "$line" | sed 's/[[:space:]]//g')" ]; thenskip_line=truehas_issues=truefifi# 检测是否有修改if [ "$original_line" != "$fixed_line" ]; thenhas_issues=truefi# 添加到修复内容(跳过多余空行)if [ "$skip_line" != true ]; thenfixed_content+="$fixed_line"$'\n'fiprev_line="$original_line"done < "$file"if [ "$has_issues" = true ]; thenecho -n "$fixed_content" > "$TEMP_DIR/$(basename "$file").expected"echo "$TEMP_DIR/$(basename "$file").expected"return 1fireturn 0
}# 检测交互式环境
is_interactive() {if [ -w /dev/tty ] 2>/dev/null; thenreturn 0elif [ -t 1 ] || [ -t 2 ]; thenreturn 0elif [ -n "$TERM" ] && [ "$TERM" != "dumb" ] && [ -z "$CI" ] && [ -z "$GITHUB_ACTIONS" ]; thenreturn 0fireturn 1
}# 获取本次提交的C++文件
cpp_files=$(git diff --cached --name-only --diff-filter=ACM $against | grep -E '\.(cpp|cc|cxx|c\+\+|h|hpp|hxx|h\+\+)$' || true)if [ -z "$cpp_files" ]; thenecho -e "${GREEN}没有C++文件需要检查${NC}"exit 0
fi# 检查每个文件
has_style_issues=false
files_to_compare=()while IFS= read -r file; doif [ ! -f "$file" ]; thencontinuefiecho -e "检查: ${BLUE}$file${NC}"expected_file=$(fix_style "$file")if [ $? -ne 0 ]; then# fix_style返回1表示有格式问题has_style_issues=truefiles_to_compare+=("$file:$expected_file")echo -e "  ${YELLOW}✗ 发现格式问题${NC}"elseecho -e "  ${GREEN}✓ 格式正确${NC}"fi
done <<< "$cpp_files"# 如果有格式问题
if [ "$has_style_issues" = true ]; thenechoecho -e "${RED}发现代码风格问题!${NC}"# 判断是否为交互式环境if ! is_interactive; thenecho -e "${YELLOW}非交互式环境,取消提交${NC}"exit 1fi# 检查meld是否安装if ! command -v meld >/dev/null 2>&1; thenecho -e "${RED}未安装 meld 工具${NC}"echo "安装: sudo apt-get install meld"exit 1fi# 使用meld比较每个文件for file_pair in "${files_to_compare[@]}"; docurrent_file="${file_pair%%:*}"expected_file="${file_pair##*:}"echoecho -e "${BLUE}=====================================${NC}"echo -e "${BLUE}文件: $current_file${NC}"echo -e "${GREEN}左侧 = 当前代码${NC}"echo -e "${YELLOW}右侧 = 期望代码${NC}"echo -e "${BLUE}=====================================${NC}"echo -e "${YELLOW}请在meld中查看差异,关闭meld窗口后继续...${NC}"echo# 启动meld(左侧当前,右侧期望)- 前台运行,等待用户关闭meld "$current_file" "$expected_file" 2>/dev/nullecho -e "${GREEN}meld已关闭${NC}"# 询问是否应用修改echo -ne "${YELLOW}应用期望代码到当前文件?(y/N) ${NC}"read -r applyif [[ "$apply" =~ ^[Yy]$ ]]; thencp "$current_file" "$current_file.backup"cp "$expected_file" "$current_file"git add "$current_file"echo -e "${GREEN}✓ 已应用并添加到暂存区${NC}"echo -e "${GREEN}  备份: $current_file.backup${NC}"elseecho -e "${YELLOW}跳过此文件${NC}"fidoneechoecho -ne "${YELLOW}是否继续提交?(y/N) ${NC}"read -r continue_commitif [[ ! "$continue_commit" =~ ^[Yy]$ ]]; thenecho -e "${RED}提交已取消${NC}"exit 1fi
fiecho -e "${GREEN}=== 检查通过 ===${NC}"
exit 0
 
注意事项
- 钩子脚本必须有 可执行权限:
chmod +x .githooks/pre-commit - 钩子 不会自动在克隆仓库后生效,需团队成员手动运行 
git config core.hooksPath .githooks(可通过README或初始化脚本引导) - 更推荐使用专业工具管理钩子,如: 
- Husky(Node.js 项目)
 - pre-commit(多语言支持,通过 YAML 配置)
 
 
