1. 这个工具要解决什么问题
1.1 先聊点糟心事:你的 Git 提交记录是不是也长这样
我最近在复盘一个半年期的项目时,被 git log 折磨得不轻。翻出来的提交信息大概有这些类型:“改一下”“update”“asdf”“fix bug”“代码微调”,还有一些直接是空的。说实话,看到这种记录,我连 blame 的欲望都没有——就算定位到某一行是哪个 commit 改的,也完全猜不到当时为什么要这么改。
Git 提交信息这件事,看起来不起眼,实际上直接影响一个项目的可维护性。它承担着三个任务:第一,给未来的自己当“记忆索引”;第二,给 Code Review 的人提供上下文;第三,为自动生成 CHANGELOG、语义化版本号、自动关闭 Issue 这些能力提供数据基础。提交信息一旦乱掉,后面做版本管理、问题追溯都是两眼一抹黑。
很多团队不是不想规范提交信息,而是“规范成本”太高。靠人自觉不靠谱,开会强调只管用三天;靠 Code Review 人工检查,reviewer 自己都经常忘;真正有效的办法是在工具层面做强制校验,让不规范的提交根本进不了仓库。gitru 就是朝这个方向走的一个工具。
gitru 是用 Rust 写的零依赖 Git 提交信息校验工具。它解决的问题非常聚焦:在你提交代码的时候,自动检查 commit message 符不符合团队约定。我把玩了一段时间,也接进了自己的工作流,今天这篇文章就聊聊这个工具的设计思路、使用方式和一些实际落地时容易踩的坑。
1.2 gitru 是干什么的,适合什么人用
先给一句话定位:gitru 是一个“只做提交信息格式校验”的命令行工具,不接管 Git 命令,不push,不自动改代码。它做的事情可以理解为给 commit message 加一道闸门:格式对了放行,格式错了报错并提示你应该怎么写。
这类工具在实际工作里相当有存在感。如果你是个人开发者,想让自己的项目记录干净、方便以后回溯,gitru 可以在本地帮你养成习惯;如果你是团队负责人或者 CI 维护者,gitru 可以部署在 Git Hook 或流水线里,作为合并代码前的一道自动检查;如果你是开源项目维护者,也可以用它给外部贡献者设立一个明确的提交门槛。
很多刚接触的人会问,这玩意儿和 commitlint 有什么区别?区别主要在运行环境。commitlint 是 Node.js 生态的,安装后下面挂着一堆依赖包;gitru 则把自己编译成了一个二进制文件,不需要额外装解释器。对一个只想“校验提交信息”的简单需求来说,gitru 的思路更干净。
我在实际使用中最喜欢它的地方是:够轻、够快、够安静。不会在你每次 git commit 的时候拖几秒钟,也不会突然报一堆依赖版本冲突。它就是一个放在 PATH 里的小程序,你写完提交信息,它帮你把关。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 为什么用 Rust 写,又为什么要做到零依赖
2.1 从 commitlint 到 gitru:选型不是跟风
先说一个很多团队的普遍痛点。现在大家在 Git 提交校验上用得最多的工具是 commitlint,但它依赖 Node 环境。你去 npm 仓库装一个 commitlint 相关的包,会看到它背后的依赖树可能比你项目本身的依赖还多。在本地机器上这还能忍,放到 CI 流水线里就比较烦了:镜像要额外装 Node,node_modules 要重新拉一遍,版本稍微不一致还会出现行为差异。
另一个常见工具 gitlint 是用 Python 写的,也存在类似的解释器依赖问题。Python 版本、pip 源、虚拟环境……这些跟“提交信息校验”本意毫无关系的事,反而成了使用成本的大头。
gitru 用 Rust 写,解决的正是这种“为一个简单诉求背上复杂环境”的尴尬。Rust 可以静态编译,编完就是一个独立的可执行文件,不仅不需要 Node、Python 这类运行时,连运行时库都基本不带。拿到哪个平台都能跑,行为一致,升级就是换一个文件。
有人可能觉得这是“为了 Rust 而 Rust”。但站在工程角度,这个选型相当合理。提交校验是一个极其高频、低延迟敏感、又希望稳定可分发的小工具,Rust 在这种场景下的优势恰好全中:编译产物单文件、启动快、内存安全、错误处理机制完善。它不是用锤子去砸钉子,而是这个钉子刚好适合 Rust 这把锤子。
2.2 零依赖到底意味着什么
“零依赖”这四个字,普通用户可能没太多感觉,但做过发布管理的人会非常敏感。一个工具依赖越少,意味着它的攻击面越小、故障点越少、生命周期越好维护。
对一个命令行校验工具来说,零依赖的直接好处有三个。第一,供应链风险极低。npm 生态里每年都会出现依赖包被恶意篡改的事件,一个包底下挂着几百个依赖,其中某个被攻破,整个 CI 就危险了。gitru 不引第三方库,编译产物里没有一堆“来源不明”的传递依赖,在安全审查上会轻松很多。第二,安装卸载干净。下载一个二进制文件放好就能用,不污染系统,不往某个全局目录里塞一整套包。第三,跨环境可预测。不用担心 Node 版本升级、Python 包冲突导致工具突然罢工。
体积方面,Rust 编译出来的二进制通常会比脚本实现大一些,因为它把运行逻辑都打包进去了。但实际使用中,这类工具一般也就几百 KB 到几 MB 的体量,跟现代前端项目动辄几百 MB 的 node_modules 相比,这点体积根本不算负担。换来的是一个无需安装任何解释器就能直接运行的文件,这笔账是划算的。
我在本地实测过 gitru 校验一条提交信息的速度,体感上就是“瞬间完成”。Git Hook 本来就在执行链路上,如果工具启动要一两秒,开发者会明显烦躁;但如果工具能在几十毫秒内结束,它几乎不会出现在你的感知里,只有在你提交写错的时候才跳出来提醒。
2.3 性能之外的隐性优势
Rust 里有一个非常实用的语言特性:枚举和模式匹配。Git 提交信息校验本质上就是“读一段文本,按规则做结构解析”,用模式匹配来表达这类逻辑非常顺手。比如区分“feat: 新增用户登录”里的 type 和 subject,或者识别正文里 “BREAKING CHANGE” 标记,Rust 处理起来干净明了。
再有一点,Rust 的静态类型系统能在编译期挡住很多低级错误。写校验工具的开发者最怕的不是逻辑复杂,而是边界情况处理不到位:空消息、超长行、多行文本、特殊字符、非 UTF-8 编码……这些都会在正式环境里冒出来。Rust 编译器会强制你显式处理各种情况,错误处理用 Result 类型兜底,做这类工具实际上非常合适。
我不是说只有 Rust 能写这类工具,而是 Rust 的工程特性天然适合“高可靠、低依赖、易分发”的命令行工具。gitru 选择了 Rust,等于从一开始就把产品体验定位在了“下载即用、安静高效”上。对最终用户来说,你不用懂 Rust 也能享受这些优点——就像你不需要懂汽车的发动机原理,也能感受到“这车启动真快,还没什么噪音”。
3. 提交规则怎么配:从约定到可执行的校验逻辑
3.1 首先要定一套提交规范
在用 gitru 之前,团队得先回答一个问题:什么样的提交信息是“规范”的?如果连这个都没想清楚,任何工具都只是摆设。
业界最通用的方案是 Conventional Commits(约定式提交)。它的核心结构是:
code复制<type>(<scope>): <subject>
<空行>
<body>
<空行>
<footer>
其中 <type> 是提交类型,常见的有 feat(新功能)、fix(修复bug)、docs(文档)、style(代码风格)、refactor(重构)、perf(性能优化)、test(测试)、build(构建)、ci(持续集成)、chore(杂务)等。<scope> 是可选的模块名称,比如 feat(auth): 增加登录失效处理 表示这次改动落在 auth 模块。<subject> 是简要描述,<body> 是详细说明,<footer> 一般放破坏性变更标记或关联的 Issue 号。
很多团队会在此基础上做裁剪。比如有的团队不喜欢带 scope,因为改动跨多个模块时很难定归属;有的团队对 type 有严格的白名单,不认识的类型一律拒绝;有的团队则要求 footer 里必须写清楚是否属于 breaking change。这些差异没有对错,但必须明确写下来。
有意思的是,关于提交规范的最大误区不是“没有规范”,而是“规范写得像一篇作文”。我见过一些团队维护了十几页的提交规范文档,开发人员每次提交前都要打开看一遍,效果反而很差。真正好用的规范应该是“短到能印在一张卡片上”,剩下的校验交给工具去执行。
3.2 gitru 的配置方式和规则示例
gitru 这类工具通常会把校验规则放在项目根目录下的一个配置文件里,这样所有开发者共用一套规则,新增成员也不用靠“传帮带”去学习。配置格式一般用 TOML 或 YAML 编写,gitru 偏好 TOML,因为 Rust 社区对 TOML 的支持非常友好,阅读上也直观。
我这里演示一份常见的配置思想:
toml复制# 头部规则
[header]
enabled = true
types = ["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert"]
type_case = "lowercase"
scope_required = false
subject_required = true
max_length = 72
# 正文规则
[body]
required = false
min_length = 0
max_line_length = 72
# 页脚规则
[footer]
required = false
pattern = "^(BREAKING CHANGE: .+|Closes #[0-9]+)$"
每个字段的含义需要结合提交场景去理解。header.enabled 表示是否启用第一行校验,types 定义了允许的提交类型,scope_required 表示是否强制要求带模块名,subject_required 表示是否必须有简要描述。max_length 设置为 72 是有讲究的——终端窗口默认宽度是 80 列,留出 8 列的余量是为了避免折行;而且很多 Git 工具(包括 GitHub 的页面渲染)在显示 commit 标题时都会在 72 字符附近做截断。
body.max_line_length 也很重要。很多人在写多行提交信息时,会写出几百个字符不换行的一段“小作文”,在 git log 里看起来极其痛苦。限制单行长度可以强制提交者合理断行,保证可读性。footer.pattern 则用正则表达式约定了页脚的写法,比如必须写 BREAKING CHANGE 说明或以 Closes #123 的形式关联 Issue。
你不需要一次性把所有规则都打开。我的建议是:先把 header 下的 type 和长度限制配上,跑通流程后,再根据团队的实际情况逐步加上 body、footer 的正则约束。开门见山就封死所有规则,很容易激起团队成员的抵触情绪。
3.3 想自定义规则,从正则和关键词入手
如果你的团队用的不是 Conventional Commits,也不要紧。gitru 这类校验工具的底层能力其实是“正则匹配 + 长度检查 + 关键词检查”,你完全可以按自己的需求重组这些原子能力。
比如团队内部有一套内部项目编号体系,要求每个 commit 必须带上工单号,格式为 TICKET-1234。你可以配置 error 提示,当一个提交信息匹配不到 TICKET-[0-9]+ 这个正则时就报错。再比如团队禁止出现“临时改的”“先提交再说”这类词语,可以通过关键字检查直接拒绝。
还有个特别实用的配置角度:大小写敏感性。很多工具默认允许 Feat: xxx 也通过,但如果团队希望统一要求小写,就配成 type_case = "lowercase"。我见过不少团队在 Git 平台上出现明明内容相同但大小写风格不一致的提交记录,像 “FEAT: add xxx” 和 “feat: add xxx” 混在一起,确实不美观。
配置规则时有个重要的“为什么”:规则的意义不是制造麻烦,而是降低沟通成本。所以配置的每一项都应该能回答“这条规则想防止什么糟糕情况”。如果一条规则连这个都回答不了,它就不应该出现在配置里。我见过有人把 subject 的最短长度限制成 50 个字符,理由是“描述详细一点”,结果是开发人员为了凑字数写了一堆废话。这就属于本末倒置。
4. 实操:把 gitru 接入你的 Git 工作流
4.1 安装和快速验证
gitru 的使用前提是你已经有了 Rust 工具链,或者能够从发布页拿到编译好的二进制文件。最省事的方式是在 GitHub Releases 页面下载对应平台的压缩包,解压后把可执行文件放到 PATH 目录下。如果你本身是 Rust 开发者,也可以用 Cargo 直接安装:
bash复制cargo install gitru
安装完成后,先用帮助命令确认版本和子命令:
bash复制gitru --version
gitru --help
一般这类工具会提供两个入口:一个接收提交信息字符串做校验,另一个接收提交信息文件做校验。Git 在触发 commit-msg 钩子时会把提交信息临时文件的路径传给脚本,所以校验文件内容这个入口才是接 Git Hook 时最常用的。
我自己测试时习惯先拿字符串入口验证配置:
bash复制gitru check "feat: 增加登录页面"
如果输出了一个明显的错误提示,大概率是配置文件里 types 定义的问题;如果静默通过,就说明这条信息符合你的规则。这种“先手动试两条 ”的方式比直接改提交脚本安全得多,可以避免把规则配错了还不知道。
4.2 接入 Git Hook:让每次 commit 都被自动检查
要让 gitru 在本地自动生效,得靠 Git 的 commit-msg 钩子。Git 在每次 git commit 时都会调用 .git/hooks/commit-msg 这个脚本,并把准备写入的提交信息文件路径作为第一个参数传给它。
先手动创建一个钩子脚本测试:
bash复制#!/bin/sh
gitru check --file "$1"
将上面的内容保存到 .git/hooks/commit-msg,然后加上可执行权限:
bash复制chmod +x .git/hooks/commit-msg
这时候你可以试着提交一条不规范的 commit message,比如 git commit -m "fix stuff",如果规则里 types 没有定义 fix 之外的修饰形式,并且 subject 不符合预期,gitru 会直接返回非零退出码,Git 会中止这次提交。把 message 改成 fix: 修复登录页重定向异常,提交就会正常完成。
这里要解释一个细节:为什么脚本里要传 "$1" 而不是直接用字符串?因为 commit-msg 钩子拿到的不是纯字符串,而是一个文件路径。gitru 需要读取这个文件的内容进行校验,所以必须用 --file 这类参数指向它。很多刚接触 Git Hook 的人在这里栽过跟头,拿 git commit -m 的字符串直接传进去,结果发现长 message 和多行 message 完全匹配不上。
手动创建 .git/hooks/commit-msg 有一个问题:这个文件不会被 Git 跟踪,团队成员各自 clone 仓库后不会自动拥有这份钩子。解决方法是把钩子脚本放到仓库内的一个目录里统一管理,比如 .githooks/commit-msg,然后让 Git 从这个目录读取钩子:
bash复制git config core.hooksPath .githooks
把 core.hooksPath 配置纳入团队的初始化文档后,新成员拉下仓库执行一次命令就能拥有同样的本地校验。要注意的是,这种方法能让钩子脚本跟着仓库走,但前提是执行 git config 配置一次,否则新成员首次提交时 Git 会使用默认的 .git/hooks 目录,找不到你的脚本就不会校验。
4.3 在 CI 流水线里做最后一道防线
本地 Git Hook 有一个天然局限:它只拦得住“不会绕过的人”。真要绕过,git commit --no-verify 就能忽略所有钩子。所以构建一个规范的提交环境,CI 校验才是最终保证。
用一个 GitLab CI 的例子说明:
yaml复制validate-commit:
stage: test
script:
- git log -1 --pretty=%B | gitru check --stdin
这段逻辑提取最近一条提交的 message,塞给 gitru 做流程校验。如果 MR 里包含多个提交,可以改成检查整个分支的提交范围:
bash复制git log origin/main..HEAD --pretty=%B | gitru check --stdin
注意这只是一个简化的示例。CI 工具和 Git 仓库管理平台(GitHub/GitLab 等)在“如何取到提交范围”的细节上差异很大,你需要根据自己平台的变量体系调整。不过核心思路是一样的:让工具去读 Git 日志,让校验失败直接阻断合并请求。
CI 与本地 Hook 的定位不同。本地 Hook 追求的是“即时反馈”,在你提交的瞬间指出问题,成本最低;CI 追求的是“不可绕过”,保证即使有人本地漏掉了,到了合入流程也一定会被拦下。规范的提交环境需要这两层同时存在,而不是只靠其中一个。
4.4 团队落地的推进顺序与经验
工具接好后,最难的反而不是技术,而是如何让团队接受一个新的“审批官”。我的经验是分三步走,每一步都给足适应时间。
第一步,只提示不拦截。先把 gitru 接到团队内部的一次性校验或定期巡检中,让大家知道工具在检查什么,看到报错先手动改,但不阻断提交。这一步是建立心理预期。
第二步,开启本地 Hook。当团队已经习惯格式要求,再把 commit-msg 钩子打开。这个时候大部分人已经知道怎么写规范,偶发性的错误会被拦截下来并提示修改,摩擦感很小。
第三步,CI 强制。当本地规范执行稳定后,在 CI 的 MR 检查里加上提交信息校验,不给任何一条不规范的提交进入主干的机会。从“大部分人自觉”变成“所有人必须”。
我也建议团队准备一个“整改期”。老仓库里大量历史提交不符合新规范,这是正常的。不要试图去改写历史——把精力放在控制新增提交上。规范的意义从今天开始创造,而不是为过去的行为买单。
5. 避坑心得与问题排查速查
5.1 实际接入时会踩到的一堆坑
先说一个我自己差点被绕进去的问题:Git 提交信息文件里的换行符。Windows 环境下如果 Git 配置了 autocrlf,提交信息文件可能以 CRLF 结尾。如果你的配置里用了类似 $ 这样的正则锚点,CRLF 可能会让匹配失败。排查方法很直接,手动看一下临时文件内容:
bash复制cat -A .git/COMMIT_EDITMSG
如果行尾出现 ^M$,就说明是 CRLF。建议在 .gitattributes 里把提交信息相关文件统一为 LF 处理,或者在正则里兼容 \r?。
再一个高频问题是 Git Hook 脚本的权限。在 Linux 和 macOS 上,脚本如果没有执行权限,Git 会静默跳过它——注意是静默,不会报任何错。很多团队的“本地校验怎么不生效”问题,最后查下来都是因为 git add 钩子脚本时没有带执行位。你可以在仓库里设置 git update-index --chmod=+x .githooks/commit-msg 来修正。
还有一类问题出在“合并提交”上。merge commit 和 squash merge 生成的消息往往不是普通提交格式,常见的是 “Merge branch 'xxx' into 'yyy'” 这种。如果团队有大量 merge 提交,就会频繁触发校验失败。解法一般是在规则里放行以 “Merge ” 开头的消息,或者直接在 CI 里只检查非 Merge 提交。这也是为什么 CI 脚本里要精确控制校验范围的原因。
5.2 问题排查速查表
我把实际工作中容易遇到的现象和排查方向整理成了一个表格,方便你直接对照处理。
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 本地提交没有被拦截 | 钩子脚本没执行权限 | 检查 .git/hooks/commit-msg 或 .githooks/commit-msg 是否有 x 权限 |
| 本地提交没有被拦截 | core.hooksPath 没有指向正确目录 | 执行 git config --get core.hooksPath 确认配置 |
| Windows 下正则总是匹配失败 | CRLF 换行符导致 $、`.`` 匹配异常 |
cat -A 查看文件实际字符,调整正则或统一 LF |
| CI 校验总是失败 | 校验范围包含了 merge commit | 在脚本里过滤 Merge 开头的提交,或改用分支差分范围 |
| 提示信息是乱码 | 中文 commit message 被终端用错误编码读取 | 确保 Git 的 i18n.logOutputEncoding 与终端编码一致 |
| 修改规则后本地不生效 | gitru 可能缓存了配置路径或版本过旧 | 确认 gitru 是读取项目根的配置文件而非用户目录全局配置 |
| 校验配置不报错但所有提交都失败 | 正则表达式写错导致永远匹配不到 | 先用测试字符串单独验证规则,再接入 Hook |
| 使用 --no-verify 能绕过 | 这是 Git 的默认特性 | 接受这个事实,CI 层强制才是最终保证 |
这个表里的每条我都实际遇到过,或者帮别人排查过。大部分问题的根因不是工具本身,而是 Git 环境的差异和脚本细节。
5.3 除了校验,gitru 这类工具还能往哪个方向延伸
如果 gitru 的校验逻辑你已经用起来了,其实可以顺手做不少延伸。我最推荐的方向是提交信息模板化——Git 支持通过 commit.template 配置一个默认模板文件,你可以把规范的格式写进模板里,开发者在编辑器里打开提交界面时,看到的就已经是填好骨架的格式,只需要往里面填内容就行。
搭配命令行参数也一样有效。比如写一个 shell 自动生成类型前缀:
bash复制git commit -m "feat: $(git branch --show-current | sed 's/feature\///')"
这里只是抛砖引玉。再往后,如果团队的提交风格能长期保持统一,就可以在 CI 里增加一个新的检查项:解析此次发布周期内的所有提交,自动根据 feat 和 breaking change 标记生成版本号建议和 CHANGELOG 草稿。没有规范的提交信息,这些自动化都无从谈起,而有了规范的提交信息,它们就只是“脚本量”的问题,不是“可行性”的问题。
我自己现在的习惯是,个人项目也接上 gitru 这类校验工具。一开始确实会有点“多此一举”的感觉,但坚持一个月后再看 git log,那种一眼扫过去就知道每条提交在干嘛的体验,是真的回不去了。如果你也被混乱的提交记录困扰过,不妨给 gitru 一个机会——先配一个最宽松的规则集,把 type 和长度限制打开,跑一个月,再回去看你的提交记录,你会感谢那个愿意花十分钟做这件事的自己。
