前言

你是否经历过这样的场景?

当你正在为某个功能跑漫长的测试时,突然来了一个紧急的 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:紧急修复

现在需要:

  1. develop 分支继续开发新功能(已有 50% 的工作)
  2. 同时在 main 分支修复一个生产 bug
  3. 并检查 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]

最佳实践建议

✅ 应该这样做

  1. 遵循命名约定

    # 好的习惯:工作树目录名和分支名保持一致
    git worktree add ../feature-auth feature/auth
    git worktree add ../release-v2.0 release/v2.0
    
  2. 及时清理不需要的工作树

    git worktree list  # 定期查看
    git worktree remove <name>  # 及时清理
    
  3. 在工作树中提交代码

    # 工作树中完全可以 commit、push
    cd ../feature-auth
    git add .
    git commit -m "implement oauth"
    git push
    
  4. 使用 lock 保护重要工作树

    git worktree lock --reason "In use for v2.0 release" release
    

❌ 避免这样做

  1. 不要手动删除工作树目录

    # ❌ 坏的做法
    rm -rf ../feature-auth
    
    # ✅ 正确的做法
    git worktree remove feature-auth
    
  2. 不要在同一工作树中检出同一分支多次

    # ❌ 会报错
    git worktree add ../app1 main
    git worktree add ../app2 main
    
    # ✅ 如果必须这样做,加 -f 强制
    git worktree add -f ../app1 main
    
  3. 不要在工作树中执行 git checkout 来切换分支

    # ❌ 这是对工作树功能的误用
    cd ../feature-auth
    git checkout main  # 这会改变工作树的分支
    
    # ✅ 应该创建新的工作树
    git worktree add ../main-check main
    
  4. 不要忽视工作树的独立性

    每个工作树都有独立的:

    • 工作目录和文件
    • 暂存区(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 pullgit 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 绝对值得一试。它会显著提升你的开发效率!


参考资源


Happy coding! 🚀