简单记录一下 git worktree 的用法。worktree 允许在同一个本地仓库中同时拥有多个工作目录,
每个工作目录可以对应不同的分支或提交。非常适合并行开发、快速切换上下文或测试。

为什么需要 worktree?

在传统的 git 使用中,一个本地仓库只有一个工作目录(即你当前检出的分支)。如果你需要同时处理两个不同的分支,通常的做法是:

  • 克隆多个仓库,可以按需要分别对应不同分支,但这样会浪费空间且难以同步。
  • 暂存当前工作,切换到另一个分支,完成工作后再切换回来,但是 stash 也可能有残留文件,而且切换可能出现问题。

worktree 解决了这个问题:它允许你为同一个仓库创建额外的工作目录,每个工作目录都有自己的实际文件夹、索引和 HEAD,它们共享同一个仓库的对象数据库和引用(refs)。

worktree 原理

Git 默认将数据检出到一个工作目录(即仓库的根目录),对于裸仓库,则不会检出。而 worktree 提供了检出多个工作目录的选择,并且这些工作目录之间共享同一个 Git 仓库的数据库和引用(refs)。
具体地说:

  • 多个工作目录共享同一个 .git/ 目录:即主工作目录下的 .git/ 目录
  • 对于派生的新的工作目录,会创建 .git 文件指向主工作目录的 .git/ 目录,其信息存储在主工作目录下的 .git/worktrees/ 中的对应目录。
  • .git/worktrees/ 中记录的工作目录位置是绝对路径,其他工作目录的 .git 文件也是使用绝对路径指向主工作目录。(这一点与 submodule 不同,submodule 的 .git 通常使用相对路径)

正常情况下,Git 会*保证不同工作目录正在使用不同的分支:

  • 在创建新的工作目录时,通常需要指定分支,不能使用目前已经被现有工作目录占用的分支;
  • 在切换分支时,同样要遵循这个原则。

在一个工作目录对一个分支进行的改动在另一个工作目录中也会立刻可见,因为它们都使用同一套数据库和引用,但工作目录的实际文件不会被更新,因为那只是其他分支的改动。

查看工作目录

使用 git worktree list 命令查看当前仓库下所有的 worktree,默认情况下只有一个,也就是主工作目录。

1
git worktree list

如果有多个工作目录,输出形如(如果没有创建多个工作目录,则只有第一行)

1
2
3
/path/to/main-repo        abc1234 [main]
/path/to/hotfix def5678 [hotfix]
/path/to/feature-x 123abcd (detached HEAD)

每一行显示:工作目录路径、当前 HEAD 的提交哈希、当前检出的分支或状态。

添加工作目录

使用 git worktree add 命令创建一个新的工作目录:

1
git worktree add <新工作目录的路径> [分支名]
  • <新工作目录的路径>:指定新工作目录要存放的目录,可以是绝对路径或相对路径。
  • [分支名]:可选,指定新工作目录要检出的分支。如果省略,默认尝试会根据提供的路径创建对应的分支(如果该分支不存在),如果失败则报错;如果指定分支,则该分支必须已经存在(或者基于当前 HEAD 创建新分支)

例如

1
2
3
4
5
# 在当前目录下创建一个名为 hotfix 的工作目录,并检出 hotfix 分支(如果不存在则自动创建)
git worktree add ../hotfix

# 创建一个基于当前 HEAD 的新分支 feature-x,并放入 ../feature-x 目录
git worktree add ../feature-x -b feature-x

注意:

  • 新工作目录目录不能与现有工作目录目录重叠,也不能是已存在的非空目录(除非是空目录)。默认情况下,新工作目录会被创建为指定分支的检出状态。
  • 新工作目录通常都是对应一个分支,但是也可以对应任意一个提交节点,在指定分支时使用 -d 选项,或者直接指定哈希值/tag,此时该工作目录会被创建为 detached HEAD 状态。
  • 每一个分支在同一时刻只能被一个工作目录使用,新工作目录不允许与其他工作目录使用相同的分支。

移除工作目录

当不再需要某个工作目录时,可以将其移除(不仅是 Git 配置层面,还会实际在文件系统中将其删除)

1
git worktree remove <工作目录路径>

注意被移除的工作目录必须处于干净状态(即没有未提交的修改),否则命令会失败。可以通过 --force 选项强制移除,但是会丢失未提交的修改。

除此之外,也可以直接手动删除工作目录的目录,然后运行 git worktree prune 来清理仓库中残留的配置记录。

1
2
rm -rf ../hotfix
git worktree prune

可以用下面的命令查看将要执行的清理动作

1
git worktree prune -n -v

为了阻止 prunegit worktree 还提供了锁定和解锁功能,但是用的很少,这里略去。

如果希望移动一个已经存在的工作目录到新位置,可以使用下面的命令移动(会实际在文件系统中操作)

1
git worktree move <worktree> <new-path>