在 Magit 中使用 Git Worktrees

Hacker News Top 工具

摘要

本文介绍了如何在 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 bmagit-worktree-checkout在新的工作树中检出一个已存在的分支
Z cmagit-worktree-branch一步创建新分支和工作树
Z gmagit-worktree-status跳转到另一个工作树的状态缓冲区
Z mmagit-worktree-move移动一个工作树
Z kmagit-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 工作树——并且你是否像我一样以这种迂回的方式发现了它们?我很想在评论中听到你的经历!

今天就到这里。继续(并行地)编码吧!

相似文章

使用 Git worktree 轻松实现并行开发

Hacker News Top

文章介绍了 Git 的 worktree 功能,该功能允许开发者在不同的目录中同时处理多个分支,从而提升并行开发任务的工作流程效率。

Git worktrees 并非编码代理的隔离边界

Hacker News Top

本文解释了 Git worktrees 并未为 AI 编码代理提供真正的隔离,因为它们与父仓库共享钩子、配置和引用,从而允许代理在主机上执行任意代码。基准测试表明,适当隔离的克隆具有可比的性能,挑战了 worktrees 既隔离又廉价的观点。