用 git history split 拆分 AI 编程代理的巨型提交

Git 2.54 的 git history split 让拆分 AI 代理的巨型提交像 git add -p 一样简单。
AI 编程代理常把多个不相关的改动塞进同一个提交,过去只能靠交互式 rebase 逐一分解。Git 2.54 引入实验性命令 `git history split`,用与 `git add -p` 相同的按键挑选 hunk,把选中内容写成新的父提交,并自动迁移从旧提交派生的本地分支。配套的 `reword`(2.54)、`fixup`(2.55)、`drop`(2.56)分别覆盖消息改写、折叠进较早提交、删除提交并重放后代等场景。备份应打 tag 而非 branch,因为分支会被一并迁移。该命令不处理合并提交、拒绝以冲突收尾的操作、不运行 githooks,截至 2.56 仍是实验性功能。
AI 编程代理一次性交出五文件、两件事的 Update checkout 提交,是很多人最近都遇到的场景。你想今天下午合入的格式化 bug 修复,和你想先争论一番的重构,被塞进了同一个 commit 里。Git 2.54 引入的实验性命令 git history split 正是为这种情况准备的:它让你像 git add -p 一样挑选 hunk,把选中的内容写成一个新的父提交,并自动把建立在旧提交之上的所有本地分支迁移到改写后的历史上。没有待办清单,没有中途停下的 rebase。

问题:一个提交,两种意图
代理很容易落入这个陷阱。任务说"清理 checkout",它沿途碰到的一切就都进了同一个提交。git show --stat HEAD~1 会告诉你问题所在:五个文件、一条消息、两个互不相关的变化。
其中两个文件修复用户今天就能看到的格式化 bug,并加上回归测试;另外三个把硬编码的税率挪到接口后面,并重写相关测试。这个提交只给你两个按钮:全要,或者全都不要。
在 git history split 之前,拆开它意味着交互式 rebase:标记 edit、reset、分块重新暂存、提交两次、continue。Git 2.54 把这个流程压缩成一条命令。
Hunk(代码块) 是 git diff 输出的基本单位:Git 会把每个文件中连续的改动聚成一段,附上若干行上下文,形成一个可以直接接受或拒绝的补丁块。git add -p 和 git history split 都以 hunk 为粒度提问,而不是以文件或整行为粒度。
需要注意,hunk 的边界由 Git 的 diff 启发式算法决定,并不总与语义边界一致。一次格式化修复和一次逻辑重构如果在同一片代码区域相邻出现,就可能被合并进同一个 hunk——这正是文中提到需要按 s 把它拆成更小 hunk 的原因。
用 git history split 拆分提交
把 split 指向那个巨型提交。当它位于 tip 下方一层时,正是过去需要 rebase 才能处理的场景:
git history split HEAD~1
Git 按路径顺序遍历提交的 hunk,用与 git add -p 相同的按键逐一询问。回答 y 把 hunk 移入新提交,n 保持原位,d 则连同该文件的其余 hunk 一起留在原处。
选取完成后,编辑器会打开两次。两次都以代理的原始消息为起点:第一次对应拆分出去的提交(即新的父提交),第二次对应原提交保留的部分。作者信息保持原提交的作者不变。
当关注的改动位于不同文件时,路径规格可以在不按 d 的情况下直达目标:
git history split HEAD~1 -- src/Money.php tests/MoneyTest.php
只有匹配的文件会被提供,其余留在原提交里。
这里有个容易被忽略的判断点:不要拆出 Checkout 测试的重写。旧测试调用无参构造函数,一旦构造函数需要 TaxRate 就会立刻失败。把重构和旧测试放进同一个提交,会让测试套件变成红色。重写后的测试必须跟随迫使它改变的那次改动。按"审阅者会一起批准什么"来对 hunk 分组。
拆分后验证没有内容丢失:
git log --oneline main..agent/checkout
git diff before-split agent/checkout
日志显示三个提交取代了原来的两个,而 diff 应当什么都不输出。根据官方文档,原提交"保持完整,只是其父提交变为新拆分出的提交",所以 tip 处的代码不变。
首次在真实分支上操作时,加上 --dry-run。Git 会写入新对象但不移动任何 ref,并打印出 git update-ref 可以读取的引用更新。若只想移动当前分支,传 --update-refs=head。
用 tag 而非 branch 做备份
改写前先打一个标签标记起点:
git tag before-split
这里的选择有讲究。默认情况下 git history 会移动所有从被改写提交派生出的本地分支,因此放在 tip 的备份分支会连同一并被移走。文档只谈到移动分支,所以标签会留在你放置的位置。
--dry-run 与 --update-refs 是两个降低风险的关键参数。--dry-run 会完成全部对象写入和引用计算,但不动任何 ref,只打印出将要执行的 git update-ref 命令,你可以先审阅再真正执行。
--update-refs=head 则把自动迁移的范围收窄到当前所在的单个分支上。默认行为是移动所有从被改写提交派生出的本地分支,这在有多个并行工作分支时可能超出预期,因此当你只想改写自己手头这条线时,显式指定该参数更安全。
不用 rebase 的 reword 与 fixup
顶部的 more fixes 提交依然在。git history reword 会在编辑器中打开任意提交的消息并就地改写。它不触碰工作树和索引,甚至在裸仓库里也能工作。把 more fixes 换成 Document the TaxRate dependency in README 只需一条命令:
git history reword HEAD
对于 HEAD 本身,git commit --amend 效果相同。reword 的价值在于三四个提交之前、amend 够不到的位置。
随后审阅发现一个缺口:修复缺少针对小于 1.00 金额的测试。Git 2.55 新增的 git history fixup 会把已暂存的改动折叠进较早的提交,并把其后的提交重放到上面:
git add tests/MoneyTest.php
git history fixup 'agent/checkout^{/Fix zero}'
^{/Fix zero} 后缀会找到分支上消息匹配的最新提交。修复提交保留原有的消息和作者;想编辑消息可以加 --reedit-message。Git 2.56 又加入了 git history drop,用于删除某个提交并将其后代重放到其父提交上。
如果 agent/checkout 已经推送过,这些操作和任何 rebase 一样属于改写历史:用 git push --force-with-lease 推送。想放弃整个练习,在分支上执行 git reset --hard before-split 即可。
它明确拒绝做什么
官方 man page 直接列出了限制,其中大部分是刻意的设计:
- 不碰含合并提交的历史。这类情况请用
git rebase --rebase-merges。 - 任何会以合并冲突结束的操作都会被拒绝。历史改写不该把你留在半完成状态,所以它直接中止而不是中途停下。
- Githooks 不会在"当前时间点"运行。如果
commit-msg钩子检查消息格式,它看不到你在拆分过程中输入的消息。 - hunk 选择器以禁用编辑的方式调用,因此不提供
e。当一次修复和一次重构共享同一个 hunk 时,可以试试s把它拆成更小的 hunk,或者退回git rebase -i。
全选或全不选同样是无效的,因为那样两个提交中会有一个为空。截至 2.56,整个命令仍标记为实验性,官方警告行为可能变化。
需要重排一段提交范围或整体搬到新基座时,git rebase 依然是正确的工具。git history 覆盖的是"一次一个目标明确的编辑"。
Githooks 是 Git 在特定事件(如 pre-commit、commit-msg、post-commit)时自动调用的脚本,常用于校验提交消息格式、跑 lint 或触发 CI。通常它们在每次 git commit 时按顺序触发。
git history split 在内部以编程方式生成提交对象,跳过了这些钩子,因此依赖 commit-msg 校验的工具不会看到拆分过程中输入的两次消息。如果团队的合规检查依赖钩子,拆分完成后需要手工核验提交消息,或另行运行校验脚本。
拆分之前,先看清代理的每一步
选择哪些 hunk 应该放在一起,是拆分中最难的部分;而代理留下的单个提交,并不会告诉你它工作的顺序。

Hermes IDE 让每个agent(Claude Code、Codex 等)在自己的终端里运行,并保存每一个 agent 轮次,因此你可以对任意单轮做 diff 或恢复。Review Desk(macOS 上 ⌘G,Windows 和 Linux 上 Ctrl+Shift+G)按轮次或按文件展示这些改动,并附带风险标记。
如果代理在第一轮做了补零修复、在第二轮做了重构,按轮次视图会在你敲下命令之前就展示出 git history split 所需的路径规格。如果在代理提交前就打开 Review Desk,你可以从那里按文件暂存并提交,根本不需要拆分。对某一轮留下的行内评论会回到写下它的代理,这样"把测试重写和重构放在一起"的建议就能抵达正确的终端。
需要说明的是,Hermes IDE 是本文作者所在的项目,阅读相关段落时请据此权衡。它免费、源码可见(BSL 1.1),支持 macOS、Windows 和 Linux,与 Nous Research、Anthropic、OpenAI 或其他代理厂商均无关联。
相关推荐

iPhone Duo上手体验:2000美元的小屏折叠,值不值?
YouTube博主Allison分享iPhone Duo上手体验:双屏多任务效率翻倍,视频观看体验出色,磨砂质感好评,但机身偏厚、单屏操作偏小,2000美元定价进入MacBook区间,属于奢侈品定位。

Sentry广告笑疯开发者:On-call也能变有趣?
Sentry广告短片以开发者On-call焦虑为切入点,用幽默对话展示其将错误、日志、链路、指标、录屏和发布记录集中一处的可观测性能力,引发开发者社区共鸣。

LLM应用越改越糟?三步构建有效评估体系
LLM应用迭代时如何判断变好还是变坏?本文基于Reddit开发者讨论,详解构建测试集、自动化回归评估、LLM-as-judge评分与结构化追踪智能体行为,建立从生产反馈到评估追踪的持续闭环。