你是不是遇到过这种情况:正在Documentation Index
Fetch the complete documentation index at: https://adonis-til.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
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 一份不就行了”。要诚实,不能一边倒:| 维度 | 多份 clone | worktree |
|---|---|---|
| 理解成本 | 零,所见即所得 | 要懂”共享 .git + 独立工作目录” |
| 磁盘占用 | 每份一个 .git(MB~GB)+ 每份 node_modules | 共享 .git,每份独立 node_modules |
fetch / pull | 各自要跑一遍 | 一次 fetch,所有 worktree 都能看到新分支和新 commit |
| 分支可见性 | 各看各的本地分支列表 | 共享分支列表(在哪个目录看都一样) |
| 误删保护 | 没有——rm -rf 就没了 | git worktree remove 会检测未 commit 改动并拦下 |
| 适合场景 | 永久并行:v1/v2 大版本、主仓库和长期 fork | 短期并行:多分支切换、PR review、hotfix |
- 同一个 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 会检查未提交改动并拦住你,而手动删目录只会留下一个之后还得补救的烂尾状态。
速查索引
按最可能的检索路径排序:| 想查 | 看哪节 |
|---|---|
| 命令怎么写 | 核心命令 |
| 怎么看 worktree 对应哪个分支 | 最常用的观察命令 |
| 新分支 / 旧分支 / 临时实验怎么建 | 三个最常见的创建姿势 |
| 路径怎么起名 | 实战流程 |
仓库内建 .worktrees/ 要注意什么 | 实战流程 开头的 .gitignore 提醒 |
| 值不值得用 | 对比”多份 clone” |
| 和 checkout 什么区别 | 和 git checkout 的真正区别 |
遇到”already checked out”或奇怪的 + | 必须知道的几条局限 |
| 不小心手动删了目录怎么办 | 清理、误删和修复 |
| 两个 dev server 打架 | 实战流程 末尾彩蛋 |
.git 让多个目录天然同步分支和对象,独立 HEAD/index/文件让每个目录互不干扰。学会之后,stash + checkout 这一对组合拳会从你的日常词典里慢慢淡出——更少的上下文切换、更快的回到状态、更稳的多线程开发。