客户端钩子
在本地 Git 仓库操作中执行的钩子脚本,允许开发者在特定的 Git 操作发生时插入自定义逻辑。客户端钩子通常在开发者本地执行,作用于个人开发环境中,用于处理一些如代码格式化、验证、提交前检查等任务,客户端的钩子种类优先,详见:.git/hooks。
类别 |
钩子名称 |
触发时机 |
用途 |
提交 |
commit-msg |
git commit 提交信息生成后 |
验证 commit msg |
prepare-commit-msg |
git commit 提交信息生成前 |
修改或生成提交信息 |
|
pre-commit |
git commit 执行前 |
执行代码检查、格式化等 |
|
推送 |
pre-push |
git push 执行前 |
推送前检查 |
pre-receive |
常用于远程仓库接收推送时 |
服务端验证推送的内容 |
|
合并 |
pre-merge-commit |
执行合并提交之前 |
合并操作前执行检查或自动化任务 |
pre-rebase |
git rebase 之前 |
变基操作前执行检查 |
|
补丁 |
applypatch-msg |
应用补丁时 |
检查补丁的提交信息 |
pre-applypatch |
应用补丁前 |
检查补丁的内容 |
|
post-update |
远程仓库更新后,常用于服务器端 |
触发远程仓库更新后的任务 |
|
其他 |
sendemail-validate |
git send-email 发送邮件时 |
检查和验证发送邮件的内容。 |
push-to-checkout |
远程仓库更新时 |
远程仓库更新时触发的操作 |
|
update |
git update-server-info 执行时 |
更新仓库的内部信息或触发通知 |
使用事例
git commit msg 校验
#!/bin/bash commit_msg=$(cat "$1") commit_regex="^(feat|fix|refactor|docs|style|test|chore|perf|ci|build|revert|wip) \[JIRA-[0-9]+\] : .+" if ! echo "$commit_msg" | grep -qE "$commit_regex"; then echo "ERROR: Invaild commit message format, example:" >&2 echo "refactor [JIRA-1234] : commit message" echo "" echo "feat: 引入新功能" echo "fix: 修复bug或错误" echo "refactor: 对代码进行重构,但不改变外部行为" echo "docs: 更新文档(如 README、API 文档等)" echo "style: 修改代码格式(如缩进、空格、分号等), 不改变功能" echo "test: 添加或修改测试代码" echo "chore: 更新构建工具、依赖或其他不影响应用功能的任务" echo "perf: 提高性能的修改" echo "build: 修改构建系统或外部依赖" echo "revert: 撤销某个提交的提交" echo "ci: 修改 CI 配置或脚本" echo "wip: 标记为工作进行中的提交" exit 1 else echo "INFO: $commit_msg" fi exit 0
# ubuntu @ ubuntu in ~/Code/leetcode on git:master x [13:17:38] $ git status On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: commit_msg.txt # ubuntu @ ubuntu in ~/Code/leetcode on git:master x [13:17:41] $ git commit -m "commit invaild format msg" ERROR: Invaild commit message format, example: refactor [JIRA-1234] : commit message feat: 引入新功能 fix: 修复bug或错误 refactor: 对代码进行重构,但不改变外部行为 docs: 更新文档(如 README、API 文档等) style: 修改代码格式(如缩进、空格、分号等), 不改变功能 test: 添加或修改测试代码 chore: 更新构建工具、依赖或其他不影响应用功能的任务 perf: 提高性能的修改 build: 修改构建系统或外部依赖 revert: 撤销某个提交的提交 ci: 修改 CI 配置或脚本 wip: 标记为工作进行中的提交 # ubuntu @ ubuntu in ~/Code/leetcode on git:master x [13:17:46] C:1 $ git commit -m "feat [JIRA-1234] : add new file" INFO: feat [JIRA-1234] : add new file [master d1eff26] feat [JIRA-1234] : add new file 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 commit_msg.txt # ubuntu @ ubuntu in ~/Code/leetcode on git:master o [13:18:14] $
git push 编译校验
#!/bin/bash echo "Running pre-push hook... Compiling the code before push..." if ! ( cmake -S . -B build && cmake --build build ) ; then echo "ERROR: Compilation failed! Push aborted." exit 1 else echo "INFO: Compilation successful. Proceeding with push." fi
# 无法通过编译push失败 # ubuntu @ ubuntu in ~/Code/leetcode on git:main o [14:35:16] $ git push INFO: Running pre-push hook... Compiling the code before push... -- install: /home/ubuntu/Code/leetcode/app -- Using GNU compiler -- Using -std=c++20 -- GTest version: 1.14.0 -- Configuring done (0.0s) -- Generating done (0.0s) -- Build files have been written to: /home/ubuntu/Code/leetcode/build [ 50%] Building CXX object src/CMakeFiles/x_exec.dir/x.cc.o /home/ubuntu/Code/leetcode/src/x.cc: In function ‘int main()’: /home/ubuntu/Code/leetcode/src/x.cc:7:11: error: expected ‘;’ before ‘}’ token 7 | return 0 | ^ | ; 8 | } | ~ gmake[2]: *** [src/CMakeFiles/x_exec.dir/build.make:76: src/CMakeFiles/x_exec.dir/x.cc.o] Error 1 gmake[1]: *** [CMakeFiles/Makefile2:98: src/CMakeFiles/x_exec.dir/all] Error 2 gmake: *** [Makefile:101: all] Error 2 ERROR: Compilation failed! Push aborted. error: failed to push some refs to 'github.com:croquettess/leetcode.git' # 仅可通过编译时push成功 # ubuntu @ ubuntu in ~/Code/leetcode on git:main o [14:19:46] $ git push INFO: Running pre-push hook... Compiling the code before push... -- install: /home/ubuntu/Code/leetcode/app -- Using GNU compiler -- Using -std=c++20 -- GTest version: 1.14.0 -- Configuring done (0.1s) -- Generating done (0.0s) -- Build files have been written to: /home/ubuntu/Code/leetcode/build [ 50%] Building CXX object src/CMakeFiles/x_exec.dir/x.cc.o [100%] Linking CXX executable x_exec [100%] Built target x_exec INFO: Compilation successful. Proceeding with push. Enumerating objects: 11, done. Counting objects: 100% (11/11), done. Delta compression using up to 4 threads Compressing objects: 100% (8/8), done. Writing objects: 100% (8/8), 742 bytes | 742.00 KiB/s, done. Total 8 (delta 4), reused 0 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (4/4), completed with 2 local objects. To github.com:croquettess/leetcode.git 0920c24..d38da12 main -> main
使用限制
- 无法共享:钩子仅在本地有效,无法自动同步到团队成员,需要手动配置。
- 跨平台兼容性:钩子脚本通常是 Shell 脚本,在不同操作系统上可能无法兼容。
- 性能问题:一些钩子执行复杂操作可能影响 Git 操作速度。
- 容易绕过:开发者可以禁用本地钩子,导致一些强制操作被绕过。
- 无法控制全局设置:钩子仅对单个仓库有效,无法在多个仓库中共享,需要手动配置或使用统一管理工具。
服务端钩子
Git 仓库在特定事件发生时,通过 HTTP POST 请求通知外部服务器。,例如 CI/CD、代码检查等。当 Git 仓库中的代码发生变化(如提交、推送、拉取请求等),Webhook 会自动触发并将相关信息发送到指定的 URL。分发 hook
hook 类型:github->repository->Settings->webhooks->Add webhook->Let me select individual events.
使用事例
jenkins 自动集成
通过 webhook 配置,向 main 分支提交代码后,jenkins 自动集成
- jenkins:
- 新增/选择一个任务->配置
- 源码管理
- Git:
Repository URL: 项目地址
Branches to build: */main
Triggers: 选择 GitHub hook trigger for GITScm pollin - Triggers:
选择 GitHub hook trigger for GITScm polling - Build Steps
执行 shell:添加构建、编译命令g
- github 添加钩子
- github->repository->Settings->webhooks->Add webhook
- Payload URL: http://$jenkins_endpoint//github-webhook/
- Content type: application/json
- Which events would you like to tragger this webhook?: Just the push event.
- Add webhook
- 更新提交到 main 分支
# ubuntu @ ubuntu in ~/Code/leetcode on git:main x [16:33:02] $ git add . # ubuntu @ ubuntu in ~/Code/leetcode on git:main x [16:33:06] $ git commit -m "feat [JIRA-0000] : jenkins auto build" INFO: feat [JIRA-0000] : jenkins auto build [main 112b6d5] feat [JIRA-0000] : jenkins auto build 1 file changed, 1 insertion(+), 1 deletion(-) # ubuntu @ ubuntu in ~/Code/leetcode on git:main o [16:33:10] $ git push INFO: Running pre-push hook... Compiling the code before push... -- install: /home/ubuntu/Code/leetcode/app -- Using GNU compiler -- Using -std=c++20 -- GTest version: 1.14.0 -- Configuring done (0.0s) -- Generating done (0.0s) -- Build files have been written to: /home/ubuntu/Code/leetcode/build [ 50%] Building CXX object src/CMakeFiles/x_exec.dir/x.cc.o [100%] Linking CXX executable x_exec [100%] Built target x_exec INFO: Compilation successful. Proceeding with push. Enumerating objects: 15, done. Counting objects: 100% (15/15), done. Delta compression using up to 4 threads Compressing objects: 100% (12/12), done. Writing objects: 100% (12/12), 1.14 KiB | 1.14 MiB/s, done. Total 12 (delta 5), reused 0 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (5/5), completed with 2 local objects. To github.com:croquettess/leetcode.git d38da12..112b6d5 main -> main # ubuntu @ ubuntu in ~/Code/leetcode on git:main o [16:33:17] $
- 查看 jenkins 控制台
- 运行
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- /var/lib/jenkins/workspace/webhook/build/src » ./x_exec ubuntu@VM-16-13-ubuntu jenkins auto build new ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- /var/lib/jenkins/workspace/webhook/build/src » ubuntu@VM-16-13-ubuntu
自定义 webhook
需要编写可以与 git 服务器建立连接的 http 服务器,处理 github 的 webhook
- 编写 http 服务器
import logging from flask import Flask, request, jsonify import hmac import hashlib import json app = Flask(__name__) logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger() # 配置 GitHub Secret 密钥 GITHUB_SECRET = "webhook" # 用于验证签名 def verify_signature(payload, signature): mac = hmac.new(GITHUB_SECRET.encode(), msg=payload, digestmod=hashlib.sha256) return hmac.compare_digest("sha256=" + mac.hexdigest(), signature) # 处理 webhook @app.route('/my-webhook', methods=['POST']) def github_webhook(): if request.method == 'POST': logger.info("Received POST request") signature = request.headers.get('X-Hub-Signature-256') payload = request.data # 验证签名 if not verify_signature(payload, signature): logger.error("Invalid signature") return "Invalid signature", 400 try: data = json.loads(payload) logger.info(f"Received GitHub payload: {json.dumps(data, indent=2)}") except json.JSONDecodeError: logger.error("Invalid JSON in payload") return "Invalid JSON", 400 # 处理 push 事件 if data.get('action') == 'push': logger.info("A push event occurred") logger.info(f"Push to branch: {data['ref']}") return jsonify({"status": "success"}), 200 else: return "Method Not Allowed", 405 if __name__ == '__main__': app.run(host='0.0.0.0', port=10000, debug=True)
- 启动 nohup python3 webhook.py &
- github 添加名称为 my-webhook 的钩子,步骤详见:服务端钩子/jenkins 自动集成/github 添加钩子
- 修改项目代码后 push 到 git 服务器上,查看 webhook 服务器日志
~ » cat nohup.out ubuntu@VM-16-13-ubuntu * Serving Flask app 'webhook' * Debug mode: on 2024-12-28 21:23:53,248 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:10000 * Running on http://10.0.16.13:10000 2024-12-28 21:23:53,249 - INFO - Press CTRL+C to quit 2024-12-28 21:23:53,250 - INFO - * Restarting with stat 2024-12-28 21:23:53,517 - WARNING - * Debugger is active! 2024-12-28 21:23:53,520 - INFO - * Debugger PIN: 108-869-862 2024-12-28 21:35:52,506 - INFO - Received POST request 2024-12-28 21:35:52,509 - INFO - Received GitHub payload: { "ref": "refs/heads/main", "before": "876a741cfd6ea55289187e80b82e61f8105632aa", "after": "63fce6d795246d80a3040984b7f3ee29a3e2b0bc", ...... } 2024-12-28 21:35:52,510 - INFO - 140.82.115.73 - - [28/Dec/2024 21:35:52] "POST /my-webhook HTTP/1.1" 200 -
使用限制
- 无法共享:每个 Webhook 配置独立,无法在多个项目间共享,需要手动配置。
- 速率限制:高频触发的事件可能被限流或延迟发送。
- 请求大小限制:单次请求的 Payload 数据大小有限(如 GitHub 最大 25 MB)。
- 重试机制有限:服务端失败返回后,Webhook 只会重试有限次数,重试用尽后事件可能丢失。
- 事件支持范围有限:仅支持特定事件类型,复杂触发逻辑需自行实现。