```html
三个代理,三个worktree,三个文件夹,三个分支。你以为每个文件夹都有自己的git设置,其实没有。git-worktree文档只有一句话:默认情况下,仓库配置文件在所有worktree之间共享。第一个跑git config user.email的代理,会把三个代理的身份一起改掉。
这是并行跑编码代理时最先踩到的坑,也是这份清单里的第一条。剩下九条,决定冲突长什么样、push和fetch怎么走、以及没人清理的worktree最后会变成什么状态。
1. 让每个worktree有自己的设置
不开extensions.worktreeConfig,在worktree里写的每一条git config都会落进那个共享文件,本来只想给一个任务用的设置,会作用到所有任务上。每个仓库开一次,然后用--worktree写单任务配置:
# 每个仓库执行一次git config extensions.worktreeConfig true# 只在某个任务的worktree里执行cd ../fix-logingit config --worktree user.name "Codex (fix-login)"git config --worktree user.email codex@example.com开启后,git-config会在共享文件之上加载config.worktree,其中的设置会覆盖其他配置文件。给每个任务单独设身份只是用法之一,之后用git log --author=codex就能直接找到这个代理的提交。用git rev-parse --git-path config.worktree可以确认某个worktree实际写的是哪个文件。
两个注意点:如果core.bare或core.worktree在共享配置里,要挪到主worktree的config.worktree;另外,较老版本的Git会拒绝访问带这个扩展的仓库。
2. 让代理之间的冲突可读
默认冲突样式只显示你这边和对方那边,把原始版本省掉了。两个代理重写了同一个函数时,原始版本恰恰是判断谁改了什么的关键。merge.conflictStyle设为zdiff3,会在diff3的基础上,把冲突边缘两侧相同的行去掉。
git config --global merge.conflictStyle zdiff3# 按指定样式重新生成已冲突文件的标记git checkout --conflict=zdiff3 src/login.js第二条命令来自git-checkout:它按你指定的样式重新生成冲突标记,覆盖配置。
3. diff和merge用同一套算法
Git的ort合并策略已经默认用histogram对齐改动,但普通diff默认还是Myers。也就是说,你review时看到的diff,和真正落地时的合并,可能把同一批行配成不同的对。diff.algorithm设为histogram,两边就统一了。
git config --global diff.algorithm histogram一项发表在Empirical Software Engineering上的实证研究(Nugroho、Hata和Matsumoto)认为,在应用补丁的场景下,Histogram比Myers更适合呈现代码改动。
4. 同一个冲突只解一次
两个代理分支碰到同样的行,同一个冲突你会解不止一次:试合并时一次,rebase时又一次,下一轮rebase再来一次。rerere.enabled打开后,第一次的解法会被记录下来并自动重放。解错了用git rerere forget src/login.js清掉记录。
git config --global rerere.enabled true# 某条记录解错了git rerere forget src/login.js记录缓存在rr-cache目录,属于仓库共享目录,和objects、refs并列。在一个worktree里解掉的冲突,会在同仓库的任何其他worktree里重放。记录默认保留60天后清理。
5. 让push不用停下来问
每个任务分支都是新的,都没有上游。在默认的push.default为simple时,往你拉取的远端push,当前分支必须有一个同名的上游跟踪分支。代理第一次裸跑git push会失败,还得自己琢磨出--set-upstream。push.autoSetupRemote设为true后,当前分支没有上游时,默认push会自动带上--set-upstream。
git config --global push.autoSetupRemote true6. 自动清掉远端已删的分支代理分支活得很短。PR合并、服务端删分支,你的克隆里还留着origin/hermes/fix-login。原因是Git会保留指向远端已删分支的本地引用。几周代理分支跑下来,git branch -r里全是服务端早就删掉的名字。fetch.prune设为true即可。
git config --global fetch.prune true7. rebase堆叠任务时更新上游分支任务B有时从任务A的分支起步,而不是从main。rebase B之后,A还指着旧提交。rebase.updateRefs会打开--update-refs,强制更新那些指向正在被rebase的提交的分支。
git config --global rebase.updateRefs true但文档紧接着一句:在任何worktree中检出的分支不会被这样更新。A的worktree还开着,A就留在原地。要么先结束或移除A的worktree,要么把它detach掉(git -C ../task-a switch --detach),让A不在任何地方被检出,再rebase B。单次rebase可以用--no-update-refs关掉。
8. 让最新的分支排在最上面
git branch默认按名字排序,刚跑完的任务埋在hermes/*列表中间。branch.sort设为-committerdate,前导负号表示降序。配合git branch --list 'hermes/*',最新的代理工作就在最上面。列表里*是你当前所在的分支,+标记在其他worktree中被检出的分支。
git config --global branch.sort -committerdategit branch --list 'hermes/*'9. 从远端直接建worktree另一台机器上的代理推了fix-login,你想在worktree里接着干。不带分支参数的git worktree add ../fix-login会从你的HEAD新建一个同名分支,名字一样,代理的提交一个都没有。worktree.guessRemote打开后,Git会去找唯一匹配新分支名的远端跟踪分支,检出并设为上游。
git config --global worktree.guessRemote truegit fetchgit worktree add ../fix-login# 带斜杠的分支名要显式写出分支git worktree add ../review hermes/fix-login猜测匹配的是文件夹的basename,所以带斜杠的hermes/fix-login它找不到,这种要显式写出分支名。如果该分支只存在于一个远端且本地没有,git worktree add会自动用--track建好。
10. 清理被直接删掉的worktree
用rm -rf而不是git worktree remove删掉worktree文件夹,管理文件会留在.git/worktrees里,git worktree list继续显示它,标着prunable。git gc会执行git worktree prune --expire 3.months.ago,这些记录要留三个月。把gc.worktreePruneExpire设成2.weeks.ago可以更早清掉。
git config --global gc.worktreePruneExpire 2.weeks.ago# 先看会清掉什么,再立即清理git worktree prune --dry-run --verbosegit worktree prune# 保护某个worktree不被清理和移除git worktree lock --reason "agent still running" ../fix-logingit worktree unlock ../fix-loginprune只删Git对这些worktree的记录,从不删文件。放在不常挂载的磁盘上的worktree看起来像丢了,这种要锁住。锁同时会挡住move和remove,移除被锁的worktree需要两次--force。
一次性贴完
第一条按仓库配置,它的--worktree行按任务worktree配置,其余九条都是全局的:
git config --global merge.conflictStyle zdiff3git config --global diff.algorithm histogramgit config --global rerere.enabled truegit config --global push.autoSetupRemote truegit config --global fetch.prune truegit config --global rebase.updateRefs truegit config --global branch.sort -committerdategit config --global worktree.guessRemote truegit config --global gc.worktreePruneExpire 2.weeks.ago# 每个仓库git config extensions.worktreeConfig true这十条是交付流程里的git层。每个代理分支最终都会变成一个PR、一次CI运行,以及一次部署。剩下的路——托管、部署、支付、认证、CI/CD和监控——是另一本书的内容。
热门跟贴