本文主要讲解如何使用 pre-commit 结合静态代码分析工具。
学习前端发现了 Husky 能在 git commit 的时候自动触发代码格式化和检查,于是我想 Python 也应该有此类工具,就搜索到了 pre-commit
# pre-commit 说明
pre-commit 预提交,是 git hooks 中的一个钩子,由 git commit 命令调用,可以通过 --no-verify 参数绕过调用 pre-commit。通常用于在提交代码前,进行代码规范检查。
为了更方便的管理 pre-commit 的设置,于是有了一个同名的工具项目 pre-commit,一个用于管理和维护多语言预提交挂钩的框架。官网地址:https://pre-commit.com/ (opens new window)
PS:由于工具项目名称和 git hooks 的钩子同名,容易混淆概念,主要注意一下。这里我们详细说明使用的则是同名的工具项目,而不是 git 自身的 pre-commit 脚本钩子
# pre-commit 框架使用
此框架是多语言的,这里以 Python 为例,下面内容均为 Python 相关的使用说明。版本为最新版 2.19.0
pre-commit 框架,随着发展,已经不单单只能用于 git hooks 的 pre-commit 阶段,而是能作用于所有 git hooks 的所有阶段,如上面说的 prepare-commit-msg, commit-msg, post-commi 等,具体有哪些阶段,参考 git 官方文档:https://git-scm.com/docs/githooks (opens new window)
也就是说,pre-commit 不单单可以用于规范代码检测,还可以在 git 的不同阶段,自定义做出各种不同的操作,灵活性很大,非常好用。
# 使用步骤
安装
# 直接使用pip安装即可 pip3 install pre-commit安装成功后,使用
pre-commit --version可正常查看版本。设置配置文件 pre-commit 依赖项目根目录配置文件
.pre-commit-config.yaml。需要手动在根目录创建此文件。# 在根目录执行如下命令,生成一个默认的配置,python版本 pre-commit sample-config > .pre-commit-config.yaml这里配置的详细设置,后面详细说,这里先使用官方示例。
设置 git hooks 钩子脚本 pre-commit 框架是通过 git hooks 本身的钩子来调用的,所以在设置好配置文件后,会根据配置,在 git 项目中将钩子脚本配置到
.git/hooks路径下。因为 pre-commit 框架支持各种不同阶段的钩子,所以需要根据配置,配置中使用到什么阶段的钩子,就回自动配置对应的
hooks脚本到.git/hooks中# 直接执行此命令,设置git hooks钩子脚本 pre-commit install对所有文件进行一次检查 (可选) 配置设置好后,默认是需要
git commit命令来出发调用的,只会检查git commit中变更的文件。对所有文件进行检查:# 直接执行此命令,进行全文件检查,--all-files 可简写为 -a pre-commit run --all-files常用命令
pre-commit gc # 清理未使用的缓存 repo
pre-commit clean # 清除缓存的 pre-commit 文件
pre-commit autoupdate # 将 pre-commit 的配置自动更新到最新的 repos 版本
# .pre-commit-config.yaml 配置详细描述
首先,是顶层的全局配置,配置项有如下这些:
| 字段名 | 描述 |
|---|---|
| repos | (必需)存储库映射列表 |
| default_install_hook_types | (可选:默认 [pre-commit])--hook-type 列表,在运行 pre-commit install 时默认使用。 |
| default_language_version | (可选:默认 {})从语言到应用于该语言的默认 language_version 的映射。这只会覆盖没有设置 language_version 的单个钩子。 |
| default_stages | (可选:默认 (all stages))钩子的 stages 属性的配置范围默认值。这只会覆盖没有设置 stages 的单个钩子。 |
| files | (可选:默认 "")全局文件包含模式,正则匹配。 |
| exclude | (可选:默认^$)全局文件排除模式,正则匹配。 |
| fail_fast | (可选:默认 false)设置为 true 时,预提交将在第一次失败后停止运行钩子。 |
| minimum_pre_commit_version | (可选:默认 '0')需要的最低 pre-commit 版本。 |
举个例子说明一下,一般常用的顶层配置大致如下:
# 设置默认的阶段为commit,只在提交时进行检查
default_stages:
- commit
# 设置默认的语言版本,你也可以在每个repos中单独设置language_version
default_language_version:
python: python3.8
# 设置排除的文件,正则匹配,这个也可以在每个repos中的hooks脚本中单独配置,以灵活实现不同文件对不同规则的检测排除
exclude: "^$"
# 起码顶层配置,一般使用默认值即可。
# 这个repos配置下面的内容,就是最重要的核心配置,下面细说
repos:
- ......
# repos 配置
repos 配置是一个存储库映射列表,用来告诉 pre-commit 从哪里获取钩子的代码。其有三个下层配置,都是必填,如下:
| 字段名 | 描述 |
|---|---|
| repo | git clone 的存储库 url |
| rev | git clone 要拉取的 tag 或者 revision |
| hooks | 钩子映射列表,也就是要进行检测的钩子列表 |
第二层配置很简单,就是指定 repo 库的地址和版本 tag,拉取代码,然后指定使用这个库中具体的哪些 hooks 脚本。
其中,hooks 配置为核心配置。
有哪些 hooks 脚本可选,都是从 git 库中的配置获得。需要具体看这个 git 库的使用方式。因此允许自定义库,自定义 hooks 脚本,然后通过 pre-commit 拉取使用。
hooks 配置下层又有如下配置项:
| 字段名 | 描述 |
|---|---|
| id | (必须)要使用存储库中的哪个钩子,钩子的 id |
| alias | (可选)允许钩子在使用 pre-commit 运行 时使用额外的 id 来引用,一般不常用。 |
| name | (可选)重写钩子的名称 - 在钩子执行时显示 |
| language_version | (可选)覆盖钩子的语言版本 |
| files | (可选)覆盖默认的文件包含,正则匹配模版 |
| exclude | (可选)覆盖默认的文件排除,正则匹配模版 |
| types | (可选)要运行的文件类型列表 (AND) |
| types_or | (可选)要在 (OR) 上运行的文件类型列表 |
| exclude_types | (可选)要排除的文件的类型列表 |
| args | (可选)要传递给钩子的附加参数列表 |
| stages | (可选)将钩子限制在 commit, merge-commit, push, prepare-commit-msg, commit-msg, post-checkout, post-commit, post-merge, post-rewrite, or manual 阶段。这也就是 git 自身支持的不同阶段的钩子 |
| additional_dependencies | (可选)将被安装在该钩子运行的环境中的依赖项列表。 |
| always_run | (可选)如果为 true,即使没有匹配的文件,这个钩子也会运行 |
| verbose | (可选)如果为 true,则强制打印该钩子的输出,即使该钩子通过 |
| log_file | (可选)日志文件路径,如果存在,当钩子失败或 verbose 为 true 时,钩子输出将额外写入此文件 |
# 实用配置
举例说明,下面就列出一份 Python 版本的配置,看看一般常见的配置是怎样的。
exclude: (migrations/|test/)
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
- id: check-toml
- id: check-yaml
- id: check-added-large-files
args: [--maxkb=51200]
- id: requirements-txt-fixer
- id: check-merge-conflict
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
args: [--skip-string-normalization]
exclude: migrations/
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.0.277"
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
如上,则是一个常用的检测配置,使用了 ruff (包含 flake8、pylint、isort、pyupgrade 等的规则)进行静态代码检查,black 进行代码格式化,同时还添加了 pre-commit 框架项目提供的一些公共 hooks,具体的使用说明(更多 args 和自定义配置),可以直接访问各 repo 地址,查看文档说明。
# 自动启用 pre-commit
看上面的介绍和使用,会发现有个问题:从 git 库中 clone 一个项目到本地后,即使项目中有 .pre-commit-config.yaml 配置文件,但是 .git/hooks 中还是没有设置钩子,需要运行 pre-commit install 安装后,才能正常使用。
那如果要让他根据 .per-commit-config.yaml 配置自动安装 git 钩子呢?让我们直接 clone 项目后,便自动启用了 pre-commit。
官方给了一个解决方式:
使用 pre-commit init-templatedir 命令,自动设置 git 项目的初始化模版,在版本中自动执行 pre-commit install 以启用 pre-commit。
设置步骤如下:
# 设置git全局变量,制定初始化模版目录
git config --global init.templateDir ~/.git-template
# 自动设置初始化模版
pre-commit init-templatedir ~/.git-template
ok,到此就可以了,然后可以测试一下,clone 一个带 .per-commit-config.yaml 配置的库,然后进行提交,则会自动进行 pre-commit 的检测。如果没有 .per-commit-config.yaml 配置,则会跳过,普通正常使用。
pre-commit 还有很多的命令和用法,这里只是列举了常见的使用,如果要进行更高级的自定义和使用,详细参考官方文档:https://pre-commit.com/ (opens new window)
官方文档比较简单,看起来也不难,以上大多都是官方文档中的内容,后续更新和使用也以官方文档为准
参考文章: