Last Updated on

前言

Git作为最常用的项目版本管理工具,Git的使用,一直都没有写文章来详细说说,后续有机会,会把git系列更一下,基础当很重要。

这里,由于最近碰到一些机会,先来说说pre-commit预提交。

正文

git hook

Git能在特定的重要动作发生时触发自定义脚本钩子。钩子分为两组:

  • 客户端钩子:pre-commit, prepare-commit-msg, commit-msg, post-commit等,主要在服务端接收提交对象时、推送到服务器之前调用。
  • 服务器钩子:pre-receive, post-receive, update等,主要在服务端接收提交对象时、推送到服务器之前调用。

git hooks位置位于每个git项目下的.git/hooks目录里,进去后会看到这些钩子的官方示例,都是以.sample结尾的文件,这些示例脚本是不会执行的,去掉.sample后缀可激活该钩子脚本。

PS:GIt hooks的每个钩子的作用和说明,详细的以官方文档为准:https://git-scm.com/docs/githooks

pre-commit说明

pre-commit预提交,是git hooks中的一个钩子,由git commit命令调用,可以通过--no-verify参数绕过调用pre-commit。通常用于在提交代码前,进行代码规范检查。

但是如果直接通过编写git hooks脚本来实现代码规范检查,会有如下的一些问题:

  • 规范越多,编写的脚本越复杂
  • 本地的规范修改,没法方便的同步到项目中其他开发人员
  • 不同语言,代码规范不一样,脚本设置都不同

那为了更方便的管理pre-commit的设置,于是有了一个同名的工具项目pre-commit,一个用于管理和维护多语言预提交挂钩的框架。官网地址:https://pre-commit.com/

PS:由于工具项目名称和git hooks的钩子同名,容易混淆概念,主要注意一下。这里我们详细说明使用的则是同名的工具项目,而不是git自身的pre-commit脚本钩子

pre-commit框架使用

此框架是多语言的,这里以Python为例,下面内容均为Python相关的使用说明。版本为最新版2.15.0
官方文档:https://pre-commit.com/

pre-commit框架,随着发展,已经不单单只能用于git hooks的pre-commit阶段,而是能作用于所有git hooks的所有阶段,如上面说的prepare-commit-msg, commit-msg, post-commi等,具体有哪些阶段,参考git官方文档:https://git-scm.com/docs/githooks

也就是说,pre-commit不单单可以用于规范代码检测,还可以在git的不同阶段,自定义做出各种不同的操作,灵活性很大,非常好用。

使用步骤

  1. 安装

    # 直接使用pip安装即可
    pip install pre-commit

    安装成功后,使用pre-commit --version可正常查看版本。

  2. 设置配置文件
    pre-commit依赖项目根目录配置文件.pre-commit-config.yaml需要手动在根目录创建此文件。

    # 在根目录执行如下命令,生成一个默认的配置,python版本
    pre-commit sample-config > .pre-commit-config.yaml

    这里配置的详细设置,后面详细说,这里先使用官方示例。

  3. 设置git hooks钩子脚本
    pre-commit框架是通过git hooks本身的钩子来调用的,所以在设置好配置文件后,会根据配置,在git项目中将钩子脚本配置到.git/hooks路径下。

    因为pre-commit框架支持各种不同阶段的钩子,所以需要根据配置,配置中使用到什么阶段的钩子,就回自动配置对应的hooks脚本到.git/hooks

    # 直接执行此命令,设置git hooks钩子脚本
    pre-commit install
  4. 对所有文件进行一次检查(可选)
    配置设置好后,默认是需要git commit命令来出发调用的,只会检查git commit中变更的文件。对所有文件进行检查:

    # 直接执行此命令,进行全文件检查
    pre-commit run --all-files

.pre-commit-config.yaml配置详细说明

首先,是顶层的全局配置,配置项有如下这些:

repos (必需)存储库映射列表
default_language_version (可选)从语言到应该用于该语言的默认language_version的映射。这将只覆盖没有设置language_version的单个钩子。默认为:{}
default_stages (可选)钩子的stages属性的配置范围默认值。这将只覆盖没有设置stages的单个钩子。默认值:all stages
files (可选)全局文件包含,正则匹配模版,1.21.0新配置,默认值:""
exclude (可选)全局文件排除,正则匹配模版,1.1.0版本新配置,默认值:^$
fail_fast (可选)设置为true时,预提交将在第一次失败后停止运行钩子。1.1.0版本新配置
minimum_pre_commit_version (可选)需要pre-commit的最小版本。1.15.0版本新配置

举个例子说明一下,一般常用的顶层配置大致如下:

# 设置默认的阶段为commit,只在提交时进行检查
default_stages:
- commit

# 设置默认的语言版本,你也可以在每个repos中单独设置language_version
default_language_version:
  python: python3.7

# 设置排除的文件,正则匹配,这个也可以在每个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为例

default_stages:
- commit
default_language_version:
  python: python3.7
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v4.0.1
  hooks:
  - id: trailing-whitespace
  - id: end-of-file-fixer
  - id: check-yaml
  - id: check-symlinks
  - id: check-merge-conflict
  - id: check-json
  - id: check-added-large-files
    args: [--maxkb=51200]
- repo: https://gitlab.com/PyCQA/flake8
  rev: 4.0.1
  hooks:
  - id: flake8
    args: [--max-line-length=120]

如上,则是一个常用的检测配置,使用了flake8检测python代码风格以遵守pep8规范,同时还添加了pre-commit框架项目提供的一些公共hooks,具体的使用说明,都可以直接访问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/

官方文档比较简单,看起来也不难,以上大多都是官方文档中的内容,后续更新和使用也以官方文档为准