Git hooks 钩子的使用

Git hooks 简介

Git 能在特定的重要动作发生时触发自定义脚本。比如,commit之前检查commit message是否符合约定的格式,push之前检查代码格式是否正确,是否编译通过等等。Git 就提供了hooks这样的机制。

我们在哪能找到hooks呢?在初始化代码仓库git init时,Git 会自动为我们创建一个.git/hooks目录,里面存放了所有的钩子。因为.git是隐藏目录,显示隐藏目录后就可以找到hooks这个目录。

在 VSCode 里一般默认把.git目录排除显示,所以打开项目目录时不会显示该目录,我们可以收到在 VSCode 显示.git目录:打开设置界面,搜索exclude找到图中的设置,将.git目录从排除列表中移除,即可在 VSCode 中显示.git目录。

现在我们找到了hooks,该如何使用呢?
所有默认的hooks都是以.sample为后缀,只需要移除.sample即可激活hooks

随便打开一个hooks文件,我们可以发现,实际是hooks就是一个个shell脚本。这些脚本会在特定的动作发生时被执行。示范的这些hooks都是shell脚本,实际上只要是文件名正确的可执行脚本都可以使用,如将pre-push内容改为python, Ruby等等脚本都可以。

如何使用一个 hooks

pre-commit这个hooks为例,来示范一下如何使用 Git hooks。

  • 打开.git/hooks/pre-commit.sample,这个hooks的大体功能是检查文件名是否包含非ASCII字符,如果包含,则无法执行commit操作,并提示用户修改文件名。

  • 删除pre-commit.sample的后缀

    ➜ mv .git/hooks/pre-commit.sample .git/hooks/pre-commit
  • 添加一个有汉字的文件名,如测试.md

    ➜  touch 测试.md
  • 将新文件提交

    ➜ git add 测试.md
    ➜ git commit -m "测试"
    Error: Attempt to add a non-ASCII file name.
    
    This can cause problems if you want to work with people on other platforms.
    
    To be portable it is advisable to rename the file.
    
    If you know what you are doing you can disable this check using:
    
    git config hooks.allownonascii true

    如果无法执行pre-commit可能未被赋予执行权限,修改一下权限即可:chmod +x .git/hooks/pre-commit

我们可以发现,在进行commit操作时被中断了,会提示用户修改文件名。其他的hooks用法类似,我们可以自定义在什么时候可以push,什么时候可以rebase等等。

hooks通常会被用来做提交代码前的一个检查,比如风格是否统一,编译是否通过等等。如果团队合作时,这样的检查最好能够与成员保持一致,但是hooks所在的.git目录是不会被Git自己版本管理的,换句话说,它不能推送到远端与成员共享。那么如何解决这个问题呢?

如何同步hooks文件

方案一:与源码放在一起

代码仓库中新建一个hooks目录,将该目录同步到远程。其他成员下载代码时也会下载hooks目录,通过脚本的方式将hooks目录覆盖本地的.git/hooks目录。

#!/bin/bash
cp -r ./hooks/ .git/hooks/
chmod +x -R .git/hooks
echo 'Hooks sync to remote success!'
exit 0

方案二:使用pre-commit框架

pre_commitpre-commit 同名的开源应用,使用pre-commit,代码仓库里只需要有一个配置文件,所有成员都可以根据配置文件,使用pre_commit生成统一的hooks

pre-commit随着发展,已经不单单只能用于git hooks的pre-commit阶段,而是能作用于所有git hooks的所有阶段,如上面说的prepare-commit-msg, commit-msg, post-commi等。

安装pre-commit

pip install pre-commit

在项目目录下,添加配置文件 .pre-commit-config.yaml

touch .pre-commit-config.yaml
  • 首先了解配置的格式

    • 顶层有一个参数名为 repos
    • repos 中每个元素为 repo ,代表一个代码库,一般是githubgitlab链接。在使用时会从对应地址下载,如果出现下载慢的情况,可以在gitee搜索是否有相关镜像。
    • 每个 repo 中有一个或多个 hook ,每个 hook 代表一个任务。
    • 每个任务里可理解为一个命令行指令,例如flake8/yapf/black
  • pre_commit官方提供了各种配置,我们可以根据需要选择一个合适的。比如我需要一个格式化C语言代码的配置,选择了mirrors-clang-format,还选了一个用来删除行尾空格的。

    repos:
    -   repo: https://github.com/pre-commit/pre-commit-hooks
        rev: v4.3.0
        hooks:
        - id: trailing-whitespace
    
    - repo: https://github.com/pre-commit/mirrors-clang-format
        rev: v14.0.6
        hooks:
        - id: clang-format
            types_or: [c]

    参数的含义可以参考pre-commit的文档。每个id对应的其实都是一个程序,为了保证都能正常运行,还需要安装这些程序。一般在仓库的README中都会有提示如何安装。

  • 根据配置文件安装hooks
    在项目根目录下运行:

    pre-commit install
  • 在执行git commit命令时将会自动检查。这个过程中,pre-commit会从仓库里下载代码,然后根据里面的配置执行相应的脚本。完成各种检查。

常用命令

# 手动对所有的文件执行 hooks,新增 hook 的时候可以执行,使得代码均符合规范。直接执行该指令则无需等到 pre-commit 阶段再触发 hooks
pre-commit run --all-files
# 执行特定 hooks
pre-commit run <hook_id>
# 将所有的hook更新到最新的版本/tag
pre-commit autoupdate
# 指定更新 repo
pre-commit autoupdate --repo https://github.com/pre-commit/mirrors-clang-format

参考资料

  1. C++ 项目中使用 Pre-commit 协助实现代码规范检查_清欢守护者的博客-CSDN 博客

  2. git push 之前自动编译验证 - 简书

  3. 使用 pre-commit 实现代码检查_清欢守护者的博客-CSDN 博客

  4. pre-commit

  5. Git 基本原理介绍 (32)——git hook 和 python_哔哩哔哩_bilibili