pre-commit 框架:跨语言项目的 git hook 统一管理

起因

团队仓库混 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 已经修好的不重检)

踩过的坑

  1. pre-commit 第一次跑超慢:每个 hook 创建 isolated env 装依赖。
    .pre-commit-cache 缓存几个 GB 之后才稳定。CI 里 cache 该目录。

  2. hook 改了文件 → commit 失败:合理设计,但新人会困惑。
    告诉团队"hook fail 看下面输出 + 重新 git add + commit"流程。

  3. SKIP= 滥用:成员养成习惯跳所有 hook。在 PR 模板里加"是否跑了
    pre-commit"checkbox。CI 一定要跑兜底。

  4. autoupdate 引入 breaking change:新版 ruff 突然报 50 个新警告。
    autoupdate 后单独 PR 修问题再合主。

  5. hook 内拉 docker imagehadolint-docker hook 第一次拉 image
    慢。可以换成本地 hadolint(先 brew install hadolint)+
    repo: local

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。