第一次用 git worktree,连踩了三个坑(附无痛清理姿势)

🚀 省流助手

  • 现象 :想给项目开个隔离副本做重构,跑了 git worktree add .worktrees/xxx -b refactor/yyy,后悔后 rm -rf 干掉,发现 git worktree list 还残留、新分支还在、.gitignore 还脏
  • 根因 :worktree 元数据存在主仓 .git/worktrees/ 里,手删工作目录 git 不会自动同步;-b 创建的分支也是独立产物,不会跟着 worktree 一起销毁
  • 解决 :worktree 路径优先放在仓库同级目录 省心;意外搞砸了用 git worktree prune -v + git branch -D <name> 一把清干净

故障现象

需求:给一个 monorepo 开个隔离工作区,做两期重构(替换两个子包的 core 依赖),不想动主分支。

第一反应敲:

bash 复制代码
cd ~/projects/your-app
git worktree add .worktrees/refactor-core-swap -b feat/topic-x

很快后悔------.worktrees/ 在仓库里,git status 看到 untracked,要不要加 .gitignore?要不要 commit 一个 chore?感觉脏,干脆 rm -rf .worktrees,把 .gitignore 改的也撤掉。

理论上应该回到原状,但是:

bash 复制代码
$ git worktree list
/Users/<user>/projects/your-app  abc1234 [dev]

$ git --no-pager branch
  feat/feature-x
  main
* dev
  feat/topic-x          ← 这玩意还在
  wip/scratch
  release

$ git status
On branch dev   ← 怎么还显示脏(提示符里有 !)

看起来像是删了,但又没完全删------这种"半残留"是最难受的。


排查路径

假设 1:rm -rf 把目录干掉应该就够了?

直觉是这样,验证:

bash 复制代码
ls .worktrees           # 不存在
git worktree list       # 仍可能列出已删 worktree(标 prunable)

不同 git 版本对此行为不一致------有的会保留 prunable 条目,有的 list 时自动检测。但元数据目录 .git/worktrees/<name>/ 大概率还在,这才是关键残留。

假设 2:-b 顺带创建的分支会跟 worktree 一起死吗?

不会。git worktree add <path> -b <branch> 本质是两步合一:

  1. git branch <branch> 在主仓里建分支
  2. git worktree add <path> <branch> 把它 checkout 到指定路径

删 worktree 只撤回第二步,分支照样躺在主仓。

bash 复制代码
git --no-pager branch
# 输出里仍能看到 feat/topic-x

假设 3:.gitignore 那个改动没 commit,怎么撤?

git status 看修改范围,未 commit 的话直接:

bash 复制代码
git restore .gitignore

回到 HEAD。

假设 4:worktree 命令必须在主仓跑吗?

我一度以为是。验证发现并不是------git worktree 所有子命令读的是共享元数据 ,主仓和任何 linked worktree 内执行效果一致。唯一例外:你不能 remove 自己当前所在的那个 worktree。


关键证据

清理前 git worktree list 的输出(关键 turning point):

javascript 复制代码
/Users/<user>/projects/your-app  abc1234 [dev]
/Users/<user>/projects/your-app/.worktrees/refactor-core-swap  abc1234 [feat/topic-x]

rm -rf .worktrees 之后再跑 git worktree list,第二行消失标记为 prunable ------表面看起来"清"了,但 .git/worktrees/refactor-core-swap/ 元数据目录还在硬盘上。这就是所有困惑的源头:git 的 worktree 状态不是看磁盘,是看主仓元数据


根因

可以一句话总结:

worktree 是 main 仓库 .git/worktrees/<name>/ 元数据 + 一个 working tree 文件夹的组合体。手删后者 git 不会自动清前者。-b 顺带建的分支是独立产物,跟 worktree 没有隶属关系。

展开三点:

1. linked worktree 的 .git 是文件不是目录

进 worktree 看:

bash 复制代码
$ cat .worktrees/xxx/.git
gitdir: /Users/<user>/projects/your-app/.git/worktrees/xxx

这就是为什么所有 worktree "共享"主仓数据------它们的 git 目录都指回主仓那一份。

2. 元数据残留是设计而非 bug

git 故意不在 rm 时联动清理,因为它无法区分"用户是想删除"还是"目录被外部进程暂时移走"(如挂载点暂时不可达)。所以清理需要显式指令:prune

3. -b 是糖语法

git worktree add <path> -b <branch>

bash 复制代码
git branch <branch>
git worktree add <path> <branch>

删 worktree 只逆掉后一步。前一步建的分支仍在 main 仓的 refs/heads/ 下,得 git branch -D 显式清。


解决方案

临时救火(已经搞砸了)

三条命令清干净:

bash 复制代码
cd ~/projects/your-app          # 主仓也行,在哪个 worktree 里都行
git worktree prune -v           # 清掉已删目录的元数据
git branch -D feat/topic-x # 清掉 -b 顺带创的分支
git restore .gitignore          # 撤销未 commit 的 .gitignore 改动

# 验证回到原状
git --no-pager worktree list    # 应只剩主仓一行
git --no-pager branch           # 不应再有那个分支
git status                      # nothing to commit, working tree clean

如果 .gitignore 已 commit 但还没 push:

bash 复制代码
git reset --hard HEAD~1         # 撤回那一笔

永久方案(一开始就避免)

worktree 放在仓库同级目录 ,命名 <repo>-<branch-slug>

bash 复制代码
cd ~/projects/your-app
git worktree add ../your-app-feat-topic-x -b feat/topic-x

cd ../your-app-feat-topic-x
pnpm install                    # 装依赖(pnpm workspace 在该 worktree 根执行)

好处:

  • 主仓 git status 看不见它
  • 不需要改 .gitignore,没有 chore commit 干扰提交历史
  • IDE 文件树里同级展示,"切窗口"就是切上下文
  • 删除时也省心:git worktree remove ../your-app-feat-topic-x 自动清元数据

预防建议

写成清单贴到 ~/.claude/CLAUDE.md 或团队 README,新人不再踩:

场景 该怎么做
想隔离工作区 路径首选仓库同级目录 ,命名 <repo>-<branch>
必须放仓库内 先把目录加 .gitignore 并 commit,再 worktree add
创建带新分支 git worktree add <path> -b <branch>(一步到位)
创建复用现有分支 git worktree add <path> <existing-branch>(不带 -b
删除 worktree git worktree remove <path> 手动 rm -rf
顺手用了 -b 删 worktree 后记得 git branch -d/-D <branch>
手动删了想补救 git worktree prune -v + git branch -D <branch>
不知道在哪跑 prune 任意 worktree 内都行,不挑位置

知识点提炼

主 worktree vs linked worktree

  • main worktree :第一次 clone 出来的目录,里面是真正的 .git/ 目录
  • linked worktree :用 git worktree add 创建的额外副本,里面是 .git 文件(gitfile pointer)

linked worktree 共享主仓的 object database、refs、config,不会重新 clone 一份对象,磁盘开销极小。这也是 worktree 比 clone 便宜的核心原因。

git rev-parse 判定自己在哪

写脚本想区分主仓 vs linked worktree?两个变量一比就知道:

bash 复制代码
GIT_DIR=$(git rev-parse --git-dir)            # 当前 worktree 的 git 目录
GIT_COMMON=$(git rev-parse --git-common-dir)  # 主仓共享的 git 目录

# 主仓中:两者相同
# linked worktree 中:GIT_DIR 是 .git/worktrees/<name>/,GIT_COMMON 是主仓的 .git/

为什么 git add . 会对内嵌 git 仓库报警告

如果 worktree 目录在主仓内、且没 ignore,git add . 会触发:

bash 复制代码
warning: adding embedded git repository: .worktrees/xxx

这个警告是说:"该目录里有 .git 文件,git 当它是嵌入式仓库(类似 submodule),会以 gitlink 形式添加而不是文件内容。"

提交进去的是一个指向某个 commit 的引用,不是文件本身。这种状态非常容易让 PR diff 看不懂,所以 worktree 一定要么 ignore 要么放仓库外。


可能你已经习惯了为每个 feature 拉新分支然后切来切去------但当你需要两个分支同时编辑 (比如对照参考实现写新代码、或一边跑测试一边修主线 bug),worktree 才是正确解法。git checkout 切换分支会污染 IDE 状态、缓存、未 push 的草稿;worktree 直接给你两份独立工作区,互不打扰。

第一次用难免踩坑,但理顺这套心智模型之后,clone-and-cd 的旧习惯就再也回不去了。

相关推荐
spmcor5 小时前
解决 Git 中已跟踪目录无法被 .gitignore 忽略的问题
git
qcx236 小时前
【AI Engineering · Harness 系列】02 确定性外壳 × 非确定性内核——git push 红线的故事
人工智能·git·prompt·agent·engineering·harness
水云桐程序员6 小时前
10 分钟 Git 上手教程
git
Dontla7 小时前
Git三个主要区域介绍(工作区Working Directory、暂存区Index/Staging Area、仓库区Repository)
git
她说可以呀7 小时前
git在Ubuntu的下载和配置用户
git·ubuntu
隔窗听雨眠8 小时前
Git二分法精准定位Bug
git·bug·git bisect
weixin_704266059 小时前
IDEA 整合 Git 并上传代码到 CSDN GitCode 超详细教程
git·intellij-idea·gitcode
芝士就是力量啊 ೄ೨10 小时前
Git使用教程(如何使用VSCode+Git+Gitee对项目进行版本控制)
git·vscode·gitee
OYangxf10 小时前
Git工作流用法
git