Chapter 08

pre-commit 钩子接入

让每次 git commit 前自动跑 Ruff,提交之前就把低级问题清零,永不提交带问题的代码。

8.1 pre-commit 框架概览

pre-commit 是一个用 Python 写的 Git hooks 管理框架——你只写一份 YAML,它帮你拉取、安装、运行各种检查工具。核心概念:

hook(钩子)
一段要在 Git 事件(commit、push)发生时自动运行的程序。本身是 Git 原生功能,但 pre-commit 框架在此之上提供了统一的声明式管理。
.pre-commit-config.yaml
项目根目录的配置文件,声明了要启用哪些钩子、从哪个仓库拉取、固定到哪个版本。
pre-commit install
一条命令把 .git/hooks/pre-commit 指向 pre-commit 框架,之后 git commit 会自动触发。

8.2 安装 pre-commit

$ uv tool install pre-commit
# 或:pipx install pre-commit / pip install pre-commit

$ pre-commit install
# pre-commit installed at .git/hooks/pre-commit

8.3 最小 .pre-commit-config.yaml

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.11.3      # 务必固定到具体 tag
    hooks:
      - id: ruff          # 跑 ruff check(带自动修复)
        args: [--fix]
      - id: ruff-format   # 跑 ruff format

把这个文件提交进仓库,队友 clone 下来后跑一次 pre-commit install 就能享受同样的钩子——配置跟代码一起版本化。

8.4 钩子的三种 ID

id等价命令用途
ruffruff check --force-exclude检查(可加 --fix
ruff-check同上的别名(新版本推荐)检查
ruff-formatruff format --force-exclude格式化
--force-exclude 的作用

pre-commit 默认会把"暂存的文件列表"显式传给钩子。但 Ruff 的 exclude 配置是在"遍历文件"时生效的——当文件被显式传入时默认会跳过 exclude 检查。--force-exclude 强制让 exclude 仍然生效,避免生成目录里的文件被误检查。

8.5 常见搭配

repos:
  # 通用检查(pre-commit 官方 hooks)
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files

  # Ruff(lint + format)
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.11.3
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  # mypy 类型检查(可选)
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.11.0
    hooks:
      - id: mypy
        additional_dependencies: ["types-requests"]

8.6 stages:什么时候跑

默认钩子在 pre-commit 阶段运行。可指定其他阶段:

- id: ruff
  args: [--fix]
  stages: [pre-commit, pre-push]   # 同时在 push 前跑一次

- id: ruff-format
  stages: [manual]   # 只在手动 pre-commit run --hook-stage manual 时跑

8.7 限定文件范围

- id: ruff
  args: [--fix]
  files: "^src/"          # 只扫 src/
  exclude: "^tests/fixtures/"

8.8 常用命令

$ pre-commit install       # 安装钩子(首次)
$ pre-commit run           # 对暂存文件跑所有钩子
$ pre-commit run --all-files   # 对所有文件跑一次(迁移时用)
$ pre-commit run ruff      # 只跑 ruff 钩子
$ pre-commit autoupdate    # 自动升级到最新 tag
$ pre-commit uninstall     # 卸载

8.9 绕过钩子:--no-verify(请慎用)

$ git commit --no-verify -m "emergency fix"

不推荐。CI 端应同样跑 Ruff,否则绕过等于没跑。强烈建议 CI 里加 ruff check . 作为兜底。

钩子失败后文件可能被改

ruff --fixruff-format 成功修改文件后,该钩子会失败(因为"有未暂存的修改")——这是设计。解决:git add -ugit commit。想让 pre-commit 自动 stage 修改后的文件,可在 CI/个人脚本里用 pre-commit run --files ... && git add -u 这类流程,但普通提交场景还是手动 add 更安全。

8.10 小结