一文了解 Git 钩子

920 阅读4分钟

Git hook

Git钩子(git hook)本质是脚本,这些脚本对应于 Git 中触发的特殊的事件,比如 commit、push 等。当这些事件触发时,脚本就会被使用。这些脚本文件都存放在当前项目的 .git/hooks 目录下,如下图所示:

image.png

默认情况下,这些 Git hook脚本是不会被执行的。如果你想要这些脚本被在执行commit 或者 push 事件时触发,则需要去掉后面的 sample 后缀。

Git hook 脚本可以分成两类:本地钩子和服务端钩子。本地钩子只会影响本地仓库,常用的脚本有 pre-commitcommit-msg;服务端的钩子与本地钩子类似,只是它们存在于服务端仓库,比如 pre-receivepost-receive。所有这些脚本对应的事件如下:

钩子名称执行时机用途
pre-commit在执行提交(commit)之前运行执行代码风格检查、静态代码分析、单元测试等,确保提交代码质量
prepare-commit-msg在提交(commit)消息被编辑之前运行修改或扩展提交消息,如添加自动化生成信息、验证提交消息格式
commit-msg在提交(commit)消息被创建后,但提交动作尚未完成时运行验证提交消息的格式、内容等是否符合规范
post-commit在提交(commit)完成后运行发送通知、更新文档、执行特定后续处理等
pre-rebase在执行变基(rebase)操作之前运行执行预检查,确保变基操作不会产生冲突或导致代码质量下降
post-checkout在检出(checkout)完成后运行执行与工作目录切换相关的操作,如更新依赖、清理临时文件等
post-merge在合并(merge)完成后运行执行与合并操作相关的操作,如重新构建项目、更新子模块等
pre-push在执行推送(push)之前运行执行预检查,如运行测试、检查代码质量等
pre-receive(服务端钩子)在远程仓库接收到推送(push)之前运行执行服务端预检查,验证提交的代码是否符合特定规范
update(服务端钩子)在推送(push)到远程仓库但尚未完成更新时运行执行与分支更新相关的操作,如验证提交代码是否符合规范、是否有权限更新等
post-receive(服务端钩子)在远程仓库接收到推送(push)并完成更新后运行执行服务端后续处理,如自动化部署、更新文档等

自定义 Git hook

我们以 pre-commit 威力,介绍如何编写一个代码检查的 git hook。首先我们先去掉 pre-commitsample 后缀。然后使用 python 创建一个检查代码的脚本,脚本代码示例如下:

import subprocess
import sys

def get_changed_class_paths():
    # 运行 git diff 命令
    result = subprocess.run(['git', 'diff', '--name-only'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    if result.returncode!= 0:
        print("git diff 命令执行失败:")
        print(result.stderr)
        return []
    # 获取输出内容
    diff_output = result.stdout.splitlines()
    # 存储发生改变的类的路径
    class_paths = []
    for path in diff_output:
        if path.endswith('.kt'):
            class_paths.append(path)
    return class_paths


if __name__ == "__main__":
    changed_classes = get_changed_class_paths()
    print("Changed class paths:")
    for class_path in changed_classes:
        print(class_path)
    # 使用 detekt 做代码检查
    try:
        subprocess.check_call(['java', '-jar', '/detekt/detekt-cli-1.20.0-all.jar',
                                    '-cp', '/detekt/jar/android.jar', '--plugins',
                                        '/detekt/detekt-formatting-1.20.0.jar , ' + '/detekt/detekt-custom-rule-1.0-SNAPSHOT.jar',
                                    '-c', '/detekt/detekt-config.yml', '-i', ','.join(class_path), '-r', 'txt:reports/checkstyle.txt'])
        # 返回 0 表示执行正常
        sys.exit(0)
    except Exception as e:
        print("Exception occurred while checking Kotlin files: ", e)
        # 返回大于 0 的值表示执行失败
        sys.exit(1)

然后我们在 pre-commit 脚本中使用这一段代码就可以了,代码示例如下:

#!/bin/sh

python3 ./script/check_code.py
# $? 用来获取上一个脚本执行的结果,当结果大于0表示失败,其他表示成功
exit "$?"

注意 check_code.py 文件不能放在 .git 目录下,不然会找不到脚本所在路径

husky

由于 .git/hooks 目录下的脚本不会被提交到远程,因此一般情况下,需要我们在本地手动修改脚本文件。husky 就是用来解决这个问题的库,一般用于前端。实现原理一般有两种:

  1. 覆盖 .git/hooks 文件夹下所有的 hook
  2. 通过 git config core.hooksPath ${path/to/hooks_dir} 来设置脚本所在目录,这时会忽略.git/hooks 目录下的脚本。这是在Git v2.9 推出的新特性

在Android中可以使用 gradle plugin 来实现类似的功能,

参考