大型功能分支最累的地方,经常不是把代码写出来,而是把它讲成一个干净故事。
理想提交应该像目录:类型、数据库、服务端 CRUD、客户端 API、UI,一层层递进。Reviewer 看得轻松,维护者也能追踪设计变化。
现实不是这样。WIP、返工、修 bug、重构、跨层改动,会把早期提交反复覆盖。代码最后能跑,提交历史却碎了。这篇文章里的 Jujutsu 工作流,解决的就是这种 Git 式严谨疲劳。
它不是 Jujutsu 官方发布,也不是“Git 已经过时”的证据。它更像一个承认现场混乱的整理术:开发时先让代码长出来,收尾时再把历史剪成可读版本。
好提交有用,但大型功能天然会弄脏历史
Good Commits 的价值很清楚。它让 review 从“看一坨 diff”变成“按层理解变化”。
| 理想提交 | 主要内容 | 对 review 的价值 |
|---|---|---|
| define types | 定义类型 | 先看数据形状 |
| add DB functions | 数据库函数 | 单独检查存储逻辑 |
| server CRUD | 服务端增删改查 | 聚焦后端行为 |
| client API | 客户端接口 | 检查前后端契约 |
| client UI | 界面实现 | 最后看交互呈现 |
问题是,功能开发很少按这个顺序发生。
你可能先写 UI 才发现类型不够用。写完 CRUD 又回头改数据库函数。调试时塞了临时代码,后来修 bug 又覆盖早期提交。顺手重构一个工具函数,还牵动了好几层。
这不是工程师不专业。很多时候,探索本来就是来回走。把探索过程保持成线性叙事,本身就是额外劳动。
对经常做大型功能分支的人,这意味着一件具体事:不要只盯着“每一步都提交得漂亮”。更现实的策略,是先保证最终代码正确,再预留一段专门整理提交历史的时间。
对 reviewer 也是一样。看到一个 PR 历史干净,不代表作者一路优雅推进。更可能是作者已经替你做过一遍编辑。好历史不是自然产物,是维护成本。
这套 Jujutsu 流程:先合成一坨,再按主题分拣
作者的做法可以压缩成三步。
先创建一组理想的空提交,比如 red、blue。它们是目标篮子,代表你希望最终呈现的主题提交。
再把真实开发中的混乱提交区间,比如 messy-first..messy-last,压进一个 everything commit。这个提交装下所有实际改动。
然后用类似 jj squash -i --from messy-first --into red 的交互式操作,把属于 red 的 hunk 挑出来,放回 red。接着处理 blue。直到 everything commit 被掏空。
它和几个常见工具的差别在边界处理上。
| 方法 | 适合什么 | 主要限制 |
|---|---|---|
jj absorb | 把新改动吸收到旧提交 | 依赖最近触碰文件的归属判断,可能分错主人 |
jj squash -i | 手动选择 hunk 合并 | 边界不干净时,容易陷入冲突 |
jj split | 把一个提交切开 | 漏掉 hunk 后,还要继续切、合、调整 |
| everything commit 流程 | 先有目标提交,再从大堆里分拣 | 单个整理后提交不保证能编译 |
这个流程的好处是顺序反过来了。
不是边开发边维护完美历史,而是在最终状态稳定后,再把改动按主题归位。你可以先挑最容易判断归属的 hunk,不必从一开始就把提交切得像手术刀。
它也降低了一类烦人的冲突。everything commit 代表最终状态,自身不会在内部打架。你从这个最终状态里往 red、blue 分拣,很多时候比反复把“修 red 和 green”的中间提交塞回旧历史更顺。
但别把它神化。最大代价很硬:整理后的每个提交未必都能单独编译。
如果团队要求每个 commit 都通过 CI,或者强依赖 git bisect 定位问题,这套方法可能不合适。至少不能原样照搬。它更适合那些把 PR review 可读性放在更高位置、但不强制每个中间提交都可运行的团队。
真正要观察的变量也在这里:你的团队到底把“每个提交可构建”看得多重?CI 是跑整条 PR,还是逐提交跑?reviewer 是按 commit 看,还是只看最终 diff?答案不同,工具选择就不同。
这不是懒人技巧,是把开发和叙事分开
我更在意的不是 Jujutsu 多顺手,而是它戳破了一个行业习惯:我们常把“会写代码”和“会编辑提交历史”混在一起考核。
开发现场需要试错。代码评审需要秩序。两件事都重要,但不是同一种劳动。
很多团队嘴上说“提交要干净”,听起来像工程素养。落到大型功能里,它常常变成另一份工作:删掉弯路,重排顺序,把返工伪装成线性推进。
这不算虚伪。Reviewer 不该被迫读你的思维草稿。维护者也需要一条能追溯的线。
问题在于,工具和流程不该假装开发过程天然整洁。古人说“修辞立其诚”。放到代码里也成立:提交历史当然可以被修辞,但前提是别把修辞成本藏起来。
Jujutsu 这套工作流的价值,就在于把成本摊开。开发阶段付探索成本,收尾阶段付叙事成本。该乱的时候别假装不乱,该交付的时候别把乱交给 reviewer。
对大型功能分支作者,动作很具体:可以在动手前先想好最终提交目录,但不必每一步都维持它。等功能稳定,再用 jj 把 everything commit 分拣回主题提交。
对想试 Jujutsu 的开发者,也别急着迁移整个团队。更稳的方式,是先在个人分支、小型 PR、非强制逐提交 CI 的仓库里试。看三件事:冲突是否减少,review 是否更顺,整理后的提交能否满足团队规则。
这事和早期电影剪辑有点像。不完全一样,但结构相似。拍摄现场不按观众观看顺序发生,剪辑台负责把素材组织成叙事。代码开发也是这样。现场有现场的乱,交付有交付的秩序。
Jujutsu 没有取消这份劳动。它只是让这份劳动更像剪片,而不是让摄影师边拍边剪。
所以结论并不复杂:如果你的团队重视逐提交可构建,这套方法要谨慎;如果你的痛点是大型 PR 难 review,它值得试。真正的分水岭不是 Git 和 jj 谁赢,而是你愿不愿意承认,干净历史本来就是事后编辑出来的。
