在团队开发中,保证代码质量不能仅依赖开发者的自觉。本文将详细介绍如何使用 pre-commit 在代码提交前自动运行检查,确保代码符合团队规范。
一、什么是 pre-commit? 1.1 定义 pre-commit 是一个用于管理和维护多语言预提交钩子的框架。它可以在代码提交到 Git 仓库之前自动运行一系列检查,确保代码符合团队规范。
1.2 主要特点
特点
说明
多语言支持
支持 Python、JavaScript、Go、Rust 等多种语言的工具
自动安装
自动下载和安装所需的工具和依赖
缓存机制
只检查变更的文件,提高效率
丰富的 hooks 库
拥有大量预定义的钩子可供使用
多阶段支持
支持 pre-commit、pre-push、commit-msg 等多个 Git 钩子阶段
1.3 工作原理 1 git commit → 触发 .git/hooks/pre-commit → 运行检查 → 通过则提交,失败则阻止
二、安装与初始化 2.1 安装 pre-commit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pip install pre-commit uv add pre-commit --group dev pipx install pre-commit poetry add pre-commit --group dev brew install pre-commit pre-commit --version
2.2 初始化配置 1 2 3 4 5 6 7 8 9 10 pre-commit sample-config > .pre-commit-config.yaml pre-commit install pre-commit install --hook-type pre-commit pre-commit install --hook-type pre-push pre-commit install --hook-type commit-msg
2.3 pre-commit install 的作用 pre-commit install 会在项目的 .git/hooks/ 目录下创建一个 pre-commit 脚本文件。
工作原理 :
Git 在执行 git commit 时,会自动检查 .git/hooks/pre-commit 文件是否存在
如果存在且可执行,Git 会先运行这个脚本
脚本返回 0 表示成功,允许提交;非 0 表示失败,阻止提交
1 2 cat .git/hooks/pre-commit
简单说 :这个命令把 pre-commit 框架”挂载”到 Git 的提交流程中。
三、Git 钩子类型 3.1 支持的钩子类型 默认 pre-commit install 只安装 pre-commit 钩子。如果你想在其他 Git 阶段运行检查,需要显式安装:
1 2 pre-commit install --hook-type pre-push pre-commit install --hook-type commit-msg
完整的钩子类型(共 8 种) :
钩子类型
触发时机
典型用途
pre-commit
git commit 前
代码格式化、lint 检查
pre-push
git push 前
运行测试、安全检查
commit-msg
提交消息写入后
校验提交消息格式
pre-merge-commit
合并前
检查合并冲突
post-checkout
git checkout 后
环境初始化
post-commit
提交完成后
通知、日志记录
post-merge
合并完成后
依赖更新检查
prepare-commit-msg
编辑提交消息前
自动生成提交消息模板
四、配置文件详解 4.1 基础结构 创建 .pre-commit-config.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 default_language_version: python: python3.11 default_stages: [pre-commit ]fail_fast: true repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer
4.2 关键配置项解释 repos:hooks 来源每个 repo 代表一个 hooks 来源。定义多个是为了从不同来源获取不同的工具:
原因
说明
不同工具由不同团队维护
Black 由 PSF 维护,isort 由 PyCQA 维护
版本独立管理
每个 repo 可以指定不同的 rev(版本)
按需选择
只引用需要的 repo
隔离环境
每个 repo 的依赖独立安装,避免冲突
id:hook 的唯一标识符id 由仓库维护者在 .pre-commit-hooks.yaml 中定义。例如 pre-commit-hooks 仓库提供了 trailing-whitespace、end-of-file-fixer 等几十个 id。
additional_dependencies:额外依赖pre-commit 使用独立的隔离环境 ,与项目虚拟环境完全分离。如果 hook 需要额外依赖,需要在此声明:
1 2 3 4 5 6 7 - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.8.0 hooks: - id: mypy additional_dependencies: - django-stubs - types-requests
1 2 3 4 5 6 项目虚拟环境(.venv) ← 你手动安装的 mypy ↓ 不共享 ↓ pre-commit 缓存环境 ← pre-commit 自动下载的 mypy (~/.cache/pre-commit)
执行顺序 同一阶段的 hooks 按定义顺序 执行,所以通常把格式化工具放在检查工具之前:
1 2 3 4 5 6 7 8 9 10 repos: - repo: https://github.com/psf/black hooks: - id: black - repo: https://github.com/pycqa/flake8 hooks: - id: flake8
4.3 阶段的确定机制 阶段的确定遵循优先级规则 (从高到低):
1 2 3 4 5 6 7 8 9 10 11 12 repos: - repo: local hooks: - id: pytest stages: [pre-push ] default_stages: [pre-commit ]
4.4 repo: local vs 远程仓库
类型
特点
使用场景
远程仓库
自动下载、隔离环境、版本锁定
通用工具(black、flake8)
repo: local
使用项目环境中的工具
自定义脚本、需要项目依赖的工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 repos: - repo: https://github.com/psf/black rev: 24.3 .0 hooks: - id: black - repo: local hooks: - id: pytest name: pytest entry: uv run pytest language: system stages: [pre-push ] pass_filenames: false
注意 :如果在本地和远程同时定义了相同工具(如 isort),两个都会运行!pre-commit 不会自动去重。
五、完整配置示例 5.1 企业级完整配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 default_language_version: python: python3.11 default_stages: [pre-commit ]fail_fast: true ci: autofix_prs: false autoupdate_schedule: weekly repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: trailing-whitespace exclude: '^.*\.(md|rst)$' - id: end-of-file-fixer - id: check-yaml args: ['--unsafe' ] - id: check-json - id: check-toml - id: check-added-large-files args: ['--maxkb=1000' ] - id: check-merge-conflict - id: detect-private-key - id: debug-statements - id: check-case-conflict - id: check-docstring-first - id: check-executables-have-shebangs - id: mixed-line-ending args: ['--fix=lf' ] - id: name-tests-test args: ['--pytest-test-first' ] exclude: '^tests/factories/' - repo: https://github.com/pycqa/isort rev: 5.13 .2 hooks: - id: isort name: isort (python) args: ['--settings-path' , 'pyproject.toml' ] - repo: https://github.com/psf/black rev: 24.3 .0 hooks: - id: black name: black (python) args: ['--config' , 'pyproject.toml' ] - repo: https://github.com/asottile/pyupgrade rev: v3.15.0 hooks: - id: pyupgrade args: ['--py311-plus' ] - repo: https://github.com/pycqa/flake8 rev: 7.0 .0 hooks: - id: flake8 name: flake8 (python) additional_dependencies: - flake8-annotations>=2.9.1 - flake8-bugbear>=23.1.14 - flake8-comprehensions>=3.10.1 args: ['--config' , 'setup.cfg' ] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.8.0 hooks: - id: mypy name: mypy (python) additional_dependencies: - django-stubs>=4.2.0 - types-requests - types-PyYAML args: ['--config-file' , 'pyproject.toml' ] pass_filenames: false always_run: true - repo: local hooks: - id: lint-imports name: lint-imports entry: poetry run lint-imports language: system types: [python ] pass_filenames: false - repo: local hooks: - id: pytest name: pytest entry: poetry run pytest --testmon -q language: system types: [python ] stages: [pre-push ] pass_filenames: false
5.2 本地自定义 Hooks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 repos: - repo: local hooks: - id: custom-lint name: Custom Lint entry: poetry run python scripts/custom_lint.py language: system types: [python ] - id: check-version name: Check Version entry: ./scripts/check_version.sh language: script files: ^(pyproject\.toml|setup\.py)$ - id: django-check name: Django Check entry: poetry run python manage.py check language: system types: [python ] pass_filenames: false
六、pyupgrade - Python 语法升级 pyupgrade 是一个自动升级 Python 语法的工具:
1 2 3 4 5 - repo: https://github.com/asottile/pyupgrade rev: v3.15.0 hooks: - id: pyupgrade args: ['--py311-plus' ]
自动转换示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from typing import Dict , List , Optional , Union def func (items: List [str ] ) -> Dict [str , int ]: pass x: Optional [str ] = None y: Union [int , str ] = 1 def func (items: list [str ] ) -> dict [str , int ]: pass x: str | None = None y: int | str = 1
七、常用命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 pre-commit install pre-commit uninstall pre-commit install --hook-type pre-push pre-commit run --all-files pre-commit run pre-commit run black --all-files pre-commit autoupdate pre-commit clean pre-commit gc git commit --no-verify -m "紧急修复" SKIP=flake8,mypy git commit -m "跳过特定检查" pre-commit run --hook-stage pre-commit --verbose
八、常见问题 8.1 pre-commit 太慢 1 2 3 4 5 6 7 8 9 10 hooks: - id: mypy types: [python ] hooks: - id: mypy pass_filenames: false always_run: true
8.2 CI 失败但本地通过 1 2 3 4 5 6 7 8 9 pre-commit run --all-files pre-commit autoupdate pre-commit clean pre-commit run --all-files
8.3 紧急情况需要跳过检查 1 2 3 4 5 git commit --no-verify -m "hotfix: 紧急修复" SKIP=flake8,mypy git commit -m "跳过特定检查"
8.4 团队成员忘记安装 hooks 在 README.md 中添加开发设置说明:
1 2 3 4 5 ## 开发设置 1. 安装依赖 ```bash poetry install
安装 pre-commit hooks
1 2 poetry run pre-commit install poetry run pre-commit install --hook-type pre-push
验证安装
1 poetry run pre-commit run --all-files
1 2 3 4 5 6 或在 `pyproject.toml` 中配置自动安装: ```toml [tool.poetry.scripts] setup = "scripts.setup:main"
九、与 CI/CD 集成 pre-commit 可以在 CI 中运行,作为双重保障:
9.1 GitHub Actions 1 2 3 4 5 6 7 8 9 10 11 12 13 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.11' - uses: pre-commit/action@v3.0.1
9.2 GitLab CI 1 2 3 4 5 6 7 8 9 10 11 12 13 pre-commit: stage: check image: python:3.11 variables: PRE_COMMIT_HOME: "${CI_PROJECT_DIR}/.cache/pre-commit" before_script: - pip install pre-commit script: - pre-commit run --all-files cache: key: pre-commit-${CI_COMMIT_REF_SLUG} paths: - .cache/pre-commit
9.3 只检查变更文件(优化 CI) 1 2 3 4 5 6 7 script: - | if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]; then pre-commit run --from-ref origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME --to-ref HEAD else pre-commit run --all-files fi
十、总结 核心要点
概念
说明
pre-commit
Git 钩子管理框架
hooks
自动运行的检查脚本
repos
hooks 的来源仓库
stages
运行的 Git 阶段
隔离环境
每个 hook 独立安装依赖
推荐配置
格式化工具 :Black、isort、pyupgrade
检查工具 :flake8、mypy
通用检查 :trailing-whitespace、check-yaml、detect-private-key
推送前测试 :pytest(在 pre-push 阶段)
工作流 1 2 3 4 5 编写代码 → git add → git commit ↓ pre-commit 自动运行 ↓ 格式化 → 检查 → 通过则提交
pre-commit 是保证代码质量的第一道防线,与 CI/CD 配合使用,形成完整的代码质量工作流!
相关文章