单一checkout的瓶颈
我正在开发一个macOS菜单栏应用。我有三个功能在待办清单中:一个消耗量的迷你图表、原生通知和一个桌面小组件。这三个功能都是独立的。我打算用Claude Code来完成这三个功能。
问题是:Claude Code在一个目录中工作。一个目录有一个分支。而git checkout就像一个单车道的环岛:一次只能通过一个。
如果我想同时推进这三个功能,我的传统选择是:
Stash乒乓球:
git stash,切换分支,工作,git stash pop,祈祷没有冲突。重复直到发疯或退休,看哪个先到。克隆仓库三次:可以工作,但现在我有三个
.git/副本,三个独立的历史记录,每个都要执行git fetch。浪费。接受串行生活:一个功能接着一个功能。安全,可预测,但慢得像手动归并排序。
都不好。但有第四个选择,自2015年以来git就有了,但几乎没人使用。
Worktrees:你已经安装的解决方案
一个worktree是第二个工作目录,共享同一个.git仓库。没有副本,没有克隆,没有黑魔法。
比喻:你的仓库是一个图书馆。到目前为止你有一张桌子,只能打开一本书。worktree是放更多桌子。每张桌子都打开着不同的书,但都从同一个书架取书。
~/code/miapp/ ← 桌子1 (main)
.git/ ← 图书馆(只有一个)
~/code/miapp-sparkline/ ← 桌子2 (feature/sparkline)
.git ← 文件,不是文件夹(指向图书馆的指针)
~/code/miapp-notificaciones/ ← 桌子3 (feature/notifications)
.git ← 另一个指针
每个目录都是一个完整的checkout,包含所有文件。你可以在一个中编译,在另一个中运行测试,在第三个中让你的AI助手工作。同时进行。
创建只需一行命令
从你的主仓库:
| |
就这样。两个新目录,每个在自己的分支上,共享整个git数据库。不需要克隆,不需要配置远程,不需要复制历史记录。
它们共享什么,不共享什么
这很重要。worktrees共享整个仓库:提交、分支、标签、远程、钩子、配置。如果你在sparkline的worktree中做了一个提交,你可以立即从notifications的worktree中看到它,不需要fetch或任何操作,因为它们是同一个数据库。
它们不共享:
- 磁盘上的文件(每张桌子都有自己的工作副本)
- 暂存区(每个都有自己的
git add) - HEAD(每个指向自己的分支)
简单来说:“我正在处理什么"的状态对每个worktree是私有的。其他一切都是公共的。
与编码助手的工作流程
这里就变得有趣了。有了worktrees,你可以真正让几个助手同时在同一个项目上工作:
| |
每个Claude实例都有自己的目录、自己的分支、自己的.build/。它们不会冲突。不会竞争index。不需要stash任何东西。
由于它们共享git数据库,当其中一个助手完成并推送时,其他的就能看到那个分支。
合并:和往常完全一样
worktrees不会改变合并流程。它们是分离目录中的正常分支:
| |
完成后,清理:
| |
没人告诉你的坑
1. 一个分支,一个worktree
你不能同时在两个worktrees中checkout main。这是设计如此:避免两个目录修改同一个HEAD并损坏它。如果你需要main的第二个checkout,创建一个临时分支。
2. 第一次构建是从头开始
每个worktree都有自己的构建目录。第一次编译会很慢。之后,每个worktree维护自己独立的缓存,这正是相对于传统git checkout的优势(每次切换分支都会使缓存失效)。
3. 未跟踪的本地文件
你的.env.local、编辑器配置、不在git中的文件…不会被复制到新的worktree中。你需要重新创建它们或制作符号链接。
4. 在磁盘上有共享状态的应用
如果你的应用写数据到~/Library/Application Support/或类似的地方,来自不同worktrees的两个应用实例会竞争同一个文件。这不是worktree的问题,而是运行同一应用的两个实例的问题。解决方案:不同时运行两个,或者为每个构建参数化数据目录。
5. 不要手动删除目录
如果你使用rm -rf删除worktree而不是使用git worktree remove,git仍然认为分支被占用。执行git worktree prune来清理孤立的引用。
6. 远程仓库什么都不知道
worktrees是100%本地的。Gitea、GitHub、GitLab…没有远程知道它们的存在。它们只看到正常的git push和正常的分支。就像问你的服务器是否有你使用Vim或VS Code的问题:它不知道,不会影响它。
最佳实践
命名约定:将worktrees放在原仓库的同级目录,带有描述性后缀:
~/code/miapp/ ← main
~/code/miapp-sparkline/ ← feature
~/code/miapp-notifications/ ← feature
~/code/miapp-hotfix-login/ ← hotfix
这样ls ~/code/miapp*一下子就能看到所有的。
每个功能一个worktree,不是为了好玩:为真正要并行工作的任务创建worktrees。如果你要一个接一个地做事情,带有checkout的正常分支就足够了。
完成后清理:被遗弃的worktrees就像没人删除的分支——它们累积起来会造成混乱。git worktree list是你的朋友。
不要从两个worktrees编辑同一个文件:技术上你可以,每个都有自己的副本。但如果两个都修改同一个文件,合并时会有冲突。尝试让功能接触代码的不同区域。
完整工作流程建议
对于那些想要有序工作流程的人,这是我使用的:
| |
循环是:创建 → 并行工作 → push/PR → 合并 → 清理。每个worktree的生命周期和功能的生命周期一样长,不多不少。
总结
worktrees从git 2.5版本(2015年7月)就有了。超过十年了。大多数人仍然在做git stash,就像我们还在2010年一样。
随着编码助手的到来,瓶颈不再是你写代码的速度——而是你切换上下文的速度。而worktrees完全消除了这种上下文切换:你不切换分支,你切换目录。cd而不是checkout。
这终究是我们一直应该做的。
总结:git worktree add ../名称 -b 分支在同一个仓库上创建第二个工作目录。没有副本,没有stash,不使缓存失效。完美适用于让多个编码助手并行工作。完成时用git worktree remove清理。