日常可以按这个口令走:一条任务 = 一条分支 +(可选)一棵工作树。
分支是必须的。改动有名字、能单独合并、出错能丢掉。工作树是可选的第二份目录:主文件夹继续停在 main,另一份目录去改那条任务。Grok、Codex 这类 Agent 并行之后,工作树更常见,但小修复并不需要它。
先问一句:你要不要同时停在 main 上。不要,就只开分支;要,再加工作树。
工作树是什么
Git 2.5 起,一个仓库可以同时检出多份工作目录。git init / git clone 得到的那份叫主工作树;git worktree add 再挂出来的叫链接工作树。每份目录有自己的 HEAD、暂存区和工作文件,提交对象和分支引用还是同一份底层仓库。
它解决的不是「切换分支变得更丝滑」。同一个文件夹里换分支,工作区不干净照样要 commit 或 stash。工作树的用法是:不去切原来的目录,把另一条分支摊到另一个文件夹。主目录可以继续停在 main,继续跑测试、开服务、留着未提交的改动。
这也比再 clone 一份轻。多出来的主要是那条分支的工作文件,对象不用再下一遍。
| 你实际要的 | 用什么 |
|---|---|
| 同时摊开几条分支,每条有自己的目录 | 工作树 |
| 当前目录改到一半,又要去另一条线干活 | 工作树(原目录不用 stash) |
| 只要看两个分支差了哪些行 | 不必开工作树,git diff main...feature 就够 |
| 要两份都能编译、启动、点一点的代码并排看着 | 工作树 |
审查 diff 本身不需要第二份目录;需要两份能跑起来的代码并排对比,才值得再开一棵。
没有新文件夹不能同时改,没有新分支分不清哪一拨
就用 a.txt。
你要 同时 做两件事:GPT 继续改现在的项目;Grok 也要改同一项目里的 a.txt。
一个文件夹做不到。打开 Grok 的版本,GPT 正在看的那份就没了。
所以复制出一个文件夹 .worktrees/demo,里面也有一份 a.txt。GPT 改 Dev/a.txt,Grok 改 demo/a.txt。这就是 新工作树 的作用:两份文件同时存在。
问题来了:改完怎么告诉 Git「这是两拨不同的修改」?如果两边都往 main 上提交,历史会缠在一起,也没法只留 Grok 的、或只丢 Grok 的。
所以给第二份文件起一个名字 grok/demo。Grok 的每次保存都记在这个名字下面。GPT 的保存还记在 main 下面。这就是 新分支 的作用:两拨修改分开记。
以后觉得 Grok 改得对,在 Dev 里执行合并,Dev/a.txt 才会加上 Grok 写的那些行。合并在哪个文件夹里跑、改哪份 a.txt,见后面「一个文件夹同一时间只能处于一个分支」。
| 缺了什么 | 结果 |
|---|---|
| 没有新文件夹 | 不能同时改。同一时刻只能摊开一份 a.txt |
| 没有新分支 | 能同时改文件,但 Git 分不清该合哪一拨、删哪一拨 |
先分清你要不要第二份目录
| 情况 | 怎么开 |
|---|---|
改动小,切走 main 一会儿没关系 |
只建分支 |
main 还要跑、Agent 另开一摊、修 hotfix 同时开发 |
分支 + 工作树 |
| 当前目录有未提交改动,又要去另一条分支 | 分支 + 工作树 |
Git 不允许同一条分支同时在两个文件夹里打开。日常把任务合进 main,就在已经处于 main 的主目录里 merge;不要为了合并,把工作树再切成一份 main。
看现在有哪些工位:
git worktree list
只开分支:一个人、一个文件夹
适合改动小、不需要同时停在 main 上。
git checkout main
git pull
git checkout -b grok/20260908-py-mypy-01-04
# 改代码、提交
git add -A
git commit -m "fix mypy"
git checkout main
git merge grok/20260908-py-mypy-01-04
# 或发 PR:git push -u origin grok/20260908-py-mypy-01-04
git branch -d grok/20260908-py-mypy-01-04
这时没有第二份目录。切回 main 后,文件就是主干的样子。git switch -c 和 git checkout -b 效果一样。
从哪条分支拉出新分支,新分支就从哪长出来。开任务前先回到 main 再 pull,避免把半成品功能分支当成起点。
分支加工作树:主目录继续停在 main
适合:主干还要编译、测试或跑服务;Agent 另开一摊;hotfix 和功能开发并行。
先停在 main,再让工作树从当前 HEAD 长出新分支:
git checkout main
git pull
git worktree add -b grok/20260908-py-mypy-01-04 \
.worktrees/20260908-py-mypy-01-04
cd .worktrees/20260908-py-mypy-01-04
# 只在这里改、测、commit
git add -A
git commit -m "fix mypy"
主目录继续停在 main,互不影响。工作树共享同一份对象库,不会像再 clone 一份那么占空间,也不会把未提交的改动从主目录里清掉。
脚本常把目录命名成 .worktrees/<项目>-<日期>-<任务>。记法很简单:一个文件夹,当前跟一条分支名。
合并:回主目录的 main
在主仓库目录做更清楚:
cd /path/to/repo
git checkout main
git merge grok/20260908-py-mypy-01-04
有远程就先在工作树里 git push -u origin grok/20260908-py-mypy-01-04,再开 PR,合并到 main。
提交发生在工作树里,分支名立刻对整个仓库可见(主目录也能 git log grok/demo)。哪个文件夹当前处于被合进去的那条,就在那个文件夹里 merge;日常合进 main,就去已经处于 main 的主目录。不要跑到任务工作树里去切 main。合并到底改哪份文件,见下一节。
删除:先撤这份目录,再删分支
cd /path/to/repo
git worktree remove .worktrees/20260908-py-mypy-01-04
git branch -d grok/20260908-py-mypy-01-04
# 已 push 且远程也要删
git push origin --delete grok/20260908-py-mypy-01-04
remove 失败,先看是不是 IDE 或终端还停在那个路径。工作区有未提交改动时,Git 也会拒绝删除;确认不要那些改动再加 --force。如果有人直接在访达里删了目录,元数据可能还在,用 git worktree list 核对,再 git worktree prune。
一个文件夹同一时间只能处于一个分支
别记复杂概念。就记两句话:
- 一个文件夹同一时间只能处于一个分支。
- 同一个分支不能同时在两个文件夹里打开。
工作树、分支,就是 两个文件夹 + 每个文件夹当前跟的那条名字。合到哪条名字、在哪个文件夹里合,就改哪份文件。
你的电脑上
- 文件夹
Dev现在处于main - 文件夹
.worktrees/demo现在处于grok/demo
a.txt 在两个文件夹里各有一份,内容可以不一样。
Dev/a.txt → 跟着 main
Dev/.worktrees/demo/a.txt → 跟着 grok/demo
合并是什么
合并 = 把 A 分支已经提交的改动,接到 B 分支 后面。
命令在 哪个文件夹里运行,就改 哪个文件夹里的文件。同时还要求:这个文件夹当前正处于 B(被合进去的那条)。
所以要「把 grok/demo 合进 feature」:
- 先让某个文件夹处于
feature - 再在这个文件夹里执行
git merge grok/demo - 只有这个文件夹里的
a.txt会变成合完的样子 Dev如果还处于main,Dev/a.txt不会动
未提交的改动不算「已经接到 B 后面」。要先在 A 那份目录里 commit,再去 B 那份目录里 merge。
「不能同时打开」
如果 Dev 已经处于 feature,你就不能在 .worktrees/demo 里再切到 feature。
不是神秘规则,就是避免两个文件夹都以为自己是最新的 feature,改崩了。
main 同理:主目录已经停在 main 时,不要在工作树里再检出一份 main。
想让主项目里的 a.txt 变
去 Dev,确认当前是 main,然后:
git merge grok/demo
这样 Dev/a.txt 才会更新。人在 .worktrees/demo 里怎么改、怎么 commit,都不会自动改写 Dev/a.txt。
用 a.txt 走一遍
假设一开始 main 里有 a.txt,内容是:
hello
主目录:
Dev/a.txt → hello (处于 main)
然后开工作树:
git worktree add -b grok/demo .worktrees/demo
此时两份 a.txt 内容相同(都从当时的 main 拷出来):
Dev/a.txt → hello (main)
Dev/.worktrees/demo/a.txt → hello (grok/demo)
你只在工作树里改并提交:
# .worktrees/demo/a.txt
hello
from grok
cd .worktrees/demo
git add a.txt
git commit -m "grok 改了 a.txt"
合并前:
Dev/a.txt → hello ← 主目录还是旧的
Dev/.worktrees/demo/a.txt → hello / from grok ← 只在这份目录里
Dev 里打开 a.txt 看不到 from grok。
git log main 也没有这次提交;git log grok/demo 才有。对象库里这次提交已经在了,只是 main 还没接到它后面。
合并必须在已经处于 main 的文件夹里做:
cd /path/to/Dev
git checkout main # 确认当前是 main;已经是就不用切
git merge grok/demo
合并后:
Dev/a.txt → hello / from grok
Dev/.worktrees/demo/a.txt → hello / from grok (还在,直到你 remove)
这时 Dev/a.txt 才拿到新内容。再 git worktree remove .worktrees/demo,只剩主目录这份。
若两边都改了 a.txt,同一行还改得不一样,merge 会冲突。冲突要在执行 merge 的那个文件夹里解开——上面这次就是 Dev。
一张对照
| 步骤 | 单目录 | 工作树 |
|---|---|---|
| 开始 | checkout -b |
worktree add -b 分支 目录 |
| 干活 | 同一文件夹切来切去 | 进 .worktrees/... |
| 合并 | 回 main 再 merge / PR |
同样回 主目录的 main 再 merge |
| 结束 | branch -d |
先 worktree remove,再 branch -d |
几个容易踩的点
- 一个文件夹同一时间只能处于一个分支。 要换名字,就在这个文件夹里
checkout/switch;要同时摊开另一条,开第二份目录。 - 同一个分支不能同时在两个文件夹里打开。
Dev已经处于feature,.worktrees/demo就不能再切到feature。main同理。 - 合并不等于收尾。 PR 合进主干之后,工作树目录还在。先
worktree remove,再branch -d。 - 脏工作区删不掉。 提交或 stash 干净再 remove;
pwd还在那棵树里时,先cd回主仓库。 - 工作树不是第二份远程。 它共享对象库和分支。在工作树里 commit 完,主目录立刻能
git log grok/demo;git log main和主目录里的文件要等 merge。 - 工作树也不会让
git switch免 stash。 脏的还是那棵树的脏。要留着未提交改动去另一条线,开第二份目录,而不是在同一份目录里硬切。
官方说明见 git-worktree。
习惯
- 小修复:只建分支。
- Grok / Codex 并行、要留着
main能跑:才建 worktree。 - 合并目标永远是
main(或 release)。 - 干完按「remove 目录 → 删分支」收尾。
一条任务一条分支,这个习惯比工作树更重要。工作树只是让你在不停主干的前提下,多开一个干净工位。




