不用再反复 stash:用 Git Worktree 同时开发多个分支

📅 2026/7/22 0:21:37
不用再反复 stash:用 Git Worktree 同时开发多个分支
很多开发者都遇到过这样的场景你正在一个功能分支上写代码修改了十几个文件程序暂时还跑不起来也不适合提交。就在这时同事突然告诉你线上出现了一个紧急问题需要马上从main分支拉出一个 Hotfix。按照传统做法你可能会先执行gitstashgitswitch maingitpullgitswitch-chotfix/xxx修复完成以后再回到原来的分支gitswitch feature/xxxgitstash pop如果只是偶尔操作一次这种方式倒也能用。但当你同时维护多个 PR、审查别人代码、修复线上问题或者需要在不同版本之间反复测试时频繁执行stash和switch很容易让工作现场变得混乱。Git 其实提供了一个非常适合这种场景的功能Git Worktree。内容结构概览本文将介绍Git Worktree 是什么它与git switch、git stash和重新clone有什么区别如何创建、查看和删除 Worktree如何在开发新功能的同时处理紧急 Bug多个 PR、代码审查和版本测试中的使用方法常见问题与容易踩到的坑。一、Git Worktree 是什么Git Worktree 可以让一个 Git 仓库同时拥有多个工作目录每个目录分别检出不同的分支。普通情况下一个仓库只有一个代码目录my-project/这个目录某一时刻只能显示一个分支。你从main切到feature/login后目录中的代码也会随之变化。使用 Worktree 后可以变成这样code/ ├── my-project/ # main ├── my-project-feature-login/ # feature/login └── my-project-hotfix/ # hotfix/payment-timeout三个目录属于同一个 Git 仓库但分别检出了三个不同分支。你可以在第一个目录运行稳定版代码在第二个目录继续开发新功能同时在第三个目录修复线上问题。它们彼此独立不需要来回切换分支也不需要把未完成的代码临时藏进stash。Git 官方把最初通过git clone或git init创建的工作目录称为main worktree后来通过git worktree add创建的目录称为linked worktree。一个普通仓库可以拥有一个主工作区以及零个或多个附加工作区。(Git)二、Worktree 到底共享什么Worktree 并不是重新复制出一份完整的 Git 仓库。多个 Worktree 会共享Git 提交历史对象数据库分支和标签引用远程仓库信息大部分 Git 配置。但每个 Worktree 都拥有自己的工作目录当前检出的分支HEAD暂存区也就是 index未提交修改未跟踪文件。可以把它理解为所有工作区共用一个仓库档案室但每个工作区都有自己的桌面。共享的 Git 仓库 commits / objects / refs │ ┌──────────────┼──────────────┐ │ │ │ Worktree 1 Worktree 2 Worktree 3 main feature hotfix 独立文件 独立文件 独立文件 独立 HEAD 独立 HEAD 独立 HEAD 独立 index 独立 index 独立 indexGit 官方文档也说明不同 Worktree 的HEAD等状态彼此独立而普通分支引用通常由各个 Worktree 共享。(Git)三、为什么不直接重新 clone 一份当然可以重新 clonegitclone gitgithub.com:example/project.git project-maingitclone gitgithub.com:example/project.git project-featuregitclone gitgithub.com:example/project.git project-hotfix但这样会得到三套独立仓库project-main/.git/ project-feature/.git/ project-hotfix/.git/对于大型仓库这意味着Git 对象可能被重复存储每个仓库都要单独执行fetchRemote、Hook 和部分配置可能不一致本地仓库数量越来越多很难管理创建新环境的速度比 Worktree 慢。Worktree 复用的是同一套 Git 仓库数据因此创建速度通常很快也更加节省空间。不过要注意Worktree 只共享 Git 数据并不会共享所有构建产物。例如下面这些目录通常仍然是各自独立的node_modules/ target/ dist/ vendor/ .venv/因此两个大型 Rust Worktree 仍可能分别生成自己的target目录占用较多磁盘空间。四、查看当前已有的 Worktree进入任意一个工作区执行gitworktree list可能得到/Users/mac/code/my-project a1b2c3d [main] /Users/mac/code/my-project-login d4e5f6g [feature/login] /Users/mac/code/my-project-hotfix h7i8j9k [hotfix/payment-timeout]每一行分别表示工作目录路径 当前提交 当前分支查看更详细的信息gitworktree list--verbose需要在 Shell 脚本中处理输出时可以使用gitworktree list--porcelain五、为已有分支创建 Worktree假设仓库中已经存在feature/login当前主仓库位于~/code/my-project可以执行cd~/code/my-projectgitworktreeadd\../my-project-feature-login\feature/loginGit 会创建新目录~/code/my-project-feature-login并在里面检出feature/login。现在可以直接进入新目录cd../my-project-feature-logingitstatus输出会显示On branch feature/login此时主目录仍然保持原来的分支和所有未提交修改完全不会受到影响。六、创建新分支时同时创建 Worktree这是最常用的方式。假设要从最新的origin/main创建一个 Hotfixgitfetch origingitworktreeadd\-bhotfix/payment-timeout\../my-project-payment-timeout\origin/main这条命令同时完成三件事基于origin/main创建hotfix/payment-timeout创建目录../my-project-payment-timeout在新目录中检出该分支。它的通用格式是gitworktreeadd-b新分支名新目录起始提交起始提交可以是main origin/main release/v2 v2.1.0 某个 commit hash例如gitworktreeadd\-bfix/readme-typo\../my-project-fix-readme\origin/main我通常建议明确写出origin/main而不是省略起点。这样可以避免本地main很久没有更新导致新分支建立在旧提交上。七、实战开发未完成时紧急修复 Bug假设你正在ethereumjs-monorepo中开发一个功能当前分支有不少未提交修改gitstatus输出类似On branch feature/trie-refactor Changes not staged for commit: modified: packages/trie/src/trie.ts modified: packages/trie/test/trie.spec.ts现在你需要基于上游最新代码修复一个 README typo。不需要stash直接执行cd~/code/ethereumjs-monorepogitfetch upstreamgitworktreeadd\-bfix-util-readme-typo\../ethereumjs-fix-util-readme-typo\upstream/master如果项目默认分支是main则改成gitworktreeadd\-bfix-util-readme-typo\../ethereumjs-fix-util-readme-typo\upstream/main进入新的工作区cd../ethereumjs-fix-util-readme-typo完成修改gitaddpackages/util/README.mdgitcommit-mdocs(util): fix README typogitpush-uorigin fix-util-readme-typo此时目录结构可能是~/code/ ├── ethereumjs-monorepo/ │ └── feature/trie-refactor保留未提交修改 │ └── ethereumjs-fix-util-readme-typo/ └── fix-util-readme-typo准备提交 PR两个工作区互不干扰。修复完成后你可以在浏览器里提交 PR也可以使用 GitHub CLIghprcreate然后回到原来的目录继续开发cd../ethereumjs-monorepo不需要执行gitstashgitswitchgitstash pop也不需要重新启动或恢复原来分支的工作状态。八、PR 合并后如何清理先确认工作区中没有需要保留的修改cd../ethereumjs-fix-util-readme-typogitstatus然后回到主仓库或者任意其他 Worktreecd../ethereumjs-monorepo删除附加工作区gitworktree remove../ethereumjs-fix-util-readme-typo注意git worktree remove会删除对应的工作目录但不会自动删除分支。如果本地分支已经合并可以继续执行gitbranch-dfix-util-readme-typo清理远程已经删除的跟踪引用gitfetch--pruneorigin完整清理流程如下gitworktree remove../ethereumjs-fix-util-readme-typogitbranch-dfix-util-readme-typogitfetch--pruneoriginGit 官方建议使用git worktree remove删除附加 Worktree而不是直接删除目录。(Git)九、为什么不能直接执行 rm -rf下面这种操作并不推荐rm-rf../my-project-hotfix因为目录虽然被删除了但主仓库内部可能仍然保留该 Worktree 的管理信息。这时执行gitworktree list可能仍会看到已经不存在的工作区。可以使用gitworktree prune清理失效记录。正式删除时最好始终使用gitworktree remove工作区路径如果目录已经被手动删除再执行gitworktree prune查看哪些记录将被清理但暂时不执行gitworktree prune --dry-run--verbose十、工作区中有未提交代码时怎么办假设某个 Worktree 中仍然存在修改gitstatus显示Changes not staged for commit此时执行gitworktree remove../my-project-hotfixGit 通常会拒绝删除因为这会导致未提交内容丢失。正确做法是先决定如何处理这些代码方案一提交代码gitadd.gitcommit-mwip: preserve current work方案二暂存修改gitstash push-u方案三确认不再需要强制删除gitworktree remove--force../my-project-hotfix--force可能造成代码永久丢失使用前应确认该目录中没有任何需要保留的文件。十一、一个分支不能同时在两个 Worktree 中检出假设main已经在主工作区中使用gitworktree list输出/Users/mac/code/my-project a1b2c3d [main]此时再执行gitworktreeadd../my-project-main-copy mainGit 通常会拒绝并提示fatal: main is already checked out这是为了防止两个工作目录同时修改同一个分支指针。假设你只是想基于main做实验应该创建一个新分支gitworktreeadd\-bexperiment/new-parser\../my-project-new-parser\main或者创建一个 detached HEAD 工作区gitworktreeadd\--detach\../my-project-main-test\main默认情况下Git 会阻止同一个分支同时出现在多个 Worktree 中。(Git)十二、用 Worktree 审查别人的 PRWorktree 不仅适合开发也非常适合代码审查。假设要审查 GitHub PR#1234可以先拉取 PR 分支gitfetch origin pull/1234/head:review/pr-1234然后创建 Worktreegitworktreeadd\../my-project-review-1234\review/pr-1234进入该目录cd../my-project-review-1234在不影响当前开发环境的情况下运行gotest./...或者cargotest审查结束后cd../my-projectgitworktree remove../my-project-review-1234gitbranch-Dreview/pr-1234这样每个 PR 都可以拥有独立的测试环境不需要频繁切换当前仓库的分支。十三、同时维护多个 PR对于经常给开源项目提交贡献的人Worktree 非常实用。假设正在处理三个 PRfix/readme-typo fix/parser-comment test/new-analyzer可以分别创建gitfetch upstreamgitworktreeadd\-bfix/readme-typo\../project-fix-readme\upstream/maingitworktreeadd\-bfix/parser-comment\../project-fix-parser-comment\upstream/maingitworktreeadd\-btest/new-analyzer\../project-new-analyzer\upstream/main目录结构变成code/ ├── project/ ├── project-fix-readme/ ├── project-fix-parser-comment/ └── project-new-analyzer/每个目录可以分别打开一个终端或 IDE 窗口。等待第一个 PR 的 CI 时可以直接进入第二个目录继续工作cd../project-fix-parser-comment这种方式比不断switch分支更加自然也不容易把不同 PR 的文件混在一起。不过同时提交多个 PR 时仍应注意项目维护者的体验。相关的小修改最好合并处理不要为了增加提交数量而拆成大量价值很低的 PR。十四、测试旧版本或历史提交Worktree 也很适合临时查看某个版本。例如需要测试v1.5.0gitworktreeadd\--detach\../my-project-v1.5.0\v1.5.0进入该目录cd../my-project-v1.5.0完成编译或测试后删除cd../my-projectgitworktree remove../my-project-v1.5.0因为使用了--detach这个工作区没有绑定普通分支适合复现旧版本 Bug比较两个版本的性能编译某个 Tag检查历史提交执行临时实验。十五、移动 Worktree不要优先使用普通的mv命令移动工作区。推荐执行gitworktree move\../my-project-hotfix\../worktrees/my-project-hotfix如果已经使用 Finder 或mv手动移动了目录导致 Git 的路径记录失效可以尝试gitworktree repair也可以指定路径gitworktree repair../worktrees/my-project-hotfix当前 Git Worktree 命令提供了move和repair分别用于移动工作区和修复工作区关联。(Git)十六、外置硬盘上的 Worktree有时 Worktree 放在移动硬盘、网络磁盘或者临时挂载目录中。磁盘没有挂载时Git 可能认为该工作区已经失效并在后续清理中删除管理记录。可以锁定该 Worktreegitworktree lock\--reasonStored on external SSD\/Volumes/SSD/my-project-release解除锁定gitworktree unlock\/Volumes/SSD/my-project-release日常放在本机固定目录中的 Worktree 通常不需要执行lock。这个功能主要用于路径可能暂时不可访问的工作区。Git 官方命令集中也包含lock和unlock。(Git)十七、推荐的目录组织方式不建议把 Worktree 建在主仓库内部my-project/ └── worktrees/ └── feature-login/这样可能导致IDE 重复索引大量文件搜索结果出现多个版本文件监听器监控额外目录构建工具误扫描另一个工作区主仓库出现复杂的未跟踪目录。更推荐使用并列目录~/code/ ├── ethereumjs-monorepo/ ├── ethereumjs-fix-readme/ └── ethereumjs-trie-refactor/或者统一放在专门目录~/code/ ├── ethereumjs-monorepo/ └── worktrees/ └── ethereumjs/ ├── fix-readme/ ├── trie-refactor/ └── review-pr-1234/对应命令gitworktreeadd\-bfix/readme\~/code/worktrees/ethereumjs/fix-readme\upstream/main目录名称不必与分支名完全相同但保持一致会更容易管理。十八、Worktree、stash 和重新 clone 怎么选三种方式分别适合不同场景。场景推荐方式临时离开当前分支几分钟git stash两个任务需要并行开发Worktree同时运行新旧两个版本Worktree审查多个 PRWorktree复现某个历史版本问题Worktree需要完全不同的 Remote 和 Git 配置重新 clone要破坏性修改.git数据重新 clone多个 AI Agent 同时处理不同任务Worktree简单来说stash是临时把桌面收起来Worktree 是再准备一张桌子重新 clone 则是再租一个办公室。十九、常用命令速查查看全部 Worktreegitworktree list为已有分支创建 Worktreegitworktreeadd目录已有分支创建新分支并创建 Worktreegitworktreeadd-b新分支目录起点创建 detached HEAD 工作区gitworktreeadd--detach目录提交或标签删除 Worktreegitworktree remove目录强制删除gitworktree remove--force目录移动 Worktreegitworktree move旧路径新路径清理失效记录gitworktree prune修复关联gitworktree repair锁定外置工作区gitworktree lock目录解除锁定gitworktree unlock目录二十、总结Git Worktree 解决的问题并不复杂它允许同一个 Git 仓库同时在多个目录中检出不同分支。过去我们常常需要在一个目录中不断执行gitstashgitswitchgitstash pop使用 Worktree 后很多时候只需要切换目录cd../project-featurecd../project-hotfix每个任务都有自己的目录、分支、暂存区和未提交修改。正在运行的开发服务器、IDE 窗口以及测试环境也可以继续保持不需要因为切换分支而反复重启。它尤其适合同时维护多个 PR开发新功能时紧急修复 Bug审查其他人的代码对比多个版本同时运行多个测试环境让多个自动化工具或 AI Agent 并行工作。掌握 Worktree 后Git 分支不再只是需要不断切换的状态而可以变成多个真正并行存在的工作空间。对于经常参与大型开源项目、同时维护多个分支的开发者来说这可能是 Git 中最值得养成使用习惯的功能之一。参考资料Git 官方git-worktree文档对 Worktree 的创建、查看、删除、移动、清理、锁定和修复等行为给出了完整定义。(Git)