积累沉淀

待山花烂漫,化茧成蝶

Git Worktree 完全手册

前言: 什么是 Git Worktree?
Git Worktree 允许你在同一个 Git 仓库中,同时拥有多个独立的工作目录,每个目录可以检出不同的分支.

Git Worktree 完全手册

基于实际对话整理,涵盖底层原理、工作流场景与常见误区。


目录

  1. 底层原理
  2. 核心机制
  3. 工作流应用场景
  4. 场景示例:紧急热修复不中断功能开发
  5. 常见误区:删除 worktree 不等于合并分支
  6. 常用命令速查

1. 底层原理

1.1 文件系统层面的实现

当你运行 git worktree add <path> <branch> 时,Git 并不会复制整个仓库,而是创建了一个轻量级的链接机制

额外工作树的 .git 是一个文本文件,而非目录:

1
2
# 在 my-feature/.git 文件中
gitdir: /path/to/main-repo/.git/worktrees/my-feature

主仓库 .git 目录的变化:

1
2
3
4
5
6
7
8
9
10
11
12
.git/
├── objects/ ← 所有 worktree 共享同一个对象数据库
├── refs/ ← 共享引用命名空间
├── worktrees/
│ ├── my-feature/
│ │ ├── HEAD ← 该工作树独立的 HEAD 指针
│ │ ├── index ← 独立的暂存区(stage)
│ │ ├── logs/HEAD ← 独立的 reflog
│ │ ├── commondir ← 指向主 .git 目录
│ │ └── gitdir ← 指向该工作树的 .git 文件位置
│ └── hotfix/
│ └── ...

1.2 与 git clone 的本质区别

维度 git clone git worktree
Objects 数据库 复制一份 共享
Refs 引用 独立 共享
磁盘占用 大(完整副本) 极小(仅工作目录差异)
同步方式 需 fetch/pull 天然同步(共用 objects)

2. 核心机制

层面 行为
Objects 数据库 所有 worktree 共享 .git/objects,不重复存储
Refs 引用 共享 .git/refs,但每个 worktree 有独立的 HEAD
Index 暂存区 各自独立,每个 worktree 维护自己的索引
分支限制 同一个分支不能同时检出到多个 worktree(防止冲突)
裸仓库支持 可以从 bare repo 创建多个工作树,实现"一个仓库多份检出"

3. 工作流应用场景

场景 痛点 Worktree 解决方式
并行开发 开发到一半要切分支修 Bug,被迫 stash 新目录直接检出 hotfix 分支,原目录完全不动
代码审查 Review 同事 PR 时需要切换分支,打断当前流 单独 worktree 检出 PR 分支,主目录继续编码
多版本维护 同时维护 v2.x LTS 和 v3.x 主线 两个 worktree 分别长期持有不同分支
长耗时构建 切分支后 node_modules 或构建缓存失效 各 worktree 有独立的工作目录,依赖互不干扰
CI/CD 优化 流水线需要同时构建多个分支 基于同一个裸仓库创建多个 worktree,节省磁盘和 clone 时间

4. 场景示例:紧急热修复不中断功能开发

背景

你正在 feature/payment-gateway 分支上开发支付网关重构,修改了 20 个文件,但还没准备好提交。此时生产环境突发严重 Bug,需要立即修复。

传统方式的问题

1
2
3
4
5
6
7
git stash push -m "WIP: payment gateway"   # 暂存当前工作
git checkout main
git pull
git checkout -b hotfix/critical-bug # 修复 Bug
# ... 修复完合并后 ...
git checkout feature/payment-gateway
git stash pop # 恢复,但可能冲突

痛点:stash/restore 可能出错;如果功能分支有未跟踪文件,stash 不会保存;切换分支可能导致 IDE 重新索引、构建缓存失效。

Worktree 方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 当前在 ~/projects/kimi 的 feature/payment-gateway 分支上开发中
# 无需 stash,直接创建热修复工作树
git worktree add ../kimi-hotfix main

# 进入热修复目录,它已经是 main 分支的最新状态
cd ../kimi-hotfix

# 创建并切换到修复分支
git checkout -b hotfix/critical-bug
# ... 修复代码、测试、提交 ...

# 推送并发起 PR
git push origin hotfix/critical-bug

# 修复完成后,清理这个工作树
cd ~/projects/kimi
git worktree remove ../kimi-hotfix # 或手动删除目录后再 prune

效果

1
2
3
4
5
6
7
8
9
~/projects/
├── kimi/ ← 原工作树,feature/payment-gateway 分支
│ ├── src/
│ ├── node_modules/ ← 不受干扰
│ └── .git/
├── kimi-hotfix/ ← 新工作树,hotfix/critical-bug 分支
│ ├── src/
│ ├── node_modules/ ← 独立的,可单独安装依赖
│ └── .git (文本文件,指向主仓库)

优势:

  • ~/projects/kimi 里的 20 个未提交修改完全不受影响
  • ✅ IDE 保持打开,无需重新加载项目
  • ✅ 两个目录有独立的 node_modules,不会因分支差异导致依赖混乱
  • ✅ 修复完成后直接删除 kimi-hotfix 目录即可,零副作用
  • ✅ 磁盘占用极小,因为 objects 数据库共享

5. 常见误区:删除 worktree 不等于合并分支

问题

当走到最后一步 git worktree remove ../kimi-hotfix 后,kimi 中是否已经更新了 kimi-hotfix 修改的内容?

答案:不会

删除 kimi-hotfix 工作树后,kimi 主工作树中的文件内容不会自动包含 hotfix 的修改。

区分三个层面

层面 状态 原因
Git 对象数据库 ✅ 已包含 hotfix 提交 所有 worktree 共享同一个 .git/objects,commit 已经写入了
分支引用 hotfix/critical-bug 分支存在 共享 .git/refs,删除 worktree 不影响引用
主工作树的文件 不包含 hotfix 修改 kimi 检出的是 feature/payment-gateway,与 hotfix/critical-bug 是两个独立分支

为什么不会自动更新?

Worktree 的核心设计就是隔离的工作目录。每个 worktree 维护自己的 HEADindex

1
2
kimi/                    HEAD → feature/payment-gateway
kimi-hotfix/ HEAD → hotfix/critical-bug (已删除,但分支仍在)

删除 kimi-hotfix 只是移除了一个工作目录的入口,并不会触发任何分支合并操作。feature/payment-gateway 分支的指针没有移动,所以 kimi 目录里的代码保持原样。

正确做法:显式合并

1
2
3
4
5
6
7
8
# 1. 先确保 hotfix 合并到 main(通过 PR 或命令行)
cd ~/projects/kimi
git checkout main
git merge hotfix/critical-bug # 或 git pull origin main

# 2. 回到功能分支,把 main 的最新修改同步进来
git checkout feature/payment-gateway
git rebase main # 或 git merge main

💡 一句话总结:Worktree 让你并行检出多个分支,但从不帮你自动合并它们。删除 worktree ≠ 合并分支。


6. 常用命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看所有工作树
git worktree list

# 基于某分支创建工作树
git worktree add ../foo branch-name

# 基于 main 创建新分支并检出
git worktree add -b feat ../foo main

# 删除工作树
git worktree remove ../foo

# 清理已不存在目录的残留记录
git worktree prune

# 从裸仓库创建工作树(CI/CD 常用)
git worktree add --track -b feature ../feature origin/feature

Buy me a coffee please.