发布于 2026-04-19 · 更新于 2026-04-20你是不是遇到过这种情况:正在
main 分支写一个功能,dev server 跑着、IDE 开了七八个标签、浏览器登着测试账号、AI 助手的上下文堆了半小时——这时候同事扔过来一个 PR 说”紧急 review 一下”。
git stash + git checkout 是教科书答案,但它会把你当前的整个工作环境连根拔起:文件变样、热重载抖动、IDE 标签内容全换、AI 上下文失真。等你 review 完切回来,光”热身”就要再花十分钟。
另一种做法是把同一个仓库 clone 好几份——project、project-2、project-hotfix……理解成本是零,但 .git 和 node_modules 在磁盘上复印了 N 次,fetch 要跑 N 遍,分支列表也各看各的。
git worktree 就是为这种场景准备的:一个 .git,挂多个工作目录。它不是新东西——Git 2.5(2015 年)就已经有了——但很多人听过名字、每次要用前犹豫一下”这玩意到底是我想的那样吗”,然后默默又多 clone 一份。这篇文章把物理模型讲清楚,顺带给一份可抄即用的实战流程。
物理模型:一个 .git + 多个工作目录
先把”一个 git 仓库 = .git 目录 + 工作目录”这句老生常谈掰开看:
.git是大脑:对象库(所有 blob/tree/commit)、分支引用、reflog、hooks,你的全部历史都在这里- 工作目录是当前快照:
git checkout做的事就是从.git里捞出对应 commit 的 tree,铺到工作目录变成文件。换句话说,工作目录里你看到的代码是”展开态”,.git里才是”压缩态”
关键细节:新 worktree 里 .git 是文件
这个点亲眼看一次就记住了。在新建的 worktree 里:
.git/ 里共享。
对应地,主仓库 .git/worktrees/<name>/ 会出现这个 worktree 的独立状态:它自己的 HEAD 指向哪、自己的 index 是什么、有没有被锁。共享对象库,独立 HEAD/index/工作文件——这就是 worktree 的全部精髓。
和 git checkout 的真正区别
概念上的区别背不下来,用一个场景讲清楚。
你正在 main 写 feature。Vite dev server 跑着,IDE 开着七八个标签,AI 助手 session 上下文堆了半小时,浏览器还登着测试账号。这时候 PR-42 要紧急 review。
选项 A:git checkout pr-42
- 未 commit 改动被拒绝,被迫
git stash - 工作目录整体换成 PR-42 的样子
- Vite 触发全量热重载,可能直接崩
- IDE 里七八个标签里的文件内容全变了——即使路径没变
- AI session 之前聊的”main 上 feature 的第三步”上下文瞬间失真
- review 完切回来:重新 stash pop、重新热重载、重新热身
git worktree add ../myapp.wt/pr-42 pr-42
- 主目录一个字节都没动
- 新建一个工作目录,在那里 checkout PR-42
- 另开一个终端、另开一个 IDE 窗口、另起一个 AI session 去新目录干活
- review 完
git worktree remove,主环境一直在原位 - 想参考主目录的代码?打开看,它就在那儿
checkout 是换衣服(身体只有一个,衣服一次只能穿一套),worktree 是克隆分身(本体不动,副本独立行动)。
这个区别是 worktree 最值得记住的事情。所有其他细节(共享对象库、.git 文件、命令 API)都是为了让这件事成立。
对比”多份 clone”:诚实的 tradeoff
很多人的第一反应是”多 clone 一份不就行了”。要诚实,不能一边倒:
一个合理的选择标准是:
- 同一个 repo 不同分支,预期几天到几周会合并回主线 → worktree
- 不同时期的 fork、或者已经长期分叉的 v1/v2 → 多 clone 没问题,别硬换
- 完全不同的项目(就算同名)→ 多 clone,没得商量
git fetch、多一个 node_modules、多一套要手工同步的本地配置。
必须知道的几条局限
worktree 不是银弹,这几条坑提前知道能少踩一脚:node_modules不能共享。前端项目这是最大的体积。pnpm 会把文件硬链到全局 store,所以多 worktree 的 node_modules 成本和多 clone 一样,不会变差——但也不会变好。如果期望 worktree 帮你省 node_modules 空间,会失望- 同一个分支同时只能被一个 worktree 签出。试图在第二个 worktree 再 checkout
main会被 git 直接拒绝:'main' is already checked out at ...。对应地,git branch -a输出里被占用的分支前面会显示+(而不是*) - 主 worktree 不能随便删。
.git/真身在主 worktree 里,删了主目录整个仓库就没了。删副 worktree 随便删 - 每个 worktree 是独立的工作环境。IDE 要单独打开那个目录;共用一个窗口在 worktree 之间跳来跳去是反模式,等于又回到了”一套身体换衣服”
- hooks 是共享的。因为它们住在主
.git/hooks/下,一套改动对所有 worktree 生效——想为某个副本单独关掉 pre-commit,只能在命令层用--no-verify绕过
核心命令(四条够用)
别被git worktree --help 的长列表吓到,90% 的场景这四条够:
lock / unlock / move / repair 这些真遇到场景再查不迟。
最常用的观察命令
真正开始用之后,你最常问的不是”能不能建”,而是:- 我现在这个目录到底在哪个分支?
- 哪个 worktree 对应哪个分支?
- 某个分支是不是已经被别的 worktree 占用了?
* 表示”当前目录正在这个分支上”,+ 表示”这个分支已经被其他 worktree checkout 住了”。日常记住一条就够:总表看 git worktree list,当前目录看 git branch --show-current。
三个最常见的创建姿势
实战流程:一个 hotfix 场景
一套可以直接抄的流程。假设你在~/projects/myapp 主目录的 main 分支写东西,测试账号登着、dev server 跑着,线上突然爆了个 bug。
命名约定:副 worktree 统一放在 <project>.wt/ 下,目录名和分支名保持一致——看一眼路径就知道自己在哪个分支,不用 git status。.wt/ 只是个习惯后缀,你写 -worktrees/ 或 .worktrees/ 都行,统一就好。
如果你打算把 worktree 直接放在仓库里面,比如:
.worktrees/ 被 .gitignore 忽略掉:
git status 会把整个 worktree 目录树都当成未跟踪文件,体验会非常差。
cd 过去继续,AI 的上下文还是旧的,IDE 的文件标签也还是旧的——那就失去了 worktree 最大的价值(环境隔离)。
前端彩蛋:两个 worktree 同时跑 pnpm dev 会抢同一个端口,用 PORT=3001 pnpm dev 解决。顺带说一句,.env.local 每个 worktree 独立(它不进 git),可以在不同 worktree 配不同的本地端口、本地数据库、feature flag,互不干扰——这反而是 worktree 优于单目录切分支的一个隐藏好处。
清理、误删和修复
worktree 真正容易把人绕晕的,不是创建,而是清理时”目录删掉了,但 Git 还记得它”。把这几种情况分开记:- 还在,想删 ->
remove - 已经被手动删了,Git 还记着 ->
prune - 没删,只是路径变了 ->
repair
git worktree remove,不要顺手 rm -rf。因为 remove 会检查未提交改动并拦住你,而手动删目录只会留下一个之后还得补救的烂尾状态。
速查索引
按最可能的检索路径排序:
写 feature、review PR、救火 hotfix、跑长实验——这些场景的共同特征都是”主环境不能动,但需要立刻在另一个分支干活”。worktree 的设计原语和它们完美契合:共享
.git 让多个目录天然同步分支和对象,独立 HEAD/index/文件让每个目录互不干扰。学会之后,stash + checkout 这一对组合拳会从你的日常词典里慢慢淡出——更少的上下文切换、更快的回到状态、更稳的多线程开发。