git-cm:在终端里用 LLM 生成 commit message 🤖 本文由作者提供想法和校验,Kimi k2p6 协助编写、整理和发布。

摘要

本文介绍 git-cm,一款在终端中用 LLM 根据 staged 改动生成 commit message 的工具。核心设计是通过 tool call 约束 LLM,仅当调用 message 工具时才输出最终结果,避免从自由文本解析。工具还支持学习历史风格、email 检查和 commit 重写。

评分: A


写 commit message 这件事,小改动随手就能写,复杂改动就挺烦的。最近我搞了个小工具 git-cm,专门在终端里让 LLM 帮我生成。

它只做一件事:根据 staged 的改动和仓库历史,输出一条合适的 commit message。

为什么需要它

写 commit message 这件事,说大不大,说小也不小。

如果只是改个 typo,我通常随手就写了。但如果改动涉及多个文件、好几处逻辑,想要总结出一句清晰、规范的 message,就得多想一下。更麻烦的是,团队里如果有约定(比如 Conventional Commits),每次还得先对齐风格。

市面上不是没有解决方案。VS Code 之类的 IDE 里有插件,但我经常只开着终端;opencode 这类通用 agent 也能做,但它需要你把规则写进工作流,走的是通用流程,出错或者跑偏的风险总是存在。

我想要的是一个更轻量的东西:只干生成 commit message 这一件事,在终端里直接跑。

git-cm 就是按这个思路做的。

核心设计:用 tool call 来收尾

git-cm 给 LLM 提供了几个工具:

  • read_file:读取仓库里的文件,补充上下文。
  • grep:在仓库里搜索特定模式。
  • diff_more:当 diff 太大时,分批获取更多内容。
  • message:提交最终生成的 commit message。

其中最关键的设计是:只有 message 工具被调用时,才认为任务结束。

这么做的原因很直接。如果让 LLM 直接在输出文本里返回 commit message,我们就得通过字符串解析去提取它。但 LLM 的输出并不总是可控的——它可能在 message 前后加一些解释、格式不一致、甚至一次返回多个候选。解析起来很容易踩坑。

用 function call(或者叫 tool call)规范输出就完全不一样了。每个工具都有明确的 JSON Schema,message 工具只接收一个 message 字段。LLM 想结束任务,就必须以结构化的方式调用这个工具。生成结果的位置、格式、类型都是确定的,不需要再从自由文本里猜。

工具定义大概长这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
MESSAGE_TOOL_SCHEMA = {
    "type": "function",
    "function": {
        "name": "message",
        "description": "Submit the final commit message when you are ready.",
        "parameters": {
            "type": "object",
            "properties": {
                "message": {
                    "type": "string",
                    "description": "The commit message to propose.",
                }
            },
            "required": ["message"],
        },
    },
}

整个交互被限制在一个很窄的范围内:LLM 只有 read_filegrepdiff_moremessage 四个工具可选,目标也很明确,就是生成一条 commit message。它不会去拆解任务、调用系统命令、或者做其他发散的事情,跑偏的概率小很多。

具体过程是一个循环:LLM 拿到提示后,可以反复调用工具收集信息,直到它觉得自己准备好了,再调用 message 提交结果。中间任何非结构化的文本输出都不会被当成最终答案。

风格、安全和重写

学习历史风格。 它会读取最近几条 commit message 塞进 prompt,让 LLM 生成的 message 更容易跟仓库现有风格保持一致,而不是每次都从头摸索。

email 检查。 提交前会检查当前 git user 是否出现在历史提交者里。如果 name 或 email 对不上,会提示确认。这个主要是防止在公司仓库用错个人邮箱,或者反过来。

commit 重写。 支持 git-cm -r 重写最近一次 commit message,也可以指定某个 commit id。重写非 HEAD 提交时会用 interactive rebase,并且会提示该提交是否已经推送到远程。

安装和使用

git-cm 用 Python 写的,安装很简单:

1
pipx install git+https://github.com/caibingcheng/git-cm.git

第一次运行会引导你配置 LLM provider、model 和 api_key。配置存在 ~/.config/git-cm/config.toml,也支持多 provider 切换。

日常使用就是:

1
2
git add .
git-cm

它会显示生成的 commit message,询问你是否接受。如果加 --yes 就直接提交:

1
git-cm --yes

也可以给点提示:

1
git-cm 'focus on the auth fix'

重写最近一次 commit:

1
git-cm -r

后记

git-cm 的代码我没写,是 coding agent 完成的。我在里面扮演的角色,更像是一个把握方向的人。

不只是提需求,还要在关键节点上做判断。比如最开始 agent 的方案是通过解析 LLM 的输出文本来提取 commit message,但我觉得用 function call 更稳,于是顺着这个方向一步步迭代,最后确定了“只有 message 工具被调用才算完成”的约束。

回头看,这个选择挺关键的。如果一开始按字符串解析去做,后面估计要处理很多格式不一致的边角问题。现阶段让 LLM 写代码并不难,难的是知道有哪些路可以走,以及在岔路口选对方向。

git-cm 就是这样一个方向的产物。代码可能是 LLM 写的,但选这条路的是我。