前言
你是否经历过这样的场景?
当你正在为某个功能跑漫长的测试时,突然来了一个紧急的 bug 要修复,但你又不能随便动工作区的代码,因为测试还在进行。此时只能无奈地切换分支,导致 node_modules 需要重新安装,编译需要重新进行…
Git Worktree 就是来解决这个问题的。它允许你在同一个 Git 仓库中同时维护多个工作目录,每个目录可以检出不同的分支,彼此独立。
这篇文章将从理论到实战,深入讲解如何使用 Git Worktree 来优化你的开发流程。
什么是 Git Worktree?
基本概念
Git Worktree(工作树)是一个 Git 特性,允许你在同一个 Git 仓库中创建多个工作目录。
在没有 Worktree 之前:
- 一个仓库 = 一个工作目录
- 切换分支会改变当前工作目录的文件状态
- 如果当前工作目录有未提交的改动,切换分支可能很困难
有了 Worktree 之后:
- 一个仓库 = 多个独立的工作目录
- 每个工作目录可以检出不同的分支
- 多个目录可以同时进行开发,互不影响
工作树 vs 传统分支
常见误解: 工作树是对分支的替代
正确理解: 工作树是对分支的一种**“文件系统视图”**
- 分支:Git 中用来隔离开发路线的概念,存储在
.git/refs/heads/中 - 工作树:只是物理上创建了一个新的目录,让你能同时看到/编辑多个分支
分支的数量和数目都不会因为创建工作树而改变,工作树只是给了你一个新的"窗口"去访问这些分支。
为什么需要 Git Worktree?
常见应用场景
1️⃣ 场景:修复紧急 bug,不中断当前工作
你正在 feature/new-api 分支上开发新功能,代码还未提交。突然生产环境爆出一个 bug,需要立即修复。
传统做法:
# 代码还没 commit,必须先 stash
git stash
# 切回 main 分支
git checkout main
# 修复 bug,提交
git commit -m "fix: xxx"
# 切回开发分支
git checkout feature/new-api
# 恢复代码
git stash pop
问题:如果项目很大(如前端项目有巨大的 node_modules),即使只是切换分支也要重新安装依赖!
使用 Worktree 的做法:
# 在新的目录中检出 main 分支
git worktree add ../hotfix main
# 切换到新目录修复 bug
cd ../hotfix
git commit -m "fix: xxx"
git push
# 完成后删除工作树
cd ..
git worktree remove hotfix
没有任何 stash、没有分支切换的污染、当前工作目录毫发无损!
2️⃣ 场景:同时维护多个版本,避免重复安装依赖
你的项目有 main(当前版本)、v1.0(旧版本)、v1.1(预发布)等分支。
传统做法: 每次切换版本都要 npm install,特别是跨版本时(Node 版本要求可能不同)
使用 Worktree: 为每个版本创建一个工作树,它们各自拥有独立的 node_modules:
git worktree add ../release-v1.0 v1.0
git worktree add ../release-v1.1 v1.1
git worktree add ../release-main main
# 现在你可以同时在三个目录中开发,互不影响
cd ../release-v1.0 # 独立的 node_modules
cd ../release-v1.1 # 独立的 node_modules
cd ../release-main # 独立的 node_modules
3️⃣ 场景:对比不同分支的代码行为
需要同时查看两个分支的代码细节,进行对比测试。使用 Worktree 可以在两个编辑器窗口中同时打开两个版本的代码,一目了然。
4️⃣ 场景:并行开发相关的功能分支
多个开发者各自在不同的功能分支上工作。一个开发者可以使用 Worktree 快速在多个分支之间切换上下文,而不产生文件系统开销。
Git Worktree 常用命令详解
1. git worktree add - 创建新工作树
基本语法:
git worktree add <path> [<branch-or-commit>]
常用形式:
# 形式1:检出已有分支到新目录
git worktree add ../feature-x feature/feature-x
# 形式2:创建新分支并检出到新目录(-b 选项)
git worktree add -b feature/new-feature ../feature-new main
# 形式3:强制添加(即使分支已被其他工作树占用)
git worktree add -f ../hotfix hotfix
# 形式4:基于某个 commit 创建工作树
git worktree add ../debug-point abc1234
# 形式5:检出到分离头指针状态
git worktree add --detach ../experiment main
参数说明:
| 参数 | 说明 |
|---|---|
<path> | 新工作树的目录路径(相对或绝对都可以) |
<branch-or-commit> | 要检出的分支或 commit ID,默认为 HEAD |
-b <branch-name> | 创建新分支并检出,新分支基于 <commit> |
-B <branch-name> | 同 -b,但如果分支已存在则强制覆盖 |
-f | --force | 强制操作,即使该分支已被其他工作树占用 |
--no-checkout | 仅创建工作树目录,不检出文件 |
--detach | 创建分离头指针状态的工作树 |
2. git worktree list - 列出所有工作树
基本用法:
git worktree list
# 输出示例:
# /home/user/project abc1234 [main]
# /home/user/project/../hotfix def5678 [hotfix]
格式化输出:
git worktree list --porcelain
# 输出示例:
# worktree /home/user/project
# branch refs/heads/main
# detached
# worktree /home/user/project/../hotfix
# branch refs/heads/hotfix
3. git worktree remove - 删除工作树
基本用法:
# 删除指定的工作树
git worktree remove hotfix
# 强制删除(即使工作树目录损坏)
git worktree remove -f hotfix
注意事项:
git worktree remove会删除工作树目录及其所有内容- 如果工作树中有未提交的改动,删除会被拒绝(除非使用
-f) - 删除工作树不会影响关联的分支
4. git worktree move - 移动工作树
如果你需要重新整理工作树的位置:
# 将工作树移动到新位置
git worktree move hotfix ../new-location/hotfix
# 移动后,该工作树在新位置继续工作
cd ../new-location/hotfix
git log # 完全正常
5. git worktree lock/unlock - 锁定工作树
防止工作树被意外删除:
# 锁定工作树
git worktree lock --reason "Do not delete, in use" hotfix
# 尝试删除会报错:
# git worktree remove: '/path/to/hotfix' is locked, use 'remove -f' to override
# 解锁工作树
git worktree unlock hotfix
6. git worktree prune - 清理损坏的工作树
如果工作树目录被手动删除(不推荐),Git 会记住它。使用 prune 清理:
# 列出将要被清理的工作树(不执行)
git worktree prune -n
# 清理超过指定时间没有被访问的工作树
git worktree prune --expire 3.days
实战案例:完整工作流
现在用一个真实的场景来演练 Git Worktree 的完整工作流。
场景描述
你有一个 Next.js 项目,有三个重要分支:
main:生产分支develop:开发分支hotfix/urgent-fix:紧急修复
现在需要:
- 在
develop分支继续开发新功能(已有 50% 的工作) - 同时在
main分支修复一个生产 bug - 并检查
hotfix/urgent-fix分支是否可以合并
步骤演示
第 1 步:查看当前工作树状态
$ git worktree list
/Users/shawn/projects/app abc1234 [develop]
第 2 步:创建 main 分支的工作树来修复 bug
$ git worktree add -b fix/production-bug ../app-main main
准备 ../app-main (标识符 fix/production-bug)
HEAD 现在位于 def5678 Prepare v2.1.0
第 3 步:查看现在有多少个工作树
$ git worktree list
/Users/shawn/projects/app abc1234 [develop]
/Users/shawn/projects/app-main def5678 [fix/production-bug]
第 4 步:在 main 的工作树中修复 bug
$ cd ../app-main
$ vim src/pages/api/checkout.ts
$ npm test # 测试修复
$ git add -A
$ git commit -m "fix: prevent double-charge in checkout"
$ git push origin fix/production-bug
第 5 步:继续在 develop 分支工作(从未切换过!)
$ cd ../app # 回到原来的开发目录
$ vim src/components/NewFeature.tsx # 继续之前的工作
$ npm test # 开发中的测试
# 一切文件状态保持不变,node_modules 也完整无缺
第 6 步:检查 hotfix 分支状态
$ git worktree add ../app-hotfix hotfix/urgent-fix
$ cd ../app-hotfix
$ npm test && npm run build # 验证可以正常编译
$ cd ..
第 7 步:修复完成后清理工作树
$ git worktree list
/Users/shawn/projects/app abc1234 [develop]
/Users/shawn/projects/app-main ghi9012 [fix/production-bug]
/Users/shawn/projects/app-hotfix jkl3456 [hotfix/urgent-fix]
# 删除已经不需要的工作树
$ git worktree remove fix/production-bug # 这会删除 ../app-main 目录
$ git worktree remove hotfix/urgent-fix # 这会删除 ../app-hotfix 目录
$ git worktree list
/Users/shawn/projects/app abc1234 [develop]
最佳实践建议
✅ 应该这样做
遵循命名约定
# 好的习惯:工作树目录名和分支名保持一致 git worktree add ../feature-auth feature/auth git worktree add ../release-v2.0 release/v2.0及时清理不需要的工作树
git worktree list # 定期查看 git worktree remove <name> # 及时清理在工作树中提交代码
# 工作树中完全可以 commit、push cd ../feature-auth git add . git commit -m "implement oauth" git push使用 lock 保护重要工作树
git worktree lock --reason "In use for v2.0 release" release
❌ 避免这样做
不要手动删除工作树目录
# ❌ 坏的做法 rm -rf ../feature-auth # ✅ 正确的做法 git worktree remove feature-auth不要在同一工作树中检出同一分支多次
# ❌ 会报错 git worktree add ../app1 main git worktree add ../app2 main # ✅ 如果必须这样做,加 -f 强制 git worktree add -f ../app1 main不要在工作树中执行
git checkout来切换分支# ❌ 这是对工作树功能的误用 cd ../feature-auth git checkout main # 这会改变工作树的分支 # ✅ 应该创建新的工作树 git worktree add ../main-check main不要忽视工作树的独立性
每个工作树都有独立的:
- 工作目录和文件
- 暂存区(staging area)
- HEAD 指向
但共享:
- Git 对象数据库(
.git/objects/) - 引用和历史
常见问题 Q&A
Q1: 工作树会占用很多磁盘空间吗?
A: 不会。工作树只是创建了新的工作目录和独立的索引文件,但Git 对象数据库是共享的。
例如,在前端项目中,最大的开销是 node_modules,每个工作树都有自己的 node_modules。如果你的项目 node_modules 是 500MB,3 个工作树就会占用 1.5GB。
但相比于每次切换分支都要重新 npm install(可能需要 2-3 分钟),这是值得的!
Q2: 工作树中的改动会影响其他工作树吗?
A: 不会。每个工作树都有独立的工作目录和暂存区。一个工作树中的未提交改动不会被其他工作树看到。
但一旦 commit 和 push,这些改动就会出现在其他工作树的历史中。
Q3: 能在工作树中直接 push 吗?
A: 完全可以!工作树中的 git 操作和原来的工作目录完全相同。commit、push、pull 都没问题。
唯一的限制是同一分支不能在多个工作树中同时被检出。
Q4: 删除分支后,它的工作树怎么办?
A: 工作树仍然存在,但变成了"分离 HEAD"状态。你可以继续在这个工作树中工作,但无法 push(因为分支已删除)。
通常这种情况下,应该及时删除工作树:
git worktree remove <name>
Q5: 工作树可以同步远程变化吗?
A: 可以。在工作树中执行 git pull 和 git fetch 完全正常,会自动同步。
与其他工具的搭配
VS Code 多工作区
你可以将多个工作树添加到 VS Code 的一个工作区中:
{
"folders": [
{
"path": "/Users/shawn/projects/app"
},
{
"path": "/Users/shawn/projects/app-main",
"name": "main"
},
{
"path": "/Users/shawn/projects/app-hotfix",
"name": "hotfix"
}
]
}
现在在 VS Code 中可以同时看到三个分支的代码!
持续集成
在 CI/CD 中,为了并行构建多个分支,Worktree 非常有用:
#!/bin/bash
# CI 脚本:同时构建多个版本
git worktree add build-main main
git worktree add build-develop develop
git worktree add build-release release/v2.0
# 并行构建
cd build-main && npm run build &
cd build-develop && npm run build &
cd build-release && npm run build &
wait # 等待所有构建完成
# 清理
git worktree remove build-main
git worktree remove build-develop
git worktree remove build-release
总结
Git Worktree 是一个强大而优雅的功能,它:
✅ 解决了什么问题:
- 避免频繁切换分支导致的依赖重装
- 允许同时在多个分支上工作
- 减少了开发中的上下文切换成本
✅ 适用的场景:
- 修复紧急 bug 而不中断当前工作
- 同时维护多个版本的代码
- 对比不同分支的差异
- 并行开发相关的功能分支
✅ 核心要点:
- Worktree 不是对分支的替代,而是一种文件系统视图
- 每个工作树都有独立的工作目录和索引,但共享 Git 对象库
- 同一分支不能在多个工作树中同时被检出
- 使用
remove而不是手动删除目录
如果你的开发流程涉及多分支并行开发或频繁的分支切换,Git Worktree 绝对值得一试。它会显著提升你的开发效率!
参考资源
- Git 官方文档 - git-worktree
- Git Worktree 的使用 - 张小凯的博客
- 深入了解 Git Worktree - 博客园
- Git 屠龙技:使用 Git Worktree 并行开发测试
Happy coding! 🚀