git钩子本质是可执行文件,c#程序需通过shell脚本调用或发布为自包含.exe;pre-commit须快速响应,post-receive需从stdin读取并指定--git-dir;跨平台钩子应统一用shell封装并区分路径与输出流。

Git钩子不是C#运行环境,得靠外部可执行程序启动
Git钩子本质是 shell 脚本或任意可执行文件,只要它在对应钩子目录下、有执行权限、且返回 0(成功)或非 0(拒绝操作),Git 就认。C# 编译出来的 dotnet 程序或 .exe 本身不能直接当钩子——因为 Git 不会调用 dotnet 去跑你写的 PreCommit.dll。
所以实际做法只有两种:
- 写一个 shell 脚本(
.sh或.bat),里面调用dotnet run或dotnet exec启动你的 C# 工具 - 发布为自包含(self-contained)的
.exe,然后让钩子文件直接exec它(Windows 下可直接用.exe;Linux/macOS 需确保有执行权限且#!/usr/bin/env ./MyHook这类 shebang 不生效,得靠 shell 中转)
常见错误:把 Program.cs 直接丢进 .git/hooks/pre-commit,然后发现 Git 完全没反应——因为它不是可执行文件,也不符合 shebang 规范。
pre-commit 钩子必须快,C# 启动开销容易超时
pre-commit 在用户敲下 git commit 后立刻执行,阻塞提交流程。如果 C# 程序要加载 .NET Runtime、JIT、再读配置、扫描文件……很容易卡住几秒,用户会觉得 Git “卡死” 或直接 Ctrl+C 中断,导致钩子被跳过。
实操建议:
- 优先用
dotnet publish -r win-x64 --self-contained true(或对应平台)生成原生.exe,避免依赖目标机器装 SDK/Runtime - 入口逻辑尽量早 return:比如先检查是否在允许跳过的分支(
git rev-parse --abbrev-ref HEAD)、是否有--no-verify参数 - 避免在钩子里做耗时操作:如完整编译、NuGet restore、网络请求。静态分析能用
dotnet format --dry-run或dotnet build -nologo -warnaserror就别自己重写 - 加简单日志输出到 stderr(
Console.Error.WriteLine),方便调试时看到卡在哪一步
post-receive 钩子运行在服务端,C# 程序需处理裸仓库路径和 stdin
不同于本地钩子,post-receive 运行在 Git 服务端(如 bare repo),没有工作区,所有信息通过 stdin 传入三元组:<oldref><newref><refname></refname></newref></oldref>。C# 程序必须自己读取 stdin 并解析,不能假设当前目录是项目根。
关键点:
- stdin 是唯一输入源,必须用
Console.In.ReadToEnd()或逐行读取,不能依赖Environment.CurrentDirectory - 裸仓库路径由 Git 通过环境变量提供:
GIT_DIR(通常是/path/to/repo.git),但工作区不存在,所以git checkout类操作无效;想查文件内容得用git show或git archive - C# 程序若需执行 Git 命令,必须显式指定
--git-dir和(必要时)--work-tree,例如:git --git-dir=/var/git/myrepo.git log -1 --format=%H HEAD - 权限问题:服务端运行用户(如
git用户)可能无权访问你的 C# 程序路径或写日志文件,建议用绝对路径 +/tmp或/var/log下预授权目录
跨平台钩子脚本里别硬编码 .exe 或 dotnet 路径
一个 pre-commit 钩子要在 Windows 开发者、macOS CI、Linux 服务器上都跑通,就不能写死 ./MyHook.exe 或 dotnet ./MyHook.dll。
推荐方案:
- 钩子文件统一用 shell(
#!/bin/sh),开头检测平台:uname -s判断是Darwin/Linux/MSYS*,再决定调用./MyHook还是./MyHook.exe - 不依赖全局
dotnet:self-contained 发布后,直接执行二进制,不碰dotnet命令。否则 CI 里没装 SDK 就直接失败 - 路径用
$(dirname "$0")/..相对定位,别用pwd——钩子执行时工作目录是 Git 根,但脚本位置固定在.git/hooks/ - 加兜底错误处理:比如
if ! command -v ./MyHook >/dev/null 2>&1; then echo "MyHook missing" >&2; exit 1; fi
最容易被忽略的是 stdin/stderr 重定向行为:Git 钩子默认把 stdout 接管用于显示提示,但 C# 的 Console.WriteLine 输出会被 Git 当作提交说明的一部分(尤其在 pre-commit),所以诊断信息一律走 Console.Error.WriteLine,别混用。










