Git-Worktree多分支并行开发

你正在 feature 分支改了 20 个文件,线上突然报了个紧急 bug——怎么办?

stash 的话,需要 5 步:暂存 → 切分支 → 修 bug → 切回来 → 还原(还要祈祷没有冲突)。Git Worktree 让你只需 2 步:新建一个目录直接开始修,feature 的代码、IDE 窗口、编译缓存全程不受干扰。

1. 快速上手:5 分钟跑通第一个 Worktree

1.1 验证环境

Worktree 是 Git 内置功能,Git 2.5+ 即可使用:

1
2
$ git --version
git version 2.43.0

1.2 创建第一个工作树

进入任意 Git 仓库,执行:

1
2
3
$ git worktree add ../my-hotfix -b hotfix/urgent
Preparing worktree (new branch 'hotfix/urgent')
HEAD is now at a1b2c3d Initial commit

这条命令做了两件事:在 ../my-hotfix 创建一个新目录,并在其中检出新分支 hotfix/urgent。你的当前目录(feature 分支)完全不受影响。

1.3 查看当前所有工作树

1
2
3
$ git worktree list
/home/user/my-project a1b2c3d [feature/login]
/home/user/my-hotfix a1b2c3d [hotfix/urgent]

两行分别对应两个目录,对应两个分支,独立共存。

1.4 清理工作树

修完 bug 之后,删除工作树:

1
$ git worktree remove ../my-hotfix

再次 list,只剩主工作树:

1
2
$ git worktree list
/home/user/my-project a1b2c3d [feature/login]

跑通了基本流程,再来看 Worktree 和分支切换的本质区别。

2. Worktree Vs Branch:为什么不用 Stash + checkout?

2.1 本质区别

Branch 是指针,Worktree 是工作空间。

  • Branch:一个指向某个 commit 的引用,本身不包含任何文件。git branch feature 只是在 .git/refs/heads/ 下创建一个 41 字节的文本文件。
  • Worktree:一个完整的工作目录,包含独立的 HEADindex(暂存区)和所有工作文件。

类比:Branch 是书的目录里的页码,Worktree 是你打开的那本书。你可以有 100 个页码,但书一次只能翻到一页——除非用 worktree “ 复印 “ 出另一本,同时翻到不同页。

2.2 对比表

维度只用 Branch 切换用 Worktree
并行编辑❌ 同一时刻只能在一个分支上工作✅ 多个目录同时打开不同分支
上下文保留❌ 切换前必须 stash 或 commit✅ 每个目录独立保留修改状态
编译缓存❌ 切换后 node_modules、build 产物可能失效✅ 各目录有独立的构建产物
IDE 状态❌ 切换后需要重新打开文件、恢复断点✅ 每个目录可以用独立的 IDE 窗口
操作步骤多(stash → checkout → work → checkout → pop)少(直接进入另一个目录)
磁盘开销无额外开销每个工作树占用一份工作文件空间(共享 .git)

3. 核心命令详解

3.1 Git Worktree Add —— 创建工作树

检出已有分支:

1
2
3
$ git worktree add ../hotfix-dir hotfix/login-bug
Preparing worktree (checking out 'hotfix/login-bug')
HEAD is now at d4e5f6a Fix login redirect

创建新分支并检出:

1
2
3
$ git worktree add ../experiment -b feature/try-new-api
Preparing worktree (new branch 'feature/try-new-api')
HEAD is now at d4e5f6a Fix login redirect

可选参数

参数说明
-b <名称>创建并检出一个新分支
--lock创建后自动加锁,防止被 prune 误清理(Git 2.15+)

3.2 Git Worktree List —— 查看所有工作树

1
2
3
4
$ git worktree list
/home/user/my-project a1b2c3d [main]
/home/user/hotfix-dir d4e5f6a [hotfix/login-bug]
/home/user/experiment d4e5f6a [feature/try-new-api]

每行显示:目录路径、当前 commit hash、检出的分支名。

可选参数--porcelain 输出机器可读格式,适合脚本解析。

3.3 Git Worktree Remove —— 删除工作树

正确删除方式(同时删除目录和 .git/worktrees 中的记录):

1
$ git worktree remove ../hotfix-dir

删除前工作树必须干净(无未提交修改)。如果想强制删除:

1
$ git worktree remove --force ../experiment

不要用 rm -rf 直接删目录,会在 .git/worktrees/ 中留下残留记录。

3.4 Git Worktree Prune —— 清理残留记录

用于清理 .git/worktrees/ 中指向已不存在目录的过期记录(通常在误用 rm -rf 之后):

1
2
3
4
$ git worktree prune --dry-run   # 先预览,不实际执行
Removing worktrees/hotfix-dir: gitdir file points to non-existent location

$ git worktree prune # 确认后执行

3.5 命令与架构总览

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart TB
    subgraph repo ["📦 Git 仓库(共享 .git)"]
        direction TB
        git_dir[".git 目录\n(对象库/引用/配置)"]
    end

    subgraph worktrees ["🗂️ 工作目录们"]
        direction LR
        main["📁 main-project/\n(主工作树 - main 分支)"]
        wt1["📁 hotfix-dir/\n(附加工作树 - hotfix 分支)"]
        wt2["📁 feature-dir/\n(附加工作树 - feature 分支)"]
    end

    git_dir -->|"共享"| main
    git_dir -->|"共享"| wt1
    git_dir -->|"共享"| wt2

    subgraph commands ["⚙️ 核心命令"]
        add["git worktree add"]
        list["git worktree list"]
        remove["git worktree remove"]
        prune["git worktree prune"]
    end

    add -->|"创建"| wt1
    add -->|"创建"| wt2
    remove -->|"删除"| wt1
    list -->|"查看"| worktrees

    classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef success fill:#10B981,stroke:#059669,color:#fff
    classDef info fill:#0EA5E9,stroke:#0284C7,color:#fff

    class git_dir primary
    class main,wt1,wt2 success
    class add,list,remove,prune info

4. 错误处理

新手使用 worktree 时,有 4 类高频报错,每类给出报错现象、原因和修复方法。

4.1 Already Checked Out

现象: 尝试在新工作树检出一个已有工作树正在使用的分支:

1
2
$ git worktree add ../another-dir feature/login
fatal: 'feature/login' is already checked out at '/home/user/my-project'

原因: 同一个分支不能同时被两个工作树检出,否则两边的 HEAD 会冲突。

修复: 换一个新分支名,在已有工作树中操作,或者 cd 到已有工作树直接开始:

1
$ git worktree add ../another-dir -b feature/login-v2

4.2 Rm -rf 误删后修复

现象: 手动删除了工作树目录后,再操作该分支报错:

1
2
3
$ rm -rf ../hotfix-dir           # 误操作
$ git worktree add ../hotfix-dir hotfix/login-bug
fatal: 'hotfix/login-bug' is already checked out at '/home/user/hotfix-dir'

原因: .git/worktrees/hotfix-dir/ 中的记录仍然存在,Git 认为该分支还在使用中。

修复:prune 清理残留记录:

1
2
$ git worktree prune
$ git worktree add ../hotfix-dir hotfix/login-bug # 再次创建,成功

4.3 在工作树内切换分支

现象: 在附加工作树目录里执行 git checkout,意外切换到了其他分支:

1
2
3
# 当前在 ../hotfix-dir(hotfix/urgent 分支)
$ git checkout main
Switched to branch 'main'

原因: 这会破坏隔离性——如果 main 已经在主工作树中被检出,两个工作树同时持有同一分支,状态会混乱。

修复: 立即 checkout 回原分支:

1
$ git checkout hotfix/urgent

之后如果需要操作另一个分支,应该回到主工作树中用 git worktree add 创建新的工作树,而不是在已有工作树内切换。

4.4 工作树内有未提交修改

现象: 删除工作树时提示有修改未提交:

1
2
$ git worktree remove ../hotfix-dir
fatal: '../hotfix-dir' contains modified or untracked files, use --force to delete it

原因: worktree remove 的默认行为是安全删除,发现未保存的工作会拦截。

修复(两种方式选一):

1
2
3
4
5
6
7
8
# 方式一:先保存工作再删除(推荐)
$ cd ../hotfix-dir
$ git stash
$ cd ../my-project
$ git worktree remove ../hotfix-dir

# 方式二:强制删除(修改会丢失,不可恢复)
$ git worktree remove --force ../hotfix-dir

5. 进阶实践

5.1 方案横向对比

维度git worktreegit clone(多份克隆)git stash + checkout
磁盘占用低(共享对象库)高(完整复制)无额外占用
并行工作✅ 可同时编辑多分支✅ 可以❌ 只能一个分支
上下文保留✅ 每个目录独立状态✅ 各自独立❌ stash 容易混乱
同步性即时共享 commit需要 push/pull不涉及
编译缓存✅ 各目录独立缓存✅ 各目录独立❌ 切换后缓存失效

5.2 裸仓库 + Worktree 模式

适合需要频繁并行维护多个长期分支的场景:

1
2
3
4
5
6
7
# 克隆为裸仓库,没有工作目录
$ git clone --bare git@github.com:user/repo.git repo.git
$ cd repo.git

# 每个分支独立检出到单独目录
$ git worktree add ../main main
$ git worktree add ../feature-x feature-x

推荐的目录结构:

1
2
3
4
~/projects/my-repo.git/       ← 裸仓库(.git 本身)
~/projects/my-repo-main/ ← main 分支
~/projects/my-repo-hotfix/ ← hotfix 分支
~/projects/my-repo-feature/ ← feature 分支

所有分支地位平等,没有 “ 主工作树 “ 的心智负担。

6. 动手练习

5 分钟体验 worktree 并行开发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. 进入任意 Git 仓库
$ cd your-repo

# 2. 创建一个附加工作树
$ git worktree add ../my-worktree -b test-wt
Preparing worktree (new branch 'test-wt')
HEAD is now at a1b2c3d ...

# 3. 查看工作树列表(应显示 2 个)
$ git worktree list
/home/user/your-repo a1b2c3d [main]
/home/user/my-worktree a1b2c3d [test-wt]

# 4. 进入新工作树,验证分支独立
$ cd ../my-worktree && git branch
* test-wt

# 5. 回到主目录,清理
$ cd ../your-repo
$ git worktree remove ../my-worktree

# 6. 验证清理完成(应只剩 1 个)
$ git worktree list
/home/user/your-repo a1b2c3d [main]

验收标准:步骤 3 显示 2 个工作树路径,步骤 6 只剩 1 个。

7. 深度探索

7.1 内部实现

每个附加工作树在 .git/worktrees/<name>/ 下有自己的 HEADindexMERGE_HEAD 等文件。工作树目录中的 .git 不是一个目录,而是一个文本文件,内容为:

1
gitdir: /path/to/.git/worktrees/<name>

这是一个指回主仓库的指针,所有工作树共享同一份对象库,不会重复存储 commit 历史。

Git 2.15+ 支持 --lock 参数,防止工作树被 prune 误删,适合放在移动硬盘或网络挂载位置:

1
$ git worktree add --lock /mnt/usb/review review-branch

7.2 真实案例

  • Linux 内核开发:维护者同时审查多个补丁系列,用 worktree 为每个补丁系列创建独立的测试环境
  • 大型前端项目(如 React):一个 worktree 跑 main 分支的 dev server 做回归测试,另一个 worktree 开发新 feature,两个浏览器窗口同时对比
  • CI/CD 流水线:构建服务器用裸仓库 + worktree 并行编译多个分支,共享对象库减少 fetch 时间和磁盘占用