人之相识,贵在相知,人之相知,贵在知心。——孟子

三行命令变一行:worktrunk 把 git 里那个被埋没的功能挖了出来

一个被埋了很久的功能

git 里有个东西叫 worktree。

它的作用是:让同一个仓库同时在多个目录里被检出,各自在不同的分支上。你在 ~/repo 上待着不动,另开一个 ~/repo.feat 去做新功能,两边的改动互不干扰,共享同一份 .git。

这个功能在 git 里已经存在很多年了,一直不温不火。原因是大部分人的工作流里,切换分支就够了——反正你只有一双手,一次只能改一处。

然后 agent 来了。

一个能连续跑半小时不喊停的编码 agent,会让你产生一种很自然的念头:那我同时开五个呢?

项目地址:https://github.com/max-sixty/worktrunk

worktrunk 就是从这个念头开始的产品。它的描述只有一句:一个 git worktree 管理的 CLI,为并行 AI agent 工作流而设计。

五个 agent,五个目录,互不踩脚

文档把背景交代得很清楚:

像 Claude Code 和 Codex 这样的 agent,已经能长时间无人监督地处理任务,因此同时管理五到十个是可行的。而 git 原生的 worktree 功能,正好能给每个 agent 一个独立的工作目录,让它们不互相踩改动。

逻辑成立。但紧接着就是那句转折:

可 git worktree 的体验很笨重。

笨重到什么程度?文档举了个例子:哪怕只是开一个新工作区这么小的事,你都得把分支名打三遍。

先是 git worktree add -b feat ../repo.feat,然后 cd ../repo.feat。

打三遍不是修辞。-b 后面一次,路径里一次,cd 里再一次。而人是会打错的,尤其是在第五个窗口里。

它做的第一件事:把分支名变成地址

worktrunk 的核心设计只有一句话:工作区用分支名寻址,路径由可配置的模板算出来。

这句话听着平淡,实际是把一个「你需要记住路径」的系统,改成了一个「你只需要记住分支」的系统。

分支名你本来就要记,因为你要提交、要开 PR、要合并。路径不是——路径是机器该操心的事。

而且它留了个后门:接受分支名的命令,也接受该工作区所在的路径。你想用哪种都行。

这套「以名字为地址」的抽象,一个直接的后果就是命令变短了。下面这张对照表来自文档:

任务 worktrunk 原生 git
切换工作区 wt switch feat cd ../repo.feat
建工作区并启动 Claude wt switch -c -x claude feat 三行命令
清理 wt remove 三行命令
带状态列出 wt list git worktree list,只有路径

表格右列最扎眼的不是长度,是最后一行那四个字:只有路径。

一条命令里塞了四件事

wt switch -c -x claude feat 这一行值得单独拆开看。

它同时做了:建分支、建工作区、切过去、启动 Claude。

四个动作,一个命令,零次重复输入。而原生 git 那边是三行,还得你自己记得 && 串起来,不然中间某一步失败了你都不知道。

这个差别在一个 agent 上不明显,在五个 agent 上就是「愿不愿意再开一个」的分界线。

工具的复利不在单次节省的那三秒,在它降低了「再来一次」的心理成本。

并行之后,冒出来的四个新问题

开五个工作区很快乐,快乐完之后有四个麻烦:

端口冲突。 每个工作区都想起一个开发服务器,然后它们想占同一个端口。

缓存重复。 十个工作区,十份 node_modules,十份 target/,硬盘开始报警。

清理。 活干完了,工作区和分支还堆在那儿,一个一个删到手软。

总览。 五个工作区五个 agent,谁卡住了,谁跑完了,谁的 CI 挂了?

worktrunk 的「生活质量功能」几乎全在回答这四问。

端口:模板里有个 hash_port 过滤器,给每个工作区算一个唯一的端口。

缓存:这一步做得最漂亮——在 APFS、btrfs 和 XFS 上,十个工作区可以直接共享 target/、node_modules/ 这些目录,既不用重新构建,也不用真的复制。这靠的是写时复制,文件系统层面的能力。

清理:wt remove 一条命令,工作区和分支一起收掉。

总览:wt list 给每个分支列出 CI 状态,还有 AI 生成的摘要。

再加上 PR 直接检出——wt switch pr:123 跳到那个 PR 的分支——并行工作流该有的零件基本齐了。

那一屏 wt list,信息密度有点高

快速开始那一段的输出,值得看一眼:

1
2
3
4
5
  Branch        Status      HEAD±     main↕    main…±    Remote⇅  Commit    Age  Message
@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticatio…
^ main ^⇡ ⇡1 0e631ad 1d Initial commit

○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path

@ 是当前所在的工作区,^ 是主分支。+ 表示有暂存的改动,↑1 表示领先 main 一个提交,⇡ 表示有没推的提交。

一行里塞了分支、状态、相对 HEAD 的增删、相对 main 的领先落后、相对远端的同步情况、提交号、时间、提交信息。

而且最后那行还告诉你:隐藏了 Path 列。

一个列表能自定义显示哪些列,说明作者自己每天都在用这个东西,而且被某一列挤得难受过。

合并:一条命令里做完六件事

wt merge main 的输出,是整份文档里我最喜欢的一段,因为它把「自动化」和「你看得见发生了什么」这两件事同时做到了:

先生成提交信息并提交改动,告诉你改了几个文件、加了多少行、有没有需要 squash。

然后合并到 main,如果不需要 rebase 就直说「不需要 rebase」。

然后打印被合并的提交、改动的文件、增删行数。

然后在后台移除工作区和分支,并说明为什么可以安全移除——因为它和 main 是同一个提交。

最后切回 main 的工作区。

这六步,每一步都有输出,每一步都说得清为什么。

对比一下某些「一条命令搞定一切」的工具:它也确实一条命令搞定了,但你不知道它干了什么,出事的时候也不知道从哪一步开始查。

自动化最难的从来不是省掉步骤,是省掉步骤之后还让人放心。

安装:一句装包,一句装 shell

1
brew install worktrunk && wt config shell install

两条命令,第二条是关键。

为什么要单独装 shell 集成?因为 wt switch 需要改变你当前 shell 的目录。一个普通子进程改了 cd,父进程是不知道的——这是 Unix 进程模型的基本事实,绕不过去。

所以它得往你的 shell 配置里注入一小段东西,让命令能在当前 shell 里执行。

这一句也解释了为什么它不能只是一个「下载了就能跑」的二进制。工具和 shell 的关系,比大多数 CLI 想象的要深。

第二种装法是 cargo,Arch 用 pacman,conda 和 pixi 也有社区维护的包。

Windows 上那个撞名的 wt

这一段是全文最诚实的技术细节之一。

wt 这个短名字在 Windows 上是 Windows Terminal 的命令。所以 winget 安装时,会额外把它装成 git-wt,避免冲突。

你要想直接用 wt,就得去设置里把 Windows Terminal 的别名关掉:设置 → 应用 → 高级应用设置 → 应用执行别名 → 关掉 Terminal 和 Terminal Preview。

给一个命令取一个两个字母的名字,代价是要跟操作系统抢命名权。

而它没打算抢,它打算共存——两个名字都给你。

顺带说一句,Windows 版本有免费的代码签名,由 SignPath 提供。

藏在注释里的两段话

这份文档有两个 HTML 注释,写到了一种少见的程度。

第一段在开头,说 crates.io 的徽章先注释掉了——因为 shields.io 被 crates.io 限流,渲染出来是「CRATES.IO: INVALID」,等上游修好再恢复。

第二段在文末,解释 star-history 图表为什么要带一个 sealed_token:GitHub 把 stargazer 数据限制给仓库管理员和协作者,而 star-history 的服务器两者都不是,所以不带 token 就只能渲染一个「GitHub restricted access」的占位图。那个 token 是细粒度的,只够到这个仓库;它必须有写权限,因为 GitHub 把写权限当作协作者身份的证明。密文公开是安全的,真正的风险是 star-history 解密后持有一份能推送到这个仓库的凭证,直到 2027 年 8 月 16 日过期——那天图表会悄悄退回占位图,没有别的信号。

这段话如果没人看,它就白写了。但它还是写了,而且写得比很多安全公告都清楚。

一个项目愿意在注释里解释「我们借出了一把有时限的钥匙,以及为什么必须借」,这种透明度不太常见。

顺便说,「最流行」是它自己说的

文档顶上有一段 2026 年 11 月的更新:worktrunk 在今年年初发布,很快就成了最流行的 git worktree 管理器,并补了一句「是用心做的,没有 slop」。

这是项目自述,读的时候心里有个数。

但同一段里有句话更有意思:请把任何摩擦告诉我,哪怕是很小的摩擦或者一句不够好的提示语;对我帮助最大的,是大家把他们察觉到的问题贴出来。

「哪怕是很小的摩擦」——这句话比任何功能列表都更能解释这个项目为什么长这样。wt list 会告诉你隐藏了哪些列,wt merge 会解释为什么不需要 rebase,wt switch 会把三个动作合成一个。这些都是被摩擦磨出来的。

贡献指南里还专门开了一个 issue 模板,标题是「Worktree 摩擦:」,然后问三件事:哪个 worktree 相关的任务还是很痛、你现在怎么绕过去、理想方案是什么。

把「痛」做成一个工单类型,这个思路挺狠。

许可

MIT 或 Apache-2.0,双许可。你可以按自己项目的需要选一个。

最后

回到开头那个被埋没的功能。

git worktree 一直都在,能力也一直够用。它缺的不是功能,是一层把「路径」翻译成「名字」的界面——而这层界面,因为 agent 并行这件事,突然变得值得做了。

所以这个工具真正的贡献不是发明了什么,是承认了一件很朴素的事:一个好功能如果每次用都要多打两遍分支名,那它就会被闲置十年。

降低摩擦这件事,听起来不像技术,更像手艺。但很多工具的命运,就是被这两三行命令决定的。