乐于分享
好东西不私藏

探索Git Worktree:AI编码代理在软件开发中的应用

探索Git Worktree:AI编码代理在软件开发中的应用

Agent Coding正在改变我们构建软件和使用git Worktree的方式,事实证明它是并行运行它们的缺失环节。

方法。首先结构化。

最近,我在开发一个用于计量和基于使用的计费的开源平台。我们用React构建前端,Rails作为API后端,整个系统采用事件驱动架构。这不是一个可以随意对待代码库的项目,因此,我逐渐形成了一套工作流程,它帮助我保持进度并提高效率。以下是我围绕三个步骤组织工作的方法,至少对我个人而言,它们非常有效:

步骤 1:技术解决方案。 在接触任何代码之前,我会写下需要构建的内容。不是小说,只是足够清晰地说明架构和关键决策。这是实际需要思考的部分。在这里出错,下游的一切都会受到牵连。

步骤 2:垂直工单分解。 技术解决方案被拆分成工单。这些工单并不是为了让项目经理感觉良好。它们是包含架构推理、边缘情况和足够上下文的密集技术文档,以至于一个Agent可以拿起一个工单并真正地运行。大小取决于复杂性:有时是1个工单,有时是8个。

步骤 3:Claude Code 作为执行者。 每个工单都提交给Claude Code,同时技术解决方案作为背景上下文。一个Agent,一个工单,一个明确定义的范围。范围越窄,Agent的表现越好。

到目前为止非常简单。很多人在做这个版本的一些工作。

瓶颈。一次一个工单。

这就是开始崩溃的地方。

一旦我把所有工单排好,我就可以在我面前看到整个功能,因为每个工单都是自包含的,所以没有理由它们需要顺序运行。问题是git只允许你在单个目录中一次处理一个分支,所以实际上你被困在了一个接一个地处理工单。

对我来说,变通方法并不是很有效:

  • 多次克隆仓库: 一开始有效,但很快会浪费磁盘空间,并且变得混乱不堪
  • git stash + 手动检出: 脆弱、容易出错,而且你还是在切换上下文,而不是并行运行
  • 第三方工具: 额外的依赖增加了复杂性,却未能解决核心问题

在真正的产品设置中,情况会变得更糟。如果你的特性涉及到多个仓库,或者依赖于另一个服务中仍在进行的分支,手动协调这一切确实非常糟糕。

解决方案:git worktree。

这就是git worktree的用武之地。它在2015年7月推出,坦白说我从未真正探索过它。以前没有合适的理由,这是我的错。但是多Agent用例使它变得实际上必不可少。

这个想法很简单:你不需要多次克隆仓库,而是可以拥有多个工作目录,它们都指向同一个.git对象。每个目录都位于自己的分支上。没有重复,所有的Worktree共享同一个远程仓库,整个操作只需要一个命令。

基本流程

cd ~/projects/my-app

# 从史诗分支开始,为并行票创建一个Worktree
git worktree add -b feature/dashboard-filters ../my-app-filters feature/dashboard

cd ../my-app-filters

# 处理票
git add .
git commit -m "feat: add date range filter to dashboard"
git push -u origin feature/dashboard-filters

# 返回主工作
cd ../my-app

# 一旦票被合并
git worktree remove ../my-app-filters

你可以从feature/dashboardmain或任何其他分支分支出来。当票共享相同的基础时,最终的合并往往很干净。如果两个票触及相同的文件,冲突仍然可能发生,但至少你可以预见到它们。

为什么这对多Agent设置很重要

每个Agent都有自己的Worktree。没有相互干扰,没有共享状态,没有Agent撤销另一个Agent刚刚做的事情。除此之外:

  • **真正的并行性:**多个终端,多个Agent,每个专注于一件事
  • **独立的测试:**在合并任何内容之前,单独启动每个功能
  • **更清晰的PRs:**每个PR的范围都很紧密,这使得审查实际上可读

超越基础。编排一个真正的架构。

上述简单案例对于平面项目或没有外部依赖的单体仓库非常有效。

真正的架构并不温和。

在Lago,我们有一个React前端,一个Rails API,一个数据库,Redis等等都在发挥作用。每个Worktree不仅仅是一个分支。它是一个环境。它需要自己的端口,它需要知道要与哪些服务通信,它需要与同时运行的其他环境共存而不发生冲突。

lago-worktree。一个bash脚本来编排一切。

我的主要职责是前端,所以我构建了一个bash脚本来处理从那一层开始的编排。这个想法很简单:每个工单都有自己的Worktree,自己的端口,自己的隔离环境。如果一个工单只涉及UI,脚本会在一个空闲端口上创建一个新的前端Worktree,并指向主堆栈中已经运行的API。如果工单同时涉及前端和API,它会为每个创建专用的Worktree。无论是哪种命令。一旦每个Worktree都启动了,我可以为每个分配一个Agent,让它们并行工作,每个都专注于自己的工单,而不会相互干扰。

仅限前端的工单:

lago-worktree create feat-01
# 前端可在 http://localhost:3001 上访问
# API与主堆栈共享在 :3000 上

同时涉及前端和API的工单:

lago-worktree create feat-02 --from-front=feat/ui --from-api=feat/endpoint
# 前端可在 http://localhost:3002 上访问
# 专用API在 http://localhost:4001 上

查看正在运行的内容:

lago-worktree ps
# 名称      前端                           API                             状态
# feat-01   http://localhost:3001 [main]    共享                           运行中
# feat-02   http://localhost:3002 [feat/ui] http://localhost:4001 [feat/...] 运行中

完成后清理:

lago-worktree destroy feat-02
# 移除前端Worktree的分支和目录
# 移除APIWorktree的分支和目录

前端和API每个工单都有自己的独立实例。数据库、Redis和事件流层在所有Worktree之间保持共享,因为每个Agent都针对相同的数据工作,无需复制它们。这保持了资源使用的低消耗和快速的设置。如果需要为每个Worktree隔离其他层,脚本设计为可扩展:只需添加该层的新标志,编排将处理其余部分。

并行工作有效。我不行。

现在,我有多个独立的完整架构实例并行运行,每个实例都有一个专用的Agent处理其工单。

接下来是什么? 好吧,现在瓶颈是我。
Agent以我无法跟上的速度产生代码,当涉及到审查代码并确保它实际上与解决方案匹配时。系统越高效,这种差距就越明显。

还有一个更大的转变值得一提:我几乎不再编写代码,而是开始指导代码、上下文、架构、目标,而Agent负责执行。那可能是件好事,但随之而来的是一个陷阱:产出的质量直接与我的思考质量相关。一个模糊的工单会产生模糊的代码,一个错误的假设会被完美地实现。屎山代码与Agent一起,这种情况发生得比以往任何时候都快。

我如何解决这个问题?我还不知道。但这是值得拥有的那种问题。