起因
团队仓库混 Python + Go + TypeScript + YAML,每种语言一套 linter /
formatter。新人 clone 后 IDE 不一定配齐 → 推上来的 PR 各种格式不一致。
review 时纠结 "tab 还是空格" 很烦。
pre-commit 是 Python 写的跨语言 git hook 框架,几行 YAML 配齐
所有语言的 lint / format,commit 时自动跑。改不合规直接拒绝。
解决方案
1. 装
pipx install pre-commit
# 或 uv tool install pre-commit
2. .pre-commit-config.yaml
放仓库根:
default_install_hook_types: [pre-commit, commit-msg]
default_stages: [pre-commit]
repos:
# 通用:尾空格 / 文件末尾换行 / 大文件 / merge conflict
- 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: ruff 一统 (lint + format + import sort)
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff # lint + autofix
args: [--fix]
- id: ruff-format # 替代 black
# Go: gofmt / golangci-lint
- repo: https://github.com/golangci/golangci-lint
rev: v1.61.0
hooks:
- id: golangci-lint
# TypeScript / JavaScript / CSS / Markdown: Prettier
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
types_or: [javascript, jsx, ts, tsx, css, scss, json, yaml, markdown]
# ESLint
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v9.10.0
hooks:
- id: eslint
files: \.(js|jsx|ts|tsx)$
additional_dependencies:
- [email protected]
- [email protected]
# Shell scripts
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.10.0.1
hooks:
- id: shellcheck
# Dockerfile
- repo: https://github.com/hadolint/hadolint
rev: v2.13.0
hooks:
- id: hadolint-docker
# commit message: conventional commits
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.4.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
args: [feat, fix, docs, style, refactor, perf, test, chore]
3. install
pre-commit install
# .git/hooks/pre-commit 被注入
# 之后 git commit 自动触发
4. 试一下
echo 'x ' > test.txt # 故意尾空格
git add test.txt
git commit -m 'test'
# trim trailing whitespace.............................Failed
# - hook id: trailing-whitespace
# - exit code: 1
# - files were modified by this hook
#
# Fixing test.txt
hook 自动修了(去掉尾空格),但本次 commit 失败。再 git add + git commit:
git add test.txt
git commit -m 'fix: trim whitespace'
# all passed!
5. 给整个仓库初始跑一遍(修复存量问题)
pre-commit run --all-files
# 跑所有 hook 在所有文件上
# 第一次可能有大量改动;review 后 commit
6. CI 也跑(防绕过)
# .github/workflows/lint.yml
- uses: actions/checkout@v4
- uses: pre-commit/[email protected]
开发者 commit 时绕过(git commit --no-verify)的情况,CI 兜底。
7. 局部跳过某个 hook
SKIP=ruff git commit -m 'wip: temp'
不推荐常态化,应急用。
8. 排除某些文件
exclude: |
(?x)^(
vendor/.*|
generated/.*|
migrations/.*|
\.min\.js$
)$
或者单 hook 排除:
- id: prettier
exclude: ^public/build/
9. 自动更新 hook 版本
pre-commit autoupdate
# 把 yaml 里所有 rev 改成各 repo 最新 release tag
每月跑一次保持 hooks 最新。结果 commit 进 git。
10. 自动加进 commit
- id: ruff
args: [--fix]
always_run: true
--fix 让 ruff 自动改。改完文件后 hook fail(让你重新 add)。
更激进的"hook 失败也不阻拦 commit":
pre-commit run --hook-stage manual
把 stages: [manual] 的 hook 设为"手动跑",commit 时不阻拦。
与 husky / lefthook 对比
| pre-commit | husky | lefthook | |
|---|---|---|---|
| 语言无关 | ✅ | 半 (Node 项目优先) | ✅ |
| 装 / 配 | Python | npm | 二进制 |
| hook 仓库化 | ✅ pre-commit-hooks 大量 | 自己写 | 自己写 |
| 性能 | 中(Python 启动) | 快 | 极快 (Go) |
| 易用 | 高 | 高 | 中 |
混语言仓库 / Python 项目 → pre-commit;纯 Node 项目 → husky 也行;
极致性能 → lefthook。
monorepo 多服务
# 给每个子目录的 hook 限制路径
- id: ruff
files: ^backend/
- id: eslint
files: ^frontend/
避免在 backend 改 Python 时跑 frontend eslint。
效果
我们仓库接入 pre-commit 后:
- PR 里 "形式修改" commit 消失(已经在本地修了)
- review 只关注业务逻辑
- 新人 clone 后
pre-commit install一次,之后所有规范自动 enforce - CI lint 时间从 5 分钟 → 1 分钟(pre-commit 已经修好的不重检)
踩过的坑
-
pre-commit 第一次跑超慢:每个 hook 创建 isolated env 装依赖。
.pre-commit-cache缓存几个 GB 之后才稳定。CI 里 cache 该目录。 -
hook 改了文件 → commit 失败:合理设计,但新人会困惑。
告诉团队"hook fail 看下面输出 + 重新 git add + commit"流程。 -
SKIP=滥用:成员养成习惯跳所有 hook。在 PR 模板里加"是否跑了
pre-commit"checkbox。CI 一定要跑兜底。 -
autoupdate 引入 breaking change:新版 ruff 突然报 50 个新警告。
autoupdate 后单独 PR 修问题再合主。 -
hook 内拉 docker image:
hadolint-dockerhook 第一次拉 image
慢。可以换成本地hadolint(先brew install hadolint)+
repo: local。
登录后参与评论。