跳转到内容

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 targetPath

AGENTS.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" } // 唤醒事件,通知 leader

Rebase 策略说明git rebase -X ours 在 rebase 上下文中意味着上游(leader)的变更优先。Worker 获取所有 leader 的变更,同时在非冲突处保留自己的变更。

三条路径

路径条件事件类型是否唤醒
AppliedWorker idle/done/failed 且 worktree 干净worker_cross_rebase_applied否(审计记录)
ConflictRebase 冲突worker_cross_rebase_conflict是(通知 leader)
SkippedWorker 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-rebaserebase -X oursWorker 获取 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