TT Lab
开始
学习 学习路径 课程

Git 实战

不把手头的活儿塞进 stash,而是再开一张桌子

在 TT Lab 中继续学习

一句话总结

git worktree 可以让一个仓库(一个 .git)挂载多个工作目录。 提交、分支和对象全部共享,只有检出的位置不同。因此可以在不把手头工作推到任何地方的情况下,打开另一个分支。

为什么需要它

周五下午经常出现这样的事:你在功能分支上改了一半十二个文件,这时生产环境出了故障,需要往发布分支上打热修复。通常的做法是这样的。

git stash
git switch release
# 고치고 커밋하고 올리고
git switch feature
git stash pop

这个流程有三个陷阱。第一,git stash 默认不会收纳未跟踪的文件。新建的文件即使切换了分支也仍留在原地,结果混进了不相干的分支。第二,stash 是没有名字的栈,堆上两三个之后就分不清谁是谁,而且 stash pop 一旦冲突,就得当场处理恢复。第三,一切换分支,构建产物和依赖目录就整体失效。在大型项目里,这次重新构建耗费的时间比热修复本身还长。

也可以用 git clone 再建一个副本。这样上面三个问题都解决了,但要把整段历史再下载一份,而且两个副本的分支开始各走各的。一边创建的提交在另一边没有,最后两者之间还得再做 push 和 fetch。

git worktree 正好填补了这两者之间的空白:对象库只有一个,所以不用再下载历史;目录有两个,所以构建产物不会混在一起。

工作原理

执行 git worktree add <경로> <브랜치>(占位符依次为路径与分支名)会产生两样东西。

这里有一个重要事实:HEAD 和索引每个工作树各有一份,但分支引用(refs/heads/*)由整个仓库共享。 所以想在两个位置检出同一个分支时,git 会拒绝。

$ git worktree add /tmp/dup main
fatal: 'main' is already used by worktree at '/home/me/repo'

这不是不友好,而是保护。分支引用只有一个,工作树却有两个,那么一边提交的瞬间,另一边就会坐在一个不是自己创建的提交之上,索引和实际文件随之错位。如果确实想在两个位置查看同一个提交,可以用 --detach 不带分支地挂载。

清理分三种命令。

情况 命令 作用
工作结束,干净地收尾 git worktree remove <경로> 同时删除目录和管理文件
目录已经被 rm -rf 删掉 git worktree prune 只删除残留的管理文件
像外接磁盘那样暂时看不见 git worktree lock 锁定,防止 prune 动它

只用 rm -rf 删掉目录的话,git worktree list 里那个位置会一直显示为 prunable。在这种状态下用同一个名字再次 add,会得到“已经存在”的提示。而且 prune 只删除管理文件——在那个位置创建的分支仍然保留,所以要连分支一起清理,得另外执行 git branch -d。

在现场相遇的样子

也有要注意的地方。工作树中运行 git gc 时,为了保住其他工作树引用的对象,git 必须知道这些位置;但如果有位置被 rm -rf 删掉后没有 prune,这些信息就过期了。所以最好不要拖延清理。

下一项实验要做什么

挂载三个工作树,亲眼看看两次打开同一个分支时被拒绝的情形。保留未保存的修改不动,单独提交一个热修复;确认目录被 rm -rf 后会显示 prunable,以及 prune 之后分支依然保留。最后还会看到被锁定的工作树如何拒绝 remove。

官方文档是 git-worktree。