Git commit-msg hook 必须用 shell 脚本包装 Go 二进制,因 Git 仅识别 POSIX shell 入口并正确传递参数;Go 工具需配 .commitlintrc.json,支持 Angular 解析且 CI 中须用 git log 显式提取当前 commit message。

Commit-msg hook 为什么必须用 shell 脚本而不是 Go 程序直接挂载
Git 的 commit-msg hook 只接受可执行文件,且要求入口是 POSIX shell 兼容的(即第一行是 #!/bin/sh 或类似解释器声明)。Go 编译出的二进制虽然可执行,但 Git 不会传入预期参数($1 是临时 commit message 文件路径),且缺乏对标准输入/输出流的隐式处理——容易导致 hook 静默失败或阻塞提交。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 把 Go 工具编译成静态二进制(
CGO_ENABLED=0 go build -o commitlint),放在项目根目录.githooks/commit-msg - 在
.git/hooks/commit-msg中只写一层 shell 包装:#!/bin/sh ./.githooks/commitlint "$1"
- 别忘了
chmod +x .git/hooks/commit-msg,否则 hook 不触发
commitlint 配置文件该放哪、怎么让 Go 工具识别
Go 实现的 commitlint 默认不读取 JavaScript 生态的 .commitlintrc.js,它只认自己支持的格式。常见错误是照搬前端配置,结果工具完全无视规则,还报 “no config found”。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用 JSON 格式写配置,命名为
.commitlintrc.json,放在项目根目录(和.git同级) - 必须包含
rules字段,例如:{ "rules": { "subject-min-length": [2, "always", 10], "type-enum": [2, "always", ["feat", "fix", "chore", "docs"]] } } - Go 版本对
extends和 JS 导出语法零支持,别尝试复用conventional-changelog的配置链
Go 实现的 commitlint 如何校验 Angular 风格提交信息
Angular 规范要求形如 feat(auth): add token refresh logic,但很多 Go 实现默认只做基础正则匹配,不解析 scope 括号、不校验冒号后空格、也不区分 type 大小写——导致 Feat(auth): … 或 feat(auth):…(无空格)被误放行。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 确保你用的 Go 工具支持
--format angular或内置 Angular 解析器(比如github.com/4ydx/commitlint-go) - 在
.commitlintrc.json中显式启用scope-case和subject-case规则 - 测试时用真实 Git 提交触发:
git commit -m "feat(api): return 401 on expired token",别只靠 CLI 命令行跑验证
CI 流程里要不要重复运行 commitlint
本地 hook 可被绕过(git commit --no-verify),所以 CI 必须二次校验。但直接在 CI 里调 commitlint 会出问题:GitHub Actions / GitLab CI 的 $GIT_COMMIT 或 HEAD 往往不是本次 PR 的最新 commit,而是 merge base 或 detached HEAD。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- CI 脚本中用
git log -1 --pretty=%B $CI_COMMIT_SHA | commitlint显式提取当前 commit message - 别依赖
git rev-list --reverse $BASE..$HEAD批量检查——单次 PR 只需验最后一个 commit,多条反而容易漏掉合并提交的 message - 如果用 GHA,记得在
steps里加fetch-depth: 0,否则git log拿不到完整历史
最麻烦的其实是 Windows 开发者:CRLF 换行会让 Go 工具把 \r\n 当作 subject 结尾字符,导致 subject-max-length 校验失败。统一用 LF(git config --global core.autocrlf input)比在代码里各种 trim 更可靠。










