git-hooks-automation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGit Hooks Automation
Git Hooks 自动化
Automate code quality enforcement at the Git level. Set up hooks that lint, format, test, and validate before commits and pushes ever reach your CI pipeline — catching issues in seconds instead of minutes.
在Git层面自动化执行代码质量校验。在提交和推送进入CI流水线之前,设置钩子来执行代码检查、格式化、测试和验证——在数秒内发现问题,而非等待数分钟。
When to Use This Skill
何时使用该技能
- User asks to "set up git hooks" or "add pre-commit hooks"
- Configuring Husky, lint-staged, or the pre-commit framework
- Enforcing commit message conventions (Conventional Commits, commitlint)
- Automating linting, formatting, or type-checking before commits
- Setting up pre-push hooks for test runners
- Migrating from Husky v4 to v9+ or adopting hooks from scratch
- User mentions "pre-commit", "commit-msg", "pre-push", "lint-staged", or "githooks"
- 用户要求"设置git hooks"或"添加pre-commit钩子"
- 配置Husky、lint-staged或pre-commit框架
- 强制执行提交信息规范(Conventional Commits、commitlint)
- 提交前自动执行代码检查、格式化或类型校验
- 设置pre-push钩子来运行测试用例
- 从Husky v4迁移到v9+或从零开始引入钩子
- 用户提及"pre-commit"、"commit-msg"、"pre-push"、"lint-staged"或"githooks"
Git Hooks Fundamentals
Git Hooks 基础
Git hooks are scripts that run automatically at specific points in the Git workflow. They live in and are not version-controlled by default — which is why tools like Husky exist.
.git/hooks/Git hooks是在Git工作流特定节点自动运行的脚本。它们默认存储在目录下,且不会被版本控制——这也是Husky这类工具存在的原因。
.git/hooks/Hook Types & When They Fire
钩子类型及触发时机
| Hook | Fires When | Common Use |
|---|---|---|
| Before commit is created | Lint, format, type-check staged files |
| After default msg, before editor | Auto-populate commit templates |
| After user writes commit message | Enforce commit message format |
| After commit is created | Notifications, logging |
| Before push to remote | Run tests, check branch policies |
| Before rebase starts | Prevent rebase on protected branches |
| After merge completes | Install deps, run migrations |
| After checkout/switch | Install deps, rebuild assets |
| 钩子 | 触发时机 | 常见用途 |
|---|---|---|
| 提交创建前 | 对暂存文件执行代码检查、格式化、类型校验 |
| 生成默认提交信息后,打开编辑器前 | 自动填充提交模板 |
| 用户编写提交信息后 | 强制执行提交信息格式 |
| 提交创建后 | 发送通知、记录日志 |
| 推送到远程仓库前 | 运行测试、检查分支策略 |
| 变基开始前 | 禁止对受保护分支执行变基 |
| 合并完成后 | 安装依赖、运行迁移脚本 |
| 切换分支/检出后 | 安装依赖、重新构建资源 |
Native Git Hooks (No Framework)
原生Git Hooks(无框架)
bash
undefinedbash
undefinedCreate a pre-commit hook manually
Create a pre-commit hook manually
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
set -e
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
set -e
Run linter on staged files only
Run linter on staged files only
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '.(js|ts|jsx|tsx)$' || true)
if [ -n "$STAGED_FILES" ]; then
echo "🔍 Linting staged files..."
echo "$STAGED_FILES" | xargs npx eslint --fix
echo "$STAGED_FILES" | xargs git add # Re-stage after fixes
fi
EOF
chmod +x .git/hooks/pre-commit
**Problem**: `.git/hooks/` is local-only and not shared with the team. Use a framework instead.STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '.(js|ts|jsx|tsx)$' || true)
if [ -n "$STAGED_FILES" ]; then
echo "🔍 Linting staged files..."
echo "$STAGED_FILES" | xargs npx eslint --fix
echo "$STAGED_FILES" | xargs git add # Re-stage after fixes
fi
EOF
chmod +x .git/hooks/pre-commit
**问题**:`.git/hooks/`目录仅本地存在,不会与团队共享。建议使用框架来管理。Husky + lint-staged (Node.js Projects)
Husky + lint-staged(Node.js 项目)
The modern standard for JavaScript/TypeScript projects. Husky manages Git hooks; lint-staged runs commands only on staged files for speed.
这是JavaScript/TypeScript项目的现代标准方案。Husky负责管理Git hooks;lint-staged仅对暂存文件执行命令,提升速度。
Quick Setup (Husky v9+)
快速搭建(Husky v9+)
bash
undefinedbash
undefinedInstall
Install
npm install --save-dev husky lint-staged
npm install --save-dev husky lint-staged
Initialize Husky (creates .husky/ directory)
Initialize Husky (creates .husky/ directory)
npx husky init
npx husky init
The init command creates a pre-commit hook — edit it:
The init command creates a pre-commit hook — edit it:
echo "npx lint-staged" > .husky/pre-commit
undefinedecho "npx lint-staged" > .husky/pre-commit
undefinedConfigure lint-staged in package.json
package.json在package.json
中配置lint-staged
package.jsonjson
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix --max-warnings=0",
"prettier --write"
],
"*.{css,scss}": [
"prettier --write",
"stylelint --fix"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
}
}json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix --max-warnings=0",
"prettier --write"
],
"*.{css,scss}": [
"prettier --write",
"stylelint --fix"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
}
}Add Commit Message Linting
添加提交信息校验
bash
undefinedbash
undefinedInstall commitlint
Install commitlint
npm install --save-dev @commitlint/cli @commitlint/config-conventional
npm install --save-dev @commitlint/cli @commitlint/config-conventional
Create commitlint config
Create commitlint config
cat > commitlint.config.js << 'EOF'
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', [
'feat', 'fix', 'docs', 'style', 'refactor',
'perf', 'test', 'build', 'ci', 'chore', 'revert'
]],
'subject-max-length': [2, 'always', 72],
'body-max-line-length': [2, 'always', 100]
}
};
EOF
cat > commitlint.config.js << 'EOF'
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', [
'feat', 'fix', 'docs', 'style', 'refactor',
'perf', 'test', 'build', 'ci', 'chore', 'revert'
]],
'subject-max-length': [2, 'always', 72],
'body-max-line-length': [2, 'always', 100]
}
};
EOF
Add commit-msg hook
Add commit-msg hook
echo "npx --no -- commitlint --edit $1" > .husky/commit-msg
undefinedecho "npx --no -- commitlint --edit $1" > .husky/commit-msg
undefinedAdd Pre-Push Hook
添加Pre-Push钩子
bash
undefinedbash
undefinedRun tests before pushing
Run tests before pushing
echo "npm test" > .husky/pre-push
undefinedecho "npm test" > .husky/pre-push
undefinedComplete Husky Directory Structure
完整的Husky目录结构
project/
├── .husky/
│ ├── pre-commit # npx lint-staged
│ ├── commit-msg # npx --no -- commitlint --edit $1
│ └── pre-push # npm test
├── commitlint.config.js
├── package.json # lint-staged config here
└── ...project/
├── .husky/
│ ├── pre-commit # npx lint-staged
│ ├── commit-msg # npx --no -- commitlint --edit $1
│ └── pre-push # npm test
├── commitlint.config.js
├── package.json # lint-staged config here
└── ...pre-commit Framework (Python / Polyglot)
pre-commit框架(Python / 多语言)
Language-agnostic framework that works with any project. Hooks are defined in YAML and run in isolated environments.
语言无关的框架,适用于任何项目。钩子通过YAML定义,并在隔离环境中运行。
Setup
搭建步骤
bash
undefinedbash
undefinedInstall (Python required)
Install (Python required)
pip install pre-commit
pip install pre-commit
Create config
Create config
cat > .pre-commit-config.yaml << 'EOF'
repos:
Built-in checks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-added-large-files args: ['--maxkb=500']
- id: check-merge-conflict
- id: detect-private-key
Python formatting
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
Python linting
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
- id: ruff args: ['--fix']
- id: ruff-format
Shell script linting
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.10.0.1
hooks:
- id: shellcheck
Commit message format
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.2.0
hooks:
- id: conventional-pre-commit stages: [commit-msg] EOF
cat > .pre-commit-config.yaml << 'EOF'
repos:
Built-in checks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-added-large-files args: ['--maxkb=500']
- id: check-merge-conflict
- id: detect-private-key
Python formatting
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
Python linting
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
- id: ruff args: ['--fix']
- id: ruff-format
Shell script linting
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.10.0.1
hooks:
- id: shellcheck
Commit message format
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.2.0
hooks:
- id: conventional-pre-commit stages: [commit-msg] EOF
Install hooks into .git/hooks/
Install hooks into .git/hooks/
pre-commit install
pre-commit install --hook-type commit-msg
pre-commit install
pre-commit install --hook-type commit-msg
Run against all files (first time)
Run against all files (first time)
pre-commit run --all-files
undefinedpre-commit run --all-files
undefinedKey Commands
常用命令
bash
pre-commit install # Install hooks
pre-commit run --all-files # Run on everything (CI or first setup)
pre-commit autoupdate # Update hook versions
pre-commit run <hook-id> # Run a specific hook
pre-commit clean # Clear cached environmentsbash
pre-commit install # Install hooks
pre-commit run --all-files # Run on everything (CI or first setup)
pre-commit autoupdate # Update hook versions
pre-commit run <hook-id> # Run a specific hook
pre-commit clean # Clear cached environmentsCustom Hook Scripts (Any Language)
自定义钩子脚本(任意语言)
For projects not using Node or Python, write hooks directly in shell.
对于不使用Node或Python的项目,可以直接用Shell编写钩子。
Portable Pre-Commit Hook
可移植的Pre-Commit钩子
bash
#!/bin/shbash
#!/bin/sh.githooks/pre-commit — Team-shared hooks directory
.githooks/pre-commit — Team-shared hooks directory
set -e
echo "=== Pre-Commit Checks ==="
set -e
echo "=== Pre-Commit Checks ==="
1. Prevent commits to main/master
1. Prevent commits to main/master
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "detached")
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
echo "❌ Direct commits to $BRANCH are not allowed. Use a feature branch."
exit 1
fi
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "detached")
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
echo "❌ Direct commits to $BRANCH are not allowed. Use a feature branch."
exit 1
fi
2. Check for debugging artifacts
2. Check for debugging artifacts
if git diff --cached --diff-filter=ACM | grep -nE '(console.log|debugger|binding.pry|import pdb)' > /dev/null 2>&1; then
echo "⚠️ Debug statements found in staged files:"
git diff --cached --diff-filter=ACM | grep -nE '(console.log|debugger|binding.pry|import pdb)'
echo "Remove them or use git commit --no-verify to bypass."
exit 1
fi
if git diff --cached --diff-filter=ACM | grep -nE '(console.log|debugger|binding.pry|import pdb)' > /dev/null 2>&1; then
echo "⚠️ Debug statements found in staged files:"
git diff --cached --diff-filter=ACM | grep -nE '(console.log|debugger|binding.pry|import pdb)'
echo "Remove them or use git commit --no-verify to bypass."
exit 1
fi
3. Check for large files (>1MB)
3. Check for large files (>1MB)
LARGE_FILES=$(git diff --cached --name-only --diff-filter=ACM | while read f; do
size=$(wc -c < "$f" 2>/dev/null || echo 0)
if [ "$size" -gt 1048576 ]; then echo "$f ($((size/1024))KB)"; fi
done)
if [ -n "$LARGE_FILES" ]; then
echo "❌ Large files detected:"
echo "$LARGE_FILES"
exit 1
fi
LARGE_FILES=$(git diff --cached --name-only --diff-filter=ACM | while read f; do
size=$(wc -c < "$f" 2>/dev/null || echo 0)
if [ "$size" -gt 1048576 ]; then echo "$f ($((size/1024))KB)"; fi
done)
if [ -n "$LARGE_FILES" ]; then
echo "❌ Large files detected:"
echo "$LARGE_FILES"
exit 1
fi
4. Check for secrets patterns
4. Check for secrets patterns
if git diff --cached --diff-filter=ACM | grep -nEi '(AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9]{48}|ghp_[a-zA-Z0-9]{36}|password\s*=\s*["\x27][^"\x27]+["\x27])' > /dev/null 2>&1; then
echo "🚨 Potential secrets detected in staged changes! Review before committing."
exit 1
fi
echo "✅ All pre-commit checks passed"
undefinedif git diff --cached --diff-filter=ACM | grep -nEi '(AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9]{48}|ghp_[a-zA-Z0-9]{36}|password\s*=\s*["\x27][^"\x27]+["\x27])' > /dev/null 2>&1; then
echo "🚨 Potential secrets detected in staged changes! Review before committing."
exit 1
fi
echo "✅ All pre-commit checks passed"
undefinedShare Custom Hooks via core.hooksPath
core.hooksPath通过core.hooksPath
共享自定义钩子
core.hooksPathbash
undefinedbash
undefinedIn your repo, set a shared hooks directory
In your repo, set a shared hooks directory
git config core.hooksPath .githooks
git config core.hooksPath .githooks
Add to project setup docs or Makefile
Add to project setup docs or Makefile
Makefile
Makefile
setup:
git config core.hooksPath .githooks
chmod +x .githooks/*
undefinedsetup:
git config core.hooksPath .githooks
chmod +x .githooks/*
undefinedCI Integration
CI集成
Hooks are a first line of defense, but CI is the source of truth.
钩子是第一道防线,但CI才是最终的保障。
Run pre-commit in CI (GitHub Actions)
在CI中运行pre-commit(GitHub Actions)
yaml
undefinedyaml
undefined.github/workflows/lint.yml
.github/workflows/lint.yml
name: Lint
on: [push, pull_request]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: pre-commit/action@v3.0.1
undefinedname: Lint
on: [push, pull_request]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: pre-commit/action@v3.0.1
undefinedRun lint-staged in CI (Validation Only)
在CI中运行lint-staged(仅验证)
yaml
undefinedyaml
undefinedValidate that lint-staged would pass (catch bypassed hooks)
Validate that lint-staged would pass (catch bypassed hooks)
name: Lint Check
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx eslint . --max-warnings=0
- run: npx prettier --check .
undefinedname: Lint Check
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx eslint . --max-warnings=0
- run: npx prettier --check .
undefinedCommon Pitfalls & Fixes
常见问题及解决方法
Hooks Not Running
钩子未运行
| Symptom | Cause | Fix |
|---|---|---|
| Hooks silently skipped | Not installed in | Run |
| "Permission denied" | Hook file not executable | |
| Hooks run but wrong ones | Stale hooks from old setup | Delete |
| Works locally, fails in CI | Different Node/Python versions | Pin versions in CI config |
| 症状 | 原因 | 解决方法 |
|---|---|---|
| 钩子静默跳过 | 未安装到 | 运行 |
| "Permission denied"(权限拒绝) | 钩子文件无执行权限 | 执行 |
| 钩子运行但执行的是旧钩子 | 旧设置留下的过期钩子 | 删除 |
| 本地正常运行,CI中失败 | Node/Python版本不一致 | 在CI配置中固定版本 |
Performance Issues
性能问题
json
// ❌ Slow: runs on ALL files every commit
{
"scripts": {
"precommit": "eslint src/ && prettier --write src/"
}
}
// ✅ Fast: lint-staged runs ONLY on staged files
{
"lint-staged": {
"*.{js,ts}": ["eslint --fix", "prettier --write"]
}
}json
// ❌ 缓慢:每次提交都检查所有文件
{
"scripts": {
"precommit": "eslint src/ && prettier --write src/"
}
}
// ✅ 快速:lint-staged仅检查暂存文件
{
"lint-staged": {
"*.{js,ts}": ["eslint --fix", "prettier --write"]
}
}Bypassing Hooks (When Needed)
跳过钩子(必要时)
bash
undefinedbash
undefinedSkip all hooks for a single commit
单次提交跳过所有钩子
git commit --no-verify -m "wip: quick save"
git commit --no-verify -m "wip: quick save"
Skip pre-push only
仅跳过pre-push钩子
git push --no-verify
git push --no-verify
Skip specific pre-commit hooks
跳过特定的pre-commit钩子
SKIP=eslint git commit -m "fix: update config"
> **Warning**: Bypassing hooks should be rare. If your team frequently bypasses, the hooks are too slow or too strict — fix them.SKIP=eslint git commit -m "fix: update config"
> **警告**:跳过钩子应是极少数情况。如果团队频繁跳过钩子,说明钩子速度太慢或规则太严格——需要优化它们。Migration Guide
迁移指南
Husky v4 → v9 Migration
Husky v4 → v9迁移
bash
undefinedbash
undefined1. Remove old Husky
1. 移除旧版Husky
npm uninstall husky
rm -rf .husky
npm uninstall husky
rm -rf .husky
2. Remove old config from package.json
2. 从package.json中移除旧配置
Delete "husky": { "hooks": { ... } } section
删除"husky": { "hooks": { ... } }部分
3. Install fresh
3. 安装新版
npm install --save-dev husky
npx husky init
npm install --save-dev husky
npx husky init
4. Recreate hooks
4. 重新创建钩子
echo "npx lint-staged" > .husky/pre-commit
echo "npx --no -- commitlint --edit $1" > .husky/commit-msg
echo "npx lint-staged" > .husky/pre-commit
echo "npx --no -- commitlint --edit $1" > .husky/commit-msg
5. Clean up — old Husky used package.json config,
5. 清理——旧版Husky使用package.json配置,
new Husky uses .husky/ directory with plain scripts
新版Husky使用.husky/目录下的纯脚本
undefinedundefinedAdopting Hooks on an Existing Project
在现有项目中引入钩子
bash
undefinedbash
undefinedStep 1: Start with formatting only (low friction)
步骤1:从仅格式化开始(低阻力)
lint-staged config:
lint-staged配置:
{ "*.{js,ts}": ["prettier --write"] }
{ "*.{js,ts}": ["prettier --write"] }
Step 2: Add linting after team adjusts (1-2 weeks later)
步骤2:团队适应后添加代码检查(1-2周后)
{ "*.{js,ts}": ["eslint --fix", "prettier --write"] }
{ "*.{js,ts}": ["eslint --fix", "prettier --write"] }
Step 3: Add commit message linting
步骤3:添加提交信息校验
Step 4: Add pre-push test runner
步骤4:添加pre-push测试运行器
Gradual adoption prevents team resistance
逐步引入可避免团队抵触
undefinedundefinedKey Principles
核心原则
- Staged files only — Never lint the entire codebase on every commit
- Auto-fix when possible — flags reduce developer friction
--fix - Fast hooks — Pre-commit should complete in < 5 seconds
- Fail loud — Clear error messages with actionable fixes
- Team-shared — Use Husky or so hooks are version-controlled
core.hooksPath - CI as backup — Hooks are convenience; CI is the enforcer
- Gradual adoption — Start with formatting, add linting, then testing
- 仅针对暂存文件——不要在每次提交时检查整个代码库
- 尽可能自动修复——使用参数减少开发者的操作成本
--fix - 钩子要快速——pre-commit钩子应在5秒内完成
- 明确报错——提供清晰的错误信息和可操作的修复方案
- 团队共享——使用Husky或让钩子被版本控制
core.hooksPath - CI作为备份——钩子是便利工具;CI才是最终的执行者
- 逐步引入——从格式化开始,再添加代码检查,最后加入测试
Related Skills
相关技能
- - Deep audit before GitHub push
@codebase-audit-pre-push - - Verification before claiming work is done
@verification-before-completion - - Advanced shell scripting for custom hooks
@bash-pro - - CI/CD workflow templates
@github-actions-templates
- - GitHub推送前深度审计
@codebase-audit-pre-push - - 完成工作前的验证
@verification-before-completion - - 用于自定义钩子的高级Shell脚本
@bash-pro - - CI/CD工作流模板
@github-actions-templates