git worktree 并发隔离:工作树创建、交叉 rebase 与合并策略
git worktree 并发隔离:工作树创建、交叉 rebase 与合并策略
学习目标
- 理解 OMX 如何通过 git worktree 实现并发 worker 的文件系统隔离
- 掌握工作树创建、脏检查和回滚机制
- 了解交叉 rebase 协议与合并策略
- 理解 worker worktree 中 AGENTS.md 叠加层的替换与恢复
前置知识
本章聚焦 Oh-My-Codex 的具体工程实践,无需前置阅读。
项目实践
Worktree 路径规划
伪代码(src/team/worktree.ts — planWorktreeTarget):function planWorktreeTarget(options): repoRoot = findGitRoot() parentDir = dirname(repoRoot) repoName = basename(repoRoot)
if options.teamMode: // 团队模式:团队作用域的 worktree return `${repoRoot}/.omx/team/${options.teamName}/worktrees/${options.workerName}` else if options.detached: // 分离模式:共享的分离 worktree 目录 return `${parentDir}/${repoName}.omx-worktrees/launch-detached` else: // 命名模式:独立 worktree return `${parentDir}/${repoName}.omx-worktrees/${options.name}`工作树创建
伪代码(ensureWorktree):function ensureWorktree(targetPath, options): if dirExists(targetPath): // 已存在,检查是否干净 if isDirty(targetPath) and not options.allowDirtyReuse: warn(`Worktree ${targetPath} is dirty, launching as-is`) return targetPath
// 检查 HEAD 是否匹配 if options.expectedHead and not matchesHead(targetPath, options.expectedHead): if options.detached: // 分离 worktree 可能因源 HEAD 前进而失效 throw WorktreeTargetMismatch(targetPath) return targetPath
// 创建新 worktree if options.detached: run(`git worktree add --detach ${targetPath} HEAD`) else: branchName = `${options.teamName}/${options.workerName}` run(`git worktree add -b ${branchName} ${targetPath} HEAD`)
return targetPathAGENTS.md 叠加层替换
Worker worktree 的根目录 AGENTS.md 被替换为团队 worker 运行时指令:
伪代码(writeWorkerWorktreeRootAgentsFile):function writeWorkerWorktreeRootAgentsFile(worktreePath, workerIdentity): originalAgents = read(`${worktreePath}/AGENTS.md`)
// 备份原始文件 backupPath = `${worktreePath}/.omx/agents-backup.md` if not fileExists(backupPath): write(backupPath, originalAgents)
// 写入团队 worker 指令 teamOverlay = generateTeamWorkerOverlay(workerIdentity) write(`${worktreePath}/AGENTS.md`, teamOverlay)
// 排除备份文件不被 git 跟踪 run(`git -C ${worktreePath} update-index --skip-worktree .omx/agents-backup.md`)关闭时恢复:
伪代码(removeWorkerWorktreeRootAgentsFile):function removeWorkerWorktreeRootAgentsFile(worktreePath): backupPath = `${worktreePath}/.omx/agents-backup.md`
if fileExists(backupPath): write(`${worktreePath}/AGENTS.md`, read(backupPath)) run(`git -C ${worktreePath} update-index --no-skip-worktree .omx/agents-backup.md`)Leader 清洁断言
伪代码(assertCleanLeaderWorkspaceForWorkerWorktrees):function assertCleanLeaderWorkspace(): status = run("git status --porcelain") if status.output != "": throw Error( "Leader workspace must be clean before worker worktree provisioning. " + "Commit, stash, or clean changes first." )Worker 提交集成到 Leader
伪代码(integrateWorkerCommitsIntoLeader):function integrateWorkerCommitsIntoLeader(workers, leaderCwd): for worker in workers: worktreePath = worker.worktreePath
// Phase A: 自动提交脏 worktree if isDirty(worktreePath): run(`git -C ${worktreePath} add -A`) run(`git -C ${worktreePath} commit -m "Auto-commit: ${worker.name}"`)
// Phase B: 合并到 leader workerHead = run(`git -C ${worktreePath} rev-parse HEAD`) leaderHead = run(`git -C ${leaderCwd} rev-parse HEAD`)
if isCleanlyAhead(worktreePath, leaderCwd): // Worker 干净地领先 → 快速合并 run(`git -C ${leaderCwd} merge --no-ff -X theirs ${workerHead}`) else: // 分叉 → 逐个 cherry-pick commits = getCommitsSince(worktreePath, leaderHead) for commit in commits: run(`git -C ${leaderCwd} cherry-pick -X theirs ${commit}`)
// Phase C: 交叉 rebase(见下文)交叉 Rebase 协议
交叉 rebase 在 leader HEAD 前进后,将 worker 的 worktree rebase 到新的 leader HEAD:
伪代码(crossRebaseProtocol):function crossRebase(worker, newLeaderHead): worktreePath = worker.worktreePath
// 检查资格条件 status = readWorkerStatus(worker) if status in ["working"]: recordEvent("worker_cross_rebase_skipped", { worker: worker.name }) return { result: "skipped" } // 审计记录,不唤醒
if isDirty(worktreePath): recordEvent("worker_cross_rebase_skipped", { worker: worker.name, reason: "dirty" }) return { result: "skipped" } // 下次周期 auto-commit 后重试
// 执行 rebase try: run(`git -C ${worktreePath} rebase -X ours ${newLeaderHead}`) recordEvent("worker_cross_rebase_applied", { worker: worker.name }) return { result: "applied" } // 非唤醒事件 catch RebaseConflict: run(`git -C ${worktreePath} rebase --abort`)
// 记录冲突证据 writeIntegrationReport(worker, "cross-rebase conflict") recordEvent("worker_cross_rebase_conflict", { worker: worker.name }) sendMailboxMessage(leader, `Cross-rebase conflict for ${worker.name}`) return { result: "conflict" } // 唤醒事件,通知 leaderRebase 策略说明:git rebase -X ours 在 rebase 上下文中意味着上游(leader)的变更优先。Worker 获取所有 leader 的变更,同时在非冲突处保留自己的变更。
三条路径:
| 路径 | 条件 | 事件类型 | 是否唤醒 |
|---|---|---|---|
| Applied | Worker idle/done/failed 且 worktree 干净 | worker_cross_rebase_applied | 否(审计记录) |
| Conflict | Rebase 冲突 | worker_cross_rebase_conflict | 是(通知 leader) |
| Skipped | Worker working 或 worktree 脏 | worker_cross_rebase_skipped | 否(审计记录) |
回滚机制
伪代码(rollbackProvisionedWorktrees):function rollbackProvisionedWorktrees(provisionedWorktrees): for worktree in provisionedWorktrees: branchName = getBranchName(worktree)
// 移除 worktree run(`git worktree remove ${worktree}`)
// 删除分支 run(`git branch -D ${branchName}`)合并策略选择
| 场景 | 策略 | 说明 |
|---|---|---|
| Worker 干净领先 | merge --no-ff -X theirs | 快速合并,冲突时 worker 优先 |
| Worker 与 leader 分叉 | cherry-pick -X theirs | 逐个提交,冲突时 worker 优先 |
| Cross-rebase | rebase -X ours | Worker 获取 leader 变更,冲突时 leader 优先 |
为什么合并和 rebase 使用不同的 -X 策略:
- 合并用
-X theirs:leader 接受 worker 的变更(worker 是工作执行者) - rebase 用
-X ours:worker 的 worktree rebase 到 leader,leader 的变更优先(保证 leader 的修改不被丢失)
问题与规避
| 问题 | 后果 | OMX 的规避策略 |
|---|---|---|
| Detached worktree 源 HEAD 前进 | 后续启动 worktree_target_mismatch | 使用 named worktree |
| Worker worktree 脏 | 交叉 rebase 跳过 | 自动提交后下次周期重试 |
| 合并冲突无法解决 | cherry-pick 中断 | -X theirs 策略自动解决 |
| Leader worktree 不干净 | Worker 预配不安全 | 断言 leader workspace 清洁 |
| Worktree 回滚时分支已合并 | 分支删除失败 | 条件性删除,忽略已合并不存在的分支 |
设计取舍
为什么选择 worktree 隔离而非子目录隔离?
OMX 为每个并发 worker 创建独立的 git worktree,而非在主仓库的子目录中工作。
优势:
- 完整 git 隔离:每个 worker 有独立的 HEAD、index 和 working tree
- 分支安全:worker 的修改不会污染主工作树
- 并发安全:多个 worker 可以同时修改不同文件而不会冲突
- 易于清理:
git worktree remove即可删除
代价:
- 磁盘空间:每个 worktree 需要完整的 working tree 副本
- 同步开销:需要通过 merge/cherry-pick 整合 worker 修改
- 复杂度:需要管理 worktree 生命周期(创建、预配、回滚)
替代方案:
- 子目录隔离 + git stash/checkout:更轻量但并发安全性差
- 分支切换(单工作树):更节省空间但不能并发
参考来源
- Oh-My-Codex v0.18.9 源码 —
src/team/worktree.ts(Worktree 管理) - Oh-My-Codex v0.18.9 源码 —
src/team/runtime.ts(集成逻辑,Phase A/B/C) - Oh-My-Codex v0.18.9 源码 —
src/team/__tests__/cross-rebase-smoke.test.ts(交叉 rebase 测试) - Git worktree 文档 — https://git-scm.com/docs/git-worktree