git worktree 解决的是一个很具体的问题:同一个仓库,想同时有多份可以编辑的工作目录,而不想每次都重新 clone 一份完整仓库。
它看起来像一个独立项目目录,但本质上不是完整副本。worktree 共享同一个仓库后端,只是多了一份前台工作目录。这一点很重要,因为很多“为什么我明明在临时目录里改代码,却还是卡在主仓库 .git 里”的问题,都来自这里。
一句话概括:
worktree 是共享同一个 Git 仓库后端的多份前端工作目录。从普通仓库开始#
最常见的 Git 仓库结构很简单:
/Users/d/code/project
├── .git/
├── app/
├── package.json
└── README.md这里 .git/ 是真正的仓库后端,里面保存提交对象、分支引用、索引、锁文件等元数据。工作目录里的文件是你正在编辑的内容。
平时执行这些命令:
git status
git add .
git commit -m "Update"Git 既会读取工作目录里的文件,也会写 .git/ 里的索引和元数据。
worktree 多出来的是什么#
git worktree 允许同一个仓库额外挂出另一个工作目录,例如:
git worktree add /private/tmp/project-fix feature/fix这时会出现一个新的目录:
/private/tmp/project-fix
├── .git
├── app/
├── package.json
└── README.md注意这里的 .git 通常不是一个目录,而是一个文本文件,里面指向主仓库下面的 worktree 元数据:
gitdir: /Users/d/code/project/.git/worktrees/project-fix也就是说,代码文件在 /private/tmp/project-fix,但 Git 需要写入的索引、HEAD、锁文件等内容,仍然挂在主仓库的 .git/worktrees/... 下面。
它和 clone 的区别#
普通 clone 是完整复制一份仓库:
/tmp/project-copy
└── .git/这份 clone 有自己的 .git/,提交时写自己的索引和锁文件。它和原仓库之间只通过 remote 同步。
worktree 不是这样。worktree 更像同一个仓库伸出去的另一只手:
/Users/d/code/project/.git/
├── objects/
├── refs/
└── worktrees/
└── project-fix/不同 worktree 可以有不同工作目录、不同 HEAD、不同索引,但底层对象库和主仓库关系仍然绑在一起。
这就是它高效的地方:不用重新 clone,不用复制完整历史,切换和并行开发都很快。
这也是它容易误判的地方:目录看起来隔离了,但 Git 元数据没有完全隔离。
worktree 和分支的区别#
worktree 和分支不是同一层东西。
分支是 Git 里的一个名字,指向某个提交。比如:
master -> 521b9296
feature/a -> abc1234它本质上是“这条开发线现在停在哪个 commit”。切换分支时,Git 会把同一个工作目录里的文件切到另一个提交状态。
worktree 是一个真实的工作目录。它让同一个仓库同时展开多个目录,每个目录可以 checkout 不同分支或不同 commit。
例如:
/Users/d/code/project # 主工作目录,在 master
/private/tmp/project-feature-a # 另一个 worktree,在 feature/a一个简单类比是:
分支 = 书签,指向故事的某一章
worktree = 打开的书桌,可以把某个书签对应的内容摊开来改常见组合是:
git branch feature/a
git worktree add /private/tmp/project-feature-a feature/a这表示:先创建一条开发线 feature/a,再给它打开一个独立目录。分支负责记录提交线,worktree 负责提供文件现场。
也可以没有分支,直接让 worktree 停在某个 commit 上:
git worktree add --detach /private/tmp/project-review 521b9296这种 detached worktree 适合只读检查、临时构建、对比旧版本。它没有分支名字跟着移动,如果在里面提交,后续要额外小心把提交挂回某个分支,否则容易变成“我记得改过,但不知道挂在哪”的状态。
简单对比:
| 项目 | 分支 | worktree |
|---|---|---|
| 是什么 | commit 指针 | 文件目录 |
| 存在哪里 | Git 元数据里 | 文件系统里 |
| 主要解决什么 | 保存一条开发线 | 同时展开多个文件现场 |
| 能不能直接编辑文件 | 不能 | 能 |
| 能不能没有分支 | 不适用 | 可以 detached HEAD |
| 删除影响 | 删除指针,不删目录 | 删除目录,不一定删分支 |
什么时候用分支,什么时候用 worktree#
如果只是要做一条新的开发线,用分支就够了:
- 新功能或修复要有独立提交历史。
- 需要开 PR。
- 当前工作目录是干净的,切分支不会打断别的事情。
例如:
git switch -c feature/search如果你需要“同时保留两个文件现场”,再用 worktree:
- 当前目录正在跑 dev server,不想停掉。
- 当前目录有没提交的改动,不想 stash。
- 要同时看
master和另一个分支。 - 自动化要在隔离目录里改文件,避免碰用户当前 checkout。
- 大仓库重新 clone 太慢,只想复用已有对象库。
例如:
git worktree add ../project-hotfix -b hotfix/login master这会从 master 新建 hotfix/login 分支,并在 ../project-hotfix 打开它。
如果要完全隔离 Git 元数据,尤其是自动化要自己 commit / push,又不确定主仓库 .git/worktrees/... 是否可写,用独立 clone 更稳:
git clone git@github.com:owner/project.git /private/tmp/project-submit判断标准可以粗暴一点:
- 只需要一条提交线:用分支。
- 需要两个目录同时存在:用 worktree。
- 需要连 Git 元数据、锁文件、权限都隔离:用独立 clone。
什么时候适合用 worktree#
worktree 适合这些场景:
- 同时处理两个分支,不想频繁 stash 或切分支。
- 一个目录跑长期 dev server,另一个目录临时修 bug。
- 自动化任务需要一份临时工作目录,避免直接污染用户当前 checkout。
- 大仓库 clone 成本高,只想复用本地对象库。
例如:
git worktree add ../project-review origin/main
git worktree add ../project-hotfix hotfix/login这样可以同时保留多个分支的文件状态,互不覆盖工作目录。
为什么自动化会被 index.lock 卡住#
这类错误一般长这样:
fatal: Unable to create '/Users/d/code/project/.git/worktrees/project-fix/index.lock': Operation not permitted表面上看,自动化正在 /private/tmp/project-fix 或 .codex/worktrees/... 这样的临时目录里工作。直觉上会以为只要这个临时目录可写,就能提交。
但 worktree 的提交路径不是只写临时目录。执行 git add 时,Git 要更新这个 worktree 对应的 index,也就是主仓库 .git/worktrees/project-fix/index。为了避免并发写坏索引,Git 会先创建 index.lock。
如果运行环境只能写临时工作目录,不能写主仓库 .git/worktrees/...,提交就会失败。
这不是文件内容冲突,而是 Git 元数据写权限问题。
怎么判断当前目录是不是 worktree#
可以看 .git:
cat .git如果输出类似这样:
gitdir: /Users/d/code/project/.git/worktrees/project-fix当前目录就是 worktree。
也可以看 Git 自己解析出来的路径:
git rev-parse --git-dir
git rev-parse --git-common-dir如果 --git-dir 指向 .git/worktrees/...,而 --git-common-dir 指向主仓库 .git,说明它不是完全独立 clone。
怎么避免自动化被卡住#
如果自动化只需要读文件、跑测试、生成 diff,worktree 很合适。
如果自动化还要自己 commit 和 push,要先确认两件事:
- worktree 的
git-dir可写。 - 主仓库的
.git/worktrees/...可写。
更稳的做法是:把 worktree 当成读写文件的隔离层,把最终提交放到临时独立 clone 里做。
流程可以是:
1. 在 worktree 中完成修改和验证
2. 如果 git 元数据不可写,创建 /private/tmp 下的临时 clone
3. 只把本次相关文件同步到临时 clone
4. 在临时 clone 中 commit / push这样提交时写的是临时 clone 自己的 .git/,不会碰主仓库 .git/worktrees/.../index.lock。
总结#
worktree 的价值是让同一个仓库拥有多份工作目录,适合并行开发、临时检查和自动化隔离。
但它不是完整 clone。它隔离了工作目录,没有完全隔离 Git 元数据。只要提交路径还要写主仓库 .git/worktrees/...,权限、锁文件和并发状态就仍然会影响它。
判断 worktree 时不要只看“文件是不是在临时目录”。真正要看的是:
git rev-parse --git-dir
git rev-parse --git-common-dir如果要让自动化稳定提交,worktree 之外最好准备一条独立 clone 的 fallback 路径。
参考#
git help worktreegit help rev-parse