在 Magit 中使用 Git Worktrees
摘要
本文介绍了如何在 Magit 中使用 Git worktrees 进行并行开发,讨论了其相对于传统分支的优势,并提及了 AI agent 应用和 Jujutsu 版本控制系统。
查看缓存全文
缓存时间: 2026/09/11 08:46
在 Magit 中使用 Git 工作树
来源:https://emacsredux.com/blog/2026/09/02/working-with-git-worktrees-in-magit/ 我承认,直到相当近期,我都不知道 Git 工作树的存在。它已作为 Git 的一部分存在了十年1 (https://emacsredux.com/blog/2026/09/02/working-with-git-worktrees-in-magit/#fn:1),而我从未需要过它。功能分支完全能满足需求——创建分支、进行工作、合并、删除,然后重复。
最终让我接触到工作树的,恰恰是 AI 编程代理。像 Claude Code 这样的工具会为每个任务创建一个工作树,这样多个代理(或同一代理的多个会话)就可以在同一个仓库中并行工作,而不会相互干扰——也不会干扰你。突然间,我的项目目录里充满了 cider-this 和 cider-that 这样的同级目录,我想我应该弄清楚那里实际发生了什么。
工作树 vs 分支
分支只是指向提交的可移动指针,创建分支基本是免费的。问题在于,一个仓库只有一个工作目录,因此处理两个分支意味着来回切换目录。你熟悉这个流程:暂存你做到一半的工作(或者提交它),检出另一个分支,完成任务,再次检出第一个分支,取消暂存。这可行,但很繁琐,而且当两个分支使你的项目处于不同的构建状态,每次切换都需要重新编译半个世界时,情况会更糟。
工作树为你提供了一个附加在同一个仓库上的额外工作目录:
$ git worktree add ../cider-smart-targeting -b smart-form-targeting
现在 ~/projects/cider-smart-targeting 是该分支的一个完整检出,而你的主检出保持原位。对象数据库、引用、暂存和远程仓库都是共享的——工作树不是克隆,所以在一个工作树中获取更新就是在所有工作树中获取更新,创建工作树几乎瞬时完成。每个工作树都有自己的 HEAD 和索引,Git 强制执行一个简单规则:一个分支一次只能在一个工作树中被检出。
什么时候值得使用?当需要同时处理两件事时:在一个分支上运行长时间测试,同时你在另一个分支上工作;审查一个 PR 而不干扰你做到一半的工作;或者——这是如今大家都在谈论它的原因——AI 代理在隔离环境中执行其任务。代价相当小:你的工作文件会在磁盘上存在多次,而任何未被 Git 跟踪的内容(依赖项、构建缓存、node_modules 及其同类)都必须在每个工作树中重新设置。
如果分支从未让你感到限制,那也没问题——对我来说,在十多年的时间里也是如此。工作树是你直到工作流程改变才会怀念的那类功能之一。
那么 Jujutsu 呢?
既然谈到工作副本——当前版本控制领域最有趣的进展是 Jujutsu (https://jj-vcs.github.io/jj/) (jj),一个兼容 Git 的版本控制系统,它使得工作树试图解决的问题基本消失。在 jj 中,工作副本就是一个提交,在你工作时自动快照。没有暂存区,也没有暂存,因为没有可能丢失或妨碍工作的未提交状态——切换上下文总是安全的。它还对并行工作目录(jj workspace)有适当支持,并有一个操作日志,使几乎所有操作都可撤销。后一点也是 AI 代理群体对其产生兴趣的部分原因。
你可以在现有的 Git 仓库上使用 jj(他们称之为共存仓库),你的同事——以及 Magit——将继续看到一个正常的 Git 仓库。我目前还只是个观察者,但这显然是一个值得关注的项目。
回到 Emacs 领域。Magit 多年前就支持了工作树,隐藏在 Z 键后:
| 按键 | 命令 | 描述 |
|---|---|---|
Z b | magit-worktree-checkout | 在新的工作树中检出一个已存在的分支 |
Z c | magit-worktree-branch | 一步创建新分支和工作树 |
Z g | magit-worktree-status | 跳转到另一个工作树的状态缓冲区 |
Z m | magit-worktree-move | 移动一个工作树 |
Z k | magit-worktree-delete | 删除一个工作树 |
最棒的是没有其他需要学习的东西。每个工作树都有自己的 Magit 状态缓冲区,每个 Magit 命令都在当前缓冲区所属的工作树上操作。Z g 是你唯一需要的切换机制,即使它也只是一个访问另一个状态缓冲区的快捷方式。
根据我(诚然是近期的)经验,这里有一些实用建议:
- 默认情况下,状态缓冲区不会列出你的工作树。通过以下方式修复:
(magit-add-section-hook 'magit-status-sections-hook
#'magit-insert-worktrees
nil t)
现在每个状态缓冲区都会显示仓库的所有工作树,你可以对其中任何一个按 RET 跳转过去。
- 创建工作树作为主检出目录的同级目录,并使用描述性名称(
cider-smart-targeting旁边是cider),而不是嵌套在其中——嵌套会混淆grep、find和许多其他工具。 - Magit 的分支选择会在其他工作树中检出的分支上标注工作树路径,并拒绝再次检出它们(这是 Git 的规则,不是 Magit 故意刁难)。如果你曾想知道为什么某个分支“无法检出”,通常这就是原因。
- 就
project.el(或 Projectile)而言,每个工作树都是一个独立的项目,因此项目切换、按项目缓冲区和搜索都能自然工作。 - 任何未被 Git 跟踪的内容都不会随之而来。你必须为每个工作树安装一次依赖项,并从一个冷的构建缓存开始。对于 Elisp 这来说成本为零;对于大型 JVM 或 JS 项目,这是整个方法的主要缺点。
- 当你完成一个工作树时,使用
Z k(或从命令行使用git worktree remove)删除它。如果你只是手动删除目录,git worktree prune会清理遗留的簿记信息。
总结思考
如今,我与代理的工作流程通常是这样的:代理在一个工作树中完成其工作,我在 Magit 中审查那里的更改(通常是在另一个任务在第二个工作树中运行时),一旦分支合并,工作树就会消失。也许有一天我会发现工作树的其他用途——我们拭目以待。
你是否使用 Git 工作树——并且你是否像我一样以这种迂回的方式发现了它们?我很想在评论中听到你的经历!
今天就到这里。继续(并行地)编码吧!
相似文章
@github: Git worktrees自2015年以来就已存在,但如今它们重新兴起。@cassidoo解释了它们是什么以及为什么AI时代……
Git worktrees(Git工作树)是自2015年起就存在的功能,随着AI时代并行工作的需求增加,它们正变得越来越受欢迎。本文解释了工作树是什么、它们与分支的区别,以及为什么它们能减少开发者的上下文切换开销。
使用 Git worktree 轻松实现并行开发
文章介绍了 Git 的 worktree 功能,该功能允许开发者在不同的目录中同时处理多个分支,从而提升并行开发任务的工作流程效率。
@realchendahuang:几天前,我发布了一篇关于 Git Worktree 的讨论,评论区有很多具体的经验分享……
本文整理了 Git Worktree 讨论中的经验,分享了高效分支管理、自动化脚本和辅助工具的策略,以增强并行开发工作流。
Git worktrees 并非编码代理的隔离边界
本文解释了 Git worktrees 并未为 AI 编码代理提供真正的隔离,因为它们与父仓库共享钩子、配置和引用,从而允许代理在主机上执行任意代码。基准测试表明,适当隔离的克隆具有可比的性能,挑战了 worktrees 既隔离又廉价的观点。
@mogician301: https://x.com/mogician301/status/2071841989751112147
文章介绍了 git worktree 在 AI 时代进行多任务并行开发的优势,以及作者开发的 cc-launch 工具如何通过自动初始化、统一管理等解决 worktree 的使用痛点,提升开发效率。