Git Worktree 完全教程(详细版)
本文是「通俗版」的加长加细版:概念更深、命令更全、场景更多,且所有示意图都用字符(ASCII)绘制,不依赖任何图形渲染,复制到哪里都能看。
目录
- 为什么需要 worktree(痛点)
- 什么是 Git Worktree
- 核心原理(
.git内部到底发生了什么) - 完整命令参考
- 实战 Walkthrough(从零到收工)
- 典型场景
- 与
git clone的对比 - 常见坑与排错
- 最佳实践
- 速查表
1. 为什么需要 worktree(痛点)
你一定遇到过这种情况:正在 feature 分支敲代码,线上突然报错要立刻修。
老办法(只有一张桌子,反复换文件):
ini
【同一张桌子,反复换分支】
┌──────────────┐
│ 工作目录 │
│ branch=feature│── 改到一半
└──────────────┘
│ 急事来了
▼
git stash ← 先把半成品藏起来
git checkout hotfix ← 切过去
改完、提交、push
git checkout feature
git stash pop ← 再摊开,还可能对冲突
(来回切,脑壳痛,stash 还有丢改动的风险)
worktree 办法(多张桌子,各干各的):
arduino
【多张桌子,互不干扰】
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 桌A:feature│ │ 桌B:hotfix │ │ 桌C:release│
└────┬─────┘ └────┬─────┘ └────┬─────┘
└─────────────┼──────────────┘
▼
┌──────────────┐
│ Git 仓库 │
│ (.git) │
└──────────────┘
三张桌子共享一个仓库,互不干扰,零切换
一句话:worktree 让你在不 stash、不切分支的前提下,把同一仓库的多个分支同时铺开在多个目录里。
2. 什么是 Git Worktree
一个 Git 仓库可以拥有多个 工作树(worktree):
- 主工作树(main worktree) :你
git clone/git init出来的第一个目录。 - 链接工作树(linked worktree) :用
git worktree add额外挂上去的目录。 - 所有工作树共享同一个
.git对象库(commits/trees/blobs 只存一份),但各自有独立的工作文件、独立的分支、独立的 IDE 窗口。
arduino
一个仓库 = 一个 .git + 多个工作树
├── 主工作树(main 分支)
├── 链接工作树 1(hotfix 分支)
└── 链接工作树 2(release 分支)
3. 核心原理(.git 内部到底发生了什么)
很多人以为 worktree 是"又复制了一份仓库",其实不是。关键在于:链接工作树里的 .git 不是目录,而是一个指向主仓库的"文件"。
javascript
主仓库 ~/project/.git/
├── HEAD
├── objects/ ← 所有版本数据,只有这一份
├── refs/
└── worktrees/ ← 每个链接工作树的"登记表"都在这
├── wt-hotfix/
│ ├── HEAD
│ └── gitdir ← 内容指向链接树的 .git 文件
└── wt-release/
└── ...
链接工作树 ~/project-hotfix/
├── .git ← 注意:是"文件"不是目录!
├── src/
└── README.md
~/project-hotfix/.git 这个文件内容长这样:
javascript
gitdir: /home/you/project/.git/worktrees/wt-hotfix
它告诉 Git:"我的真实数据都在主仓库的那个 worktrees/wt-hotfix 里"。所以链接树本身几乎不占空间,全靠主仓库。
推论:删链接工作树时,不能直接
rm -rf,否则主仓库.git/worktrees/里的登记会残留(后面"排错"会讲怎么清理)。
4. 完整命令参考
4.1 列出所有工作树
bash
git worktree list
输出示例:
arduino
/home/you/project abc123 [main]
/home/you/project-hotfix def456 [hotfix/bug]
/home/you/project-rel fed789 [release/1.0]
机器可读格式(写脚本用):
bash
git worktree list --porcelain
4.2 新增工作树(最常用的命令)
基本句式:
bash
git worktree add <新目录路径> [<分支或提交>]
几种常见写法:
bash
# 基于已有分支 hotfix 开一个 worktree(要求 hotfix 没被别的树占用)
git worktree add ../project-hotfix hotfix
# 新建分支 hotfix/bug 并开 worktree(最常用)
git worktree add -b hotfix/bug ../project-hotfix main
# 分离 HEAD,临时看某次提交/标签,不占任何分支
git worktree add --detach ../project-v1 v1.0
# 基于某次提交开新分支
git worktree add -b exp/fix ../project-exp a1b2c3d
规则:如果给的分支名已存在 且没被占用,就直接检出它;如果不存在 ,Git 会自动以当前 HEAD 为基新建该分支(等价于隐式
-b)。想显式控制就用-b。
4.3 移除工作树
bash
git worktree remove ../project-hotfix
- 只能移除链接工作树,不能移除主工作树。
- 工作树里有未提交改动时会拒绝,加
-f可强制(会丢改动,慎用)。
4.4 移动工作树
bash
git worktree move ../project-hotfix ../hotfix-new
等价于把目录挪位置,并自动更新 .git/worktrees/ 里的登记。比手动 mv 再 repair 省事。
4.5 锁定 / 解锁
bash
git worktree lock ../project-hotfix
git worktree unlock ../project-hotfix
用途:当 worktree 放在可移动磁盘 / NFS / 临时会离线 的位置时,git worktree prune 可能误以为它"失效"并清理登记。先 lock 就能防止被 prune 误删。
4.6 清理失效登记
bash
git worktree prune
当你手快直接 rm -rf 删了 某个 worktree 目录,主仓库里还留着它的登记,list 会显示 (error),此时跑 prune 把残留记录清掉。
4.7 修复路径(仓库被移动后)
bash
git worktree repair
如果主仓库目录被挪了位置,链接工作树里的 .git 指向就失效了,在任一工作树里跑 repair 可批量修正所有登记。
5. 实战 Walkthrough(从零到收工)
假设你有个项目 ~/project,当前在 main 分支。现在要并行修一个紧急 bug。
javascript
【开始前】
~/project/ ← 主工作树(main 分支,正写着 feature)
步骤 1:开一张新桌子
bash
cd ~/project
git worktree add -b hotfix/login ../project-hotfix main
javascript
【执行后】
~/project/ ← 主工作树(main,feature 原封不动)
~/project-hotfix/ ← 新 worktree(hotfix/login 分支)
两者共享 ~/project/.git
步骤 2:在新桌子干活
bash
cd ~/project-hotfix
# 改代码、跑测试
git add -A
git commit -m "fix: 登录空指针"
git push -u origin hotfix/login
此时 ~/project 里的 feature 一行没动,IDE 也不用来回切。
步骤 3:合并后收桌子
bash
# 在 Git 平台合并 hotfix/login 到 main 后
git worktree remove ../project-hotfix
javascript
【收工后】
~/project/ ← 只剩它,干干净净
步骤 4(可选):盘点
bash
git worktree list
# 输出只剩 ~/project 一行
6. 典型场景
场景 A:线上紧急 bug
最经典用法,见上面 Walkthrough。核心就一句:add -b 开新树 → 改完 → remove 收工。
场景 B:对比两个版本
bash
git worktree add --detach ../v1.0 v1.0
git worktree add --detach ../v2.0 v2.0
然后用编辑器/对比工具同时打开 ./、../v1.0、../v2.0,diff 一目了然。
场景 C:评审别人的 PR
bash
git fetch origin
git worktree add -b review/pr123 ../pr123 origin/pr/123
cd ../pr123
# 本地把 PR 跑起来看效果,看完 remove 掉
场景 D:同时维护多个 release 分支
arduino
project/ ← main(开发下一代)
project-1.0/ ← release/1.0(修老版本)
project-2.0/ ← release/2.0(维护当前线上)
三个目录各对应一个长期维护分支,互不打架,构建缓存也各自独立。
场景 E:多分支同时跑构建/测试
主目录跑 feature 的单测,另一个 worktree 跑 release 的集成测试,不用等一个跑完再切。
7. 与 git clone 的对比
很多人分不清"再 clone 一份"和"开 worktree"的区别:
scss
【克隆两份】 【worktree】
project/ (仓库 A 的完整副本) project/ (主,共享 .git)
project2/ (仓库 A 的完整副本) project-b/ (链接,共享 .git)
├─ 两份 .git,占双倍磁盘 ├─ 一份 .git,几乎不占额外空间
├─ 两边历史各自独立,久了易"漂移" ├─ 历史天然一致(同一对象库)
└─ 适合:完全独立的两个开发机 └─ 适合:同机并行多分支
什么时候用 clone :需要在另一台机器、或想要完全隔离的副本时。 什么时候用 worktree:同一台机器上,想并行处理同一仓库的多个分支时。
8. 常见坑与排错
csharp
坑 1:分支被占用
现象:fatal: 'main' is already checked out at '...'
原因:同一分支不能被两个 worktree 同时检出
解决:开新分支(-b),或用 --detach 看旧版本
坑 2:直接 rm -rf 删了 worktree 目录
现象:git worktree list 显示 (error) / 路径失效
解决:git worktree prune 清理残留登记
坑 3:忘记自己开过几个 worktree
现象:磁盘里一堆目录,搞不清哪个还在用
解决:定期 git worktree list 盘点;不用的及时 remove
坑 4:可移动磁盘上的 worktree 被 prune 误删登记
现象:磁盘还在,但 list 里显示失效
解决:长期离线前先 git worktree lock,回来再 unlock
坑 5:想 remove 主工作树
现象:报错,主工作树不能直接 remove
解决:主工作树随仓库本身存在,要"删"就直接删整个项目目录
坑 6:在 worktree 里又 clone 了一份
现象:worktree 套 worktree,空间翻倍
解决:不必,worktree 本就共享对象库
9. 最佳实践
- 命名有规律 :worktree 目录用
../<项目>-<分支>或<项目>@<分支>风格,一眼知道对应什么。 - 短期任务用完即
remove:临时修 bug / 看 PR 的 worktree,合并后顺手删,别堆着。 - 长期维护分支才留常驻 worktree :如
release/1.0这种。 - 别在 worktree 间硬拷文件 :改动走 commit,保持各树干净,方便
remove不报错。 - CI / 脚本里用
--porcelain:便于程序解析list输出。 - 仓库搬家后跑一次
repair:确保所有链接树登记同步更新。
10. 速查表
css
命令 作用
─────────────────────────────────────────────────────────
git worktree list 列出所有工作树(--porcelain 机器可读)
git worktree add <p> <b> 新增工作树并检出已有分支 b
git worktree add -b <nb> <p> 新建分支 nb 并开工作树
git worktree add --detach <p> <commit> 分离 HEAD 看某版本
git worktree remove <p> 移除工作树(-f 强制)
git worktree move <p> <np> 移动工作树路径
git worktree lock <p> 锁定,防 prune 误删
git worktree unlock <p> 解锁
git worktree prune 清理失效登记
git worktree repair 修复仓库移动后的路径登记
总结
git worktree 的本质:一个 .git,多张"办公桌"。
- 想并行多分支 → 开 worktree,告别
stash反复切。 - 同一分支不能同时被两个 worktree 检出 → 用新分支或
--detach。 - 删 worktree 用
remove,别直接rm;手快删了就prune补救。
把它放进你的日常工具箱,下一次"写到一半被打断",你会感谢现在的自己。