问题背景

在 Git 中,Work Tree(工作树/工作区) 是一个非常核心但容易混淆的概念。很多开发者经常需要在多个分支上并行工作,传统方式需要:

  1. 频繁切换分支 (git stash / git switch)

    • 痛点:如果你正在开发一个功能,编译了一半,突然要修一个紧急 Bug。需要暂存代码、切换分支、修完、再切回来。涉及复杂编译环境时很容易出错或丢失上下文。
  2. 克隆多个仓库 (git clone)

    • 痛点:虽然互不干扰,但浪费磁盘空间(尤其是大型项目,.git 对象库会很大),且需要分别维护两个远程连接。

核心困惑:

  • Work Tree 和 Branch 有什么区别?
  • 新建 worktree 会自动新建分支吗?
  • 如果主分支和 worktree 都在修改,最后怎么合并?

解决方案

1. 核心概念:三区对比

理解 Work Tree 的关键在于区分它和”仓库”:

区域说明文件状态
Work Tree (工作树/工作区)就是你电脑文件系统上的那个文件夹(如 my-project/)。你在这里编写代码、创建文件、删除文件。Untracked(未跟踪)或 Modified(已修改)
Repository / .git directory (版本库)隐藏文件夹 .git 里的内容。存储所有提交历史、分支信息、配置等。Git 内部格式,不可直接编辑
Index / Staging Area (暂存区)位于 Work Tree 和 Repository 之间的缓冲区域。通过 git add 把改动放到这里,准备提交。Staged(已暂存)

关系图:

flowchart LR
    subgraph Git流程["Git 三区流程"]
        WT[Work Tree<br/>工作树] -->|git add| SA[Staging Area<br/>暂存区]
        SA -->|git commit| Repo[Repository<br/>版本库]
        Repo -->|git checkout| WT
    end

    style WT fill:#e3f2fd,stroke:#1565c0
    style SA fill:#fff3e0,stroke:#ef6c00
    style Repo fill:#e8f5e9,stroke:#2e7d32

2. 形象比喻

想象你在画画:

概念比喻
Work Tree (工作树)就是你的画架和画布。你在这里挥洒笔墨,进行实际的创作。此时画还是”未定稿”状态。
Staging Area (暂存区)是一个展示台。你觉得画得不错了,就把画拿过去摆好,准备让人看。
Repository (版本库)是一个档案馆。你确认展示台上的画没问题了,就拍个照存档。这个存档的照片是永久的,以后随时可以拿出来看。

3. Branch vs. Worktree 核心区别

很多开发者容易混淆 Branch(分支)Worktree(工作树命令),因为它们经常配合使用。简单来说:分支是逻辑上的”线”,而 worktree 是物理上的”坑”。

flowchart TD
    subgraph Physical ["💻 物理层面 (文件夹/工作台)"]
        WT1[📁 主工作区<br/>/project]
        WT2[📁 副工作区<br/>/project-fix]
    end

    subgraph Logical ["🌲 逻辑层面 (Git 分支/时间线)"]
        BranchA[🌿 分支:feature-main]
        BranchB[🌿 分支:fix-bug]
        Repo[("🗄️ 仓库历史")]
    end

    WT1 -->|检出 | BranchA
    WT2 -->|检出 | BranchB
    BranchA --> Repo
    BranchB --> Repo

    style WT1 fill:#e1f5fe,stroke:#01579b
    style WT2 fill:#e1f5fe,stroke:#01579b
    style BranchA fill:#fff9c4,stroke:#fbc02d
    style BranchB fill:#fff9c4,stroke:#fbc02d

详细对比表:

特性Git Branch (分支)Git Worktree (工作树命令)
本质逻辑指针。指向某个提交(Commit)的引用。物理目录。关联到同一个仓库的另一个工作文件夹。
操作对象改变当前目录下的文件内容。创建新的目录,里面包含另一套文件。
并发能力同一时间,一个仓库只能在一个目录里检出一个分支同一时间,一个仓库可以在多个目录里检出多个分支
存储空间几乎不占空间(只是一个指针)。占用文件空间,但共享 .git 对象库(比 clone 省空间)。
典型命令git checkout, git switchgit worktree add, git worktree list
比喻就像书的目录页码。你只能翻到某一页看。就像复印了多份书,每份书摊开在不同的桌子上,可以看不同的页。

关键约束:同一个分支不能同时在多个 worktree 中被检出。

4. 内部结构:为什么 worktree 省空间?

很多人担心创建 worktree 会占用双倍磁盘空间,其实不会。

flowchart TD
    subgraph Disk ["💾 磁盘结构"]
        MainDir[("📁 主工作区<br/>/project")]
        SubDir[("📁 工作树目录<br/>/project-hotfix")]
    end

    subgraph GitSystem ["⚙️ Git 系统内部"]
        MainGit[("📁 主 .git 目录<br/>/project/.git")]
        SubGitFile[("📄 文本文件<br/>/project-hotfix/.git")]
        LinkedConfig[("📁 关联配置<br>.git/worktrees/hotfix")]
        Objects[("🗄️ 对象数据库<br>.git/objects<br/><b>所有数据都存在这里</b>")]
    end

     分支选择
    Decision -->|紧急修 Bug| A["创建修复型 Worktree"]
    Decision -->|review 同事代码 | B["创建审查型 Worktree"]
    Decision -->|长期并行开发 | C["创建长期 Worktree"]

     工作状态
    Cmd1 --> Work["💻 在新目录独立工作"]
    Cmd2 --> Work
    Cmd3 --> Work

    Work --> Commit["✅ 提交代码"]
    Commit --> Check{"任务完成了吗?"}

    %% 清理
    Check -- 否 --> Work
    Check -- 是 --> Remove["`git worktree remove ../目录名`"]
    Remove --> Clean["🧹 清理文件夹 (手动 rm)"]
    Clean --> End["🏁 结束"]

    style Start fill:#e1f5fe,stroke:#01579b
    style End fill:#e1f5fe,stroke:#01579b
    style Remove fill:#ffebee,stroke:#c62828

7. 合并流程详解

核心原则:你合并的是”分支(Branch)“,而不是”工作树(Worktree)”。

git worktree 只是给你提供了多个文件夹来同时操作不同的分支。合并操作和普通 Git 流程完全一样,只是你需要注意在哪个文件夹里执行合并命令

合并流程图:

flowchart LR
    subgraph 开发阶段["📝 开发阶段"]
        WT1[📁 主工作区<br/>分支:feature-A] -->|git commit| C1["✅ Commit A"]
        WT2[📁 副工作区<br/>分支:feature-B] -->|git commit| C2["✅ Commit B"]
    end

    subgraph 合并阶段["🔀 合并阶段"]
        WT1 -->|在主工作区执行| Merge["`git merge feature-B`"]
        Merge --> C3["✅ 合并完成"]
    end

    subgraph 清理阶段["🧹 清理阶段"]
        C3 --> Remove["`git worktree remove`"]
        Remove --> Done["🏁 副工作区删除"]
    end

    style Merge fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px

操作步骤:

假设场景:

  • 主工作区 (/project):正在开发 feature-login 分支
  • 副工作区 (/project-fix):新建的 worktree,正在修 master 分支的 bug

第一阶段:并行开发

# 1. 在主工作区
cd /project
# 修改文件...
git add .
git commit -m "开发登录功能"
 
# 2. 在副工作区
cd /project-fix
# 修改文件...
git add .
git commit -m "修复紧急 bug"

第二阶段:执行合并

问题:我应该在哪里运行 git merge

规则:git merge 是把别的分支合并到当前分支。因此,你必须进入目标分支所在的工作区

# 1. 回到主工作区 (因为 feature-login 分支在这里)
cd /project
 
# 2. 确认当前分支
git branch
# 输出应该显示 * feature-login
 
# 3. 执行合并
git merge master
# 或者如果是远程分支:git merge origin/master

⚠️ 重要限制:不能/project-fix 里运行 git checkout feature-login 然后合并。因为 feature-login 已经被 /project 占用了!Git 禁止同一个分支在两个 worktree 同时检出。

第三阶段:清理工作树

# 1. 回到主工作区 (不能在要删除的目录里删)
cd /project
 
# 2. 删除 worktree 关联
git worktree remove ../project-fix
 
# 3. (可选) 手动删除文件夹
rm -rf ../project-fix

8. 三种常见合并场景

场景 A:把副工作区的修改,合并到主工作区

  • 主工作区:feature 分支
  • 副工作区:master 分支 (修了 bug)
  • 操作:
    1. 在副工作区提交 master
    2. 切换到主工作区
    3. 在主工作区运行 git merge master
    4. 删除副工作区

场景 B:把主工作区的修改,合并到副工作区

  • 主工作区:master 分支
  • 副工作区:feature 分支 (开发新功能)
  • 操作:
    1. 在主工作区提交 master
    2. 切换到副工作区
    3. 在副工作区运行 git merge master (把主分支的新代码同步到功能分支)
    4. 继续开发,最后再把 feature 合并回 master

场景 C:两个工作区开发完后,合并到一个新分支

  • 主工作区:dev-A
  • 副工作区:dev-B
  • 操作:
    1. 两边都提交完
    2. 随便进一个工作区(比如主工作区)
    3. 创建新分支:git checkout -b integration
    4. 合并两边:git merge dev-A (当前就是,跳过) 然后 git merge dev-B
    5. 解决冲突(如果有)

适用场景

场景描述推荐命令
紧急修复 (Hotfix)主目录正在开发大功能,突然线上报错git worktree add ../hotfix master
代码审查 (Code Review)同事提了 PR,创建 worktree 检出同事的分支查看git worktree add ../review pr-branch
长期编译/测试某个分支编译需要 1 小时,在 worktree 里编译,不耽误写代码git worktree add -b build ../build-test
多版本对比同时检出 v1.0v2.0 两个 tag 到不同目录,直观对比git worktree add ../v1.0 v1.0

常见疑问解答 (Q&A)

Q1: 如果两个工作区修改了同一个文件,合并时会冲突吗?

  • A: 会。但这和普通的分支合并冲突一样。Git 会根据文件内容判断冲突,而不是根据文件夹。你需要在合并时手动解决冲突。

Q2: 我可以在副工作区直接 git push 吗?

  • A: 可以!git worktree 只是本地文件夹的变化。只要你提交了(commit),你就可以在任何工作区运行 git push 把代码推送到远程仓库。

Q3: 合并完后,副工作区的文件夹里的文件怎么办?

  • A: 合并操作更新的是分支的历史。副工作区文件夹里的文件依然保持它分支原来的样子(除非你也在那个文件夹里做了合并操作)。通常合并完后,我们就不需要那个副工作区文件夹了,直接删除即可。

Q4: 分支名冲突怎么办?

  • A: 你不能在 worktree 里创建一个和主仓库已存在分支同名的新分支。如果主仓库已经有 feature-A 了,你不能运行 git worktree add -b feature-A ../path,Git 会报错说分支已存在。这时你应该直接用 git worktree add ../path feature-A(检出已有分支)。

注意事项

  1. 不要手动删除文件夹

    • 如果直接在文件管理器里删除了 worktree 文件夹,Git 并不知道
    • 需要运行 git worktree prune 来清理主仓库里的残留记录
  2. 分支锁定

    • 只要一个 worktree 正在使用某个分支,其他 worktree 就不能切换到这个分支(除非强制)
  3. 合并规则

    • 合并的是”分支”,而不是”文件夹”
    • 你想把分支 B 合并到分支 A,就必须进入检出分支 A 的那个 worktree 目录执行 git merge B
  4. 裸仓库限制

    • git worktree 不能在裸仓库上使用,必须是非裸仓库

常见误区

误区 1:以为新建了文件夹就是新建了分支

  • 真相:文件夹只是”皮”,分支才是”魂”。如果你用 git worktree add ../path master,你在文件夹里改代码,其实就是直接在 master 分支上改。如果你忘了切分支,可能会把未完成的代码提交到 master
  • 建议:如果是开发新功能,务必加上 -b 参数新建一个分支,这样更安全

误区 2:想”合并文件夹”

  • 真相:不要想着”合并文件夹”,要想”合并分支”。worktree 只是容器,合并发生在分支层面

总结

  1. Branch时间线(过去、现在、未来的代码版本)
  2. Worktree工作台(你当前手边摆着的代码文件夹)
  3. 以前你只有一个工作台,想换时间线得把台面清空(切换分支)
  4. 现在你可以开多个工作台(git worktree add),每个台面摆不同的时间线,互不干扰,且共用同一个仓库核心

命令速记:

  • 隔离开发新功能? ➡️ 用 -b (新建分支)
  • 修现有分支的 Bug? ➡️ 不用 -b (检出已有分支)
  • 临时看一眼? ➡️ 什么都不加 (游离指针)

大多数开发场景下,为了安全起见,推荐使用 -b 新建一个专属分支,除非你明确知道自己在操作现有分支。