用 Jujutsu 打败 Git 严谨疲劳

对于许多开发者来说,理想的 Pull Request 是一个精心策划的叙事:一系列逻辑清晰、原子化的提交,引导审阅者了解实现过程——从类型定义开始,接着是数据库函数,最后是 UI。实际上,在开发高峰期保持这种“严谨”是令人筋疲力尽的。

我们大多数人都会陷入一种“混乱提交”的模式:将特性开发、错误修复和重构混合在少数几个凌乱的变更集里。虽然像 git rebase -ijj absorb 这样的工具可以用来清理,但它们往往会带来自己的阻力,例如合并冲突或不精确的变更分配。这被称为“git 严谨疲劳”——在尝试保持干净的历史记录的同时解决复杂技术问题所带来的心理负担。

“大堆洗衣”工作流

为了解决这种疲劳,可以采用一种更轻松的方法,使用 Jujutsu (jj),这是一种将工作副本视为一等提交的版本控制系统。该工作流并不是在开发 期间 维持完美的历史,而是建议接受混乱,并在结束时一次性整理干净。

过程

  1. 拥抱混乱:正常开发你的特性。随意创建提交,包含临时调试状态,不必在意具体改动落在何处。你很可能会得到一系列凌乱、重叠的提交。
  2. 定义理想状态:特性完成后,创建理想历史的“骨架”。使用 jj new 创建空提交,并使用你希望拥有的描述性标题(例如,“define types”、“add DB functions”、“server CRUD”)。
  3. 整合混乱:将所有实际的开发提交压缩为一个“全部提交”。
  4. 分配改动:使用 jj squash -i 交互式地将“全部提交”中的改动移动到你理想化的骨架提交中。你挑选出“红色”改动(类型)并移动到类型提交,随后将“蓝色”改动(UI)移动到 UI 提交,依此类推。

在流程结束时,“全部提交”将为空,而你的历史记录则为审阅者完美结构化。

为什么这优于传统方法

相较于 jj split 或标准的交互式 rebase,这种“洗衣”方法提供了若干优势:

  • 降低冲突风险:因为你是将改动从最终的、稳定的状态移动到新的历史中,避免了在开发期间迭代使用 jj squash -i 时常见的中间合并冲突。
  • 降低认知负荷:在编码过程中,你无需决定改动属于哪个提交。你只在结束时执行一次“排序”任务。
  • 灵活性:可以先对最简单的代码块进行排序,而不必担心这会如何影响其余历史的顺序。

权衡与反驳

和任何工作流一样,这种方法也并非没有缺点。主要担忧是中间提交可能无法编译。如果你的团队要求 PR 中的每一次提交都通过 CI 并且可构建,那么此方法可能会成为致命因素,因为你是按逻辑类别而非时间顺序进行排序的。

围绕此话题的社区讨论凸显了关于“好提交”价值的更广泛争论。

“干净历史”案例

“Jujutsu 将工作副本视为一次提交的做法解决了 Git rebase 工作流中许多常见的摩擦点。” — @danborn26

对于这些用户而言,将 VCS 视为叙事工具的能力显著提升了审阅过程的效率。

“Squash and Merge”案例

相反,有人认为如果团队在合并时将整个 PR 压缩为一次提交,那么为打造完美历史所付出的努力就是浪费。

“我终于接受了 PR 的 squash,并意识到我浪费了青春去写好提交。” — @drdrey

其他人指出,完美的干净历史实际上可能不利于调试,因为它剥离了设计演进的上下文。

最后思考

无论你使用 Git 还是 Jujutsu,目标都是降低编写代码与记录历史之间的摩擦。有人偏好从一开始就保持原子提交的纪律,也有人倾向于通过 squash 完全抹去历史,而“Big Pile of Laundry”方法提供了折中方案:在创作期间可以随意混乱,随后再有能力精心策划叙事。

SUMMARY: 探索一种使用 Jujutsu 管理复杂特性开发的新工作流,以避免在活跃开发期间维护干净提交历史的心理负担。

TITLE: 用 Jujutsu 打败 Git 严谨疲劳

Sources