
把一个 linked worktree 目录直接删掉,磁盘上看似已经收干净,回到主仓库执行 git worktree list,那条工作树记录却还在。Git 记住的不只是目录本身:每个 linked worktree 还有一份放在主仓库管理区里的登记。目录消失后,登记不会自动跟着被文件管理器删除。
目录不在,登记仍在
linked worktree 的工作目录有自己的文件,但 .git 通常是一个指向主仓库内部管理记录的文件。主仓库会在 .git/worktrees/ 下保留对应的元数据。手动删目录时,这两处不是同一个对象:工作目录被移除,主仓库里的管理记录没有被触碰。
我在临时仓库里创建一个 detached worktree,再用 Python 删除它的目录。运行 git worktree list --porcelain,Git 仍列出主工作树和那条已失联的路径,并在末尾标出原因:
prunable gitdir file points to non-existent location
这是 Git 的发现,不是仓库损坏的判决。记录还在,Git 才能指出它原本指向哪里;prunable 表示它符合清理条件。脚本和完整输出分别保存在 tools/verify-tools-git-worktree-prune.py、tools/verify-tools-git-worktree-prune.log。实验只在系统临时目录里操作,退出后目录自动清除。
dry-run 只预览,不删记录
清理前可以先运行:
git worktree prune --dry-run --verbose --expire=now
本机用这组选项跑完后,Git 把 Removing worktrees/linked: gitdir file points to non-existent location 写到标准错误;再查 git worktree list --porcelain,那条记录仍标为 prunable。这行是预览,不代表登记已经删除。日志原文是:
stderr: Removing worktrees/linked: gitdir file points to non-existent location
所以不能把 Removing 这个词直接读成已经动手清理;dry-run 结束后还要看状态。至少在这次 Git 2.50.1(Apple Git-155)实验中,列表比单看提示词可靠。
prune 清的是哪一层
接着在同一临时仓库执行不带参数的 git worktree prune --verbose,重新列出工作树时,刚才那条记录已经不见,只剩主工作树。此处观察到的是本机版本的默认行为;如果仓库配置了过期策略,或运行在别的 Git 版本上,结果需要按实际环境确认。默认 prune 在标准错误中报告了移除动作;后续的 --expire=now 调用则没有标准输出或错误输出。判断清理是否生效,要比较调用前后的工作树列表,不能只看终端是否打印了提示。
为单独验证强制过期选项,脚本再创建一棵新工作树、删除其目录,然后执行 git worktree prune --expire=now。命令结束后再次列出登记,只剩主工作树,管理区里的失联记录也不存在。日志中的关键输出原样如下:
== list after prune ==
verified: stale worktree registration removed
--expire=now 把过期时间界限设为现在,适合清理已确认失效的登记;它不负责找回被删掉的工作区内容。这里的目录在执行 prune 前已经不存在,删掉的是仍留在仓库管理区里的记录。若目录里还有尚未提交的文件,直接从文件管理器删除并不能由 prune 补救。
先判断要清理的是什么
日常删除仍存在的工作树时,优先用 Git 自己的工作树管理命令,让它同时处理工作目录和登记;prune 面向的是目录已失联、登记成了孤儿的情况。二者的对象不同,不能把 prune 当成通用的「删除 worktree」快捷键。
排查时先看 git worktree list --porcelain。若记录仍对应一个需要保留的目录,就不要 prune;若路径已被有意删除,再看 prunable 提示,并用 dry-run 检查影响。此后是否运行普通 prune 或指定 --expire=now,取决于仓库的过期策略和清理时机。不建议只凭终端里没有报错就清理,先确认列表里的目标确实失联,能避免把管理记录与有效工作目录混为一谈。