Ju! Ju! Tsu!:一本讲 jj(Jujutsu)版本控制的书
文章摘要
《Ju! Ju! Tsu!》是意大利开发者 Arialdo 写的一本免费在线书,主题是 Jujutsu(命令行叫 jj),一个与 Git 完全兼容的开源版本控制系统。书用 mdBook 发布在 Codeberg Pages 上,有 HTML 和 PDF 两种版本,采用 CC BY-SA 4.0 许可。
「献给人类」。 序言第一节的标题是「In praise of human beings」,作者开门见山地说:这本书是人写的,不是 AI 写的,因为他尊重读者的智力。他给这本书定的目标是:精简(你的时间很宝贵)、实用(少说多做)、渐进(从第一天就能用上 jj)、不手把手(自己发现比被告知更有趣)、不走寻常路(Jujutsu 本身就很出格,普通教程太无聊)。书是免费的,作者说自己只是把得到的东西回馈出去;如果想付钱,请捐给你选择的慈善机构。他自称不是权威,只是一个同学,按自己希望别人当初给他讲的方式来写。书很长,但大部分是命令和终端输出。英语不是他的母语,欢迎提 PR。
这本书写给谁。 作者列了一串 Git 用户的典型焦虑:「分离 HEAD」让你紧张,reset 的 –soft、–mixed、–hard 分不清,rebase 时祈祷好运或者干脆不敢,reflog 是未知之地,搞砸之后不知道怎么回去。另一类读者是已经精通 Git、但对「同时在多个分支上工作」「边打字边交互式 rebase」「把修改发送到过去和未来的提交」这类奇思妙想感兴趣的人。他说这本书同时面向两种截然不同的读者:玩转 reflog 的 Git 巫师,以及一遇冲突就僵住的胆怯新手。原因在于 Jujutsu 自身「简单得不可思议,又强大得惊人」,就像化身博士的两个人格在同一间实验室里和睦共处。
为什么需要 Jujutsu。 作者用「如果有一根魔杖,你会改 Git 的哪三件事」来引出动机:
- Git 的基本构件太多:改动分别存在工作区、暂存区和仓库三个地方,stash 又是另一套规则,冲突时还会冒出 MERGE_HEAD 之类的伪引用。
- 接口不一致:他引用 Steve Losh 的《Git 公案》,列出标签、远程、分支和当前分支各用一套完全不同的命令查看。暂存区是最典型的特例:它像下一个提交又不是提交,有 SHA1 却不能被分支指向,也不能 git show。
- 抽象泄漏:在 Git 里撤销一个操作,你得先知道它底层怎么实现,再推出逆操作。交互式 rebase、stash apply、打标签、add、commit –amend,各有各的撤销办法。而 jj 里统一就是 jj undo,撤销 fetch、rebase、合并、冲突解决都一样。
从 amend 讲到「可编辑的历史」。 作者很欣赏 git commit –amend 这个抽象:它捕捉了「我想改上一个提交」的意图,隐藏了底层的 reset 和 cherry-pick。可是一旦你想改倒数第二个提交,或者某条旁支上的提交,抽象就撕裂了。Jujutsu 的做法是把它推广:jj edit 任意一个提交都能直接修改,后代提交会自动跟着变基。rebase 冲突也不再让仓库进入「危险模式」:Jujutsu 把冲突当作一等公民,rebase 照常完成,冲突的提交在 jj log 中被标出来,你可以晚点再解决,也可以在解决时继续使用其他所有功能。
Change ID。 Git 里 amend 之后提交哈希就变了,Jujutsu 额外给每个「变更」一个稳定的 Change ID,提交内容怎么改,它都不变。Change ID 用 z 到 k 的「反向十六进制」字母,和 Git 的十六进制 SHA1 永远不会混淆,而且 jj log 只高亮能消除歧义的最短前缀,往往两个字母就够了。作者还给出了自己的单行精简日志模板,后文都用这个格式。
「拔牙」:先接受几条反直觉的设定。 作者说学 Jujutsu 的公式是:清空 Git 心智模型,接受几条起初让人反感的新假设,学会极少的几个构件,然后收获。这些假设包括:默认强制推送;它会自己提交,连 status 和 log 都会触发提交;rebase 自动发生;默认追踪文件而非忽略;提交是可变的;「分离 HEAD」是常态;习惯上先提交再写代码;没有分支、没有暂存区、没有 stash、没有 merge、cherry-pick、reset 或 add 命令。他回忆 Git 刚出现时也曾招来一片抗议(「本地存整个历史?太蠢了!」「重写历史?异端!」),没想到会看到续集。
一套 CRUD 语法。 作者把 Jujutsu 归结为对「变更」的增删改查:new、show、edit、abandon,再加上 rebase(移动变更)、squash(把修改挪到另一个变更)、restore(在变更之间复制文件)、log 和操作日志的 undo/redo。在此之上还有 split、duplicate、arrange、bisect 和能把修改自动分发到合适位置的 absorb。他说光是这两组命令就能让你走得比 Git 更远。
正文结构。 书的主体是大量手把手练习:初始化(为什么新仓库一上来就有两个变更,其中全 z 的根变更像空对象模式,是所有历史的起点)、提交、新建与丢弃、克隆、合并与拆合并、在历史中上下移动、各种「移动」操作(移动变更、移动集合、对一两个分支 rebase、移动编辑、absorb、squash 工作流、restore 等)、冲突(合并冲突、rebase 冲突、与冲突共存、各种出错场景的「自助餐」以及冲突模型)、书签与远程(书签不是分支、书签可以同时指向多个修订、跟踪、推送、拉取、书签冲突),附录还有日志模板、「抽掉自己脚下的地毯」和 Emacs 式撤销。
书中有一个插曲叫「Megamerge」:把三个本地功能分支在本地合成一个巨型合并,直接在合并之上开发,再把修改分发回各自分支,用完 jj abandon 掉合并即可。紧接着的一节「锤子与钉子」是全书最有思想性的段落:作者认为工具的难易决定了什么是可以想到的,需要 40 步脆弱 Git 操作的工作流不会被尝试,它干脆从思维中消失了。他引用 Ken Iverson 1979 年的图灵奖演讲《作为思维工具的记号》和 Paul Graham 的 Blub 悖论,建议读者刻意接触那些自己永远不会认真使用的工具和语言,因为它们会扩展想象力的边界。另一节「不要直接 edit」则自我纠正:直接 jj edit 一个历史变更会把文件系统里的一切(包括构建产物和手误)都提交进去并级联到后代,更稳妥的做法是在它上面新建一个变更再 squash 回去,作者由此承认 Git 的暂存区其实是个好东西。
HN 评论精华
这个帖子只有 23 分、11 条评论,讨论不算热烈,主要分成两部分:几位读者称赞这本书的写作,另外一组长回复则演变成一场关于 jj 值不值得学的争论。
对书本身的评价。 mugul 说这是一本很好的教程,技术深度和行文节奏「至少对我来说恰到好处」,读到用心写成、不像又一篇 Claude 生成博客的东西,感觉很好。grim_io 接话说,哪怕只是为了换换口味读点不像 Claude 写的东西,他也要读一读。metiscus 开玩笑说原以为这是一本讲柔术(Jujutsu)的书,他不用这个「非格斗版本」,但看得出写得很好,向作者致意。TheHalfDeafChef 说读下来很享受书的节奏,顺便指出了一个错别字:序言「At a Glance」那一节里的「Impatietn」应为「Impatient」(本文写作时这个笔误仍在页面上)。
关于 jj 值不值得学的争论。 cguess 说自己试过 jj 好几次,每次一天就放弃了:要跟原始 SHA1 哈希打交道太繁琐,始终没搞明白怎么在日常工作里用它。他认为这本书看得出是用心之作,但 Git 用户的头号难题合并冲突直到第 13 章才讲,让他想起最近读的一本 Neovim 教程,「编辑文本」要到第 6 章才出现。
rstuart4133 回复说「你用错了」:用 jj 时需要用到原始哈希的次数比 Git 少得多。刚开始时你会主要用书签,因为你还把 jj 当成 Git 的另一个前端,脑子里还是 Git 的 HEAD;之后你会习惯成堆的变更,开始用 Change ID 而不是 SHA1;再往后你会学会 revset,自定义几个别名,在提交树里来回穿梭快到旁观的 Git 用户看不懂。然后某天被迫回到 Git,你会纳闷以前是怎么用它干活的。「那是一年多以后的事。」
cguess 回应说自己有三成把握这是反讽,如果不是的话:他日常用 Git 从不碰哈希,全用分支名,用到哈希通常意味着已经出了大问题,进入了 xkcd 1597 那种境地。他还质疑为什么要在提交之间挪来挪去,这不是破坏了提交作为「确认过的改动集合」的意义吗?他的结论是 jj 像是给喜欢「管理代码」而非「写代码」的人准备的。
rstuart4133 坚持说自己是认真的:jj 没有 commit、add 这类日常命令,它们是自动完成的,多数 rebase 也一样。如果目标是少花时间敲 VCS 命令,就该用 jj,但只有长期才会省时间;短期内你会像当初学 Git 时一样手忙脚乱(那时你还会用删掉仓库重新 clone 来恢复),而 jj 的 jj undo 对所有命令都有效,不会丢失信息。接着你会花几个月把它当作 Git 的另一个前端,再过许多个月才会潜移默化地按设计者的本意使用它。只试几个小时连「手忙脚乱期」都没熬过。
cguess 表示理解对方的好意,但如果一个工具要花好几个小时才基本可用,他就不玩了。Git 上手不需要几个小时,add、commit、push 立刻就有用,至少能把代码存到笔记本以外的地方,其余细节用到再学。他认为「要花好几个月潜移默化才能按设计者的本意使用」这种说法太离谱,意味着 jj 永远火不起来,他愿意为此押一大笔钱;即便是 Vim 或 Emacs,打开第一个小时也能开始写东西。
rstuart4133 最后一次长回复承认「这很公平」,但认为难点恰恰来自 Git 经验:jj 其实比 Git 简单,没有暂存、没有单独的提交步骤、更少需要 rebase、undo 总能用,对新手更容易,可 Git 老用户得先忘掉很多东西。如果你在 Git 里只做 pull、add、commit、push,那确实很难说服你换,省掉两条命令算不上大收益;但大多数人只用这几条,正是因为 Git 其他操作又乱又危险,「如果你没被 git reset –hard 弄丢过代码,说明你用 Git 还不够久」。等你熟悉 jj 之后,像把一个大 PR 拆成一串讲清楚演进过程的提交、减轻审阅者负担这类事会变得很容易,而在 Git 里在提交之间挪代码非常痛苦,所以 jj 用户的提交历史往往整洁得多。
cguess 的最后回复是:听起来你只是更喜欢管理代码而不是写代码,这也没问题,就像有些人喜欢花几个小时定制编辑器。至于 git reset –hard,他用了 15 年 Git 从没需要过,最多回退过单个文件。争论到此结束,双方都没有被说服。