Git hook
Git钩子(git hook)本质是脚本,这些脚本对应于 Git 中触发的特殊的事件,比如 commit、push 等。当这些事件触发时,脚本就会被使用。这些脚本文件都存放在当前项目的 .git/hooks 目录下,如下图所示:
默认情况下,这些 Git hook脚本是不会被执行的。如果你想要这些脚本被在执行commit 或者 push 事件时触发,则需要去掉后面的 sample 后缀。
Git hook 脚本可以分成两类:本地钩子和服务端钩子。本地钩子只会影响本地仓库,常用的脚本有 pre-commit、commit-msg;服务端的钩子与本地钩子类似,只是它们存在于服务端仓库,比如 pre-receive、post-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-commit 的sample 后缀。然后使用 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 就是用来解决这个问题的库,一般用于前端。实现原理一般有两种:
- 覆盖
.git/hooks文件夹下所有的 hook - 通过
git config core.hooksPath ${path/to/hooks_dir}来设置脚本所在目录,这时会忽略.git/hooks目录下的脚本。这是在Git v2.9 推出的新特性
在Android中可以使用 gradle plugin 来实现类似的功能,