Files
oh-my-opencode/src/hooks/start-work/worktree-detector.ts
T
YeonGyu-Kim c9d30f8be3 feat: add porcelain worktree parser with listWorktrees and parseWorktreeListPorcelain
Introduce git worktree list --porcelain parsing following upstream opencode patterns. Exports listWorktrees() for full worktree enumeration with branch info alongside existing detectWorktreePath().

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-12 17:25:10 +09:00

78 lines
1.8 KiB
TypeScript

import { execFileSync } from "node:child_process"
export type WorktreeEntry = {
path: string
branch: string | undefined
bare: boolean
}
export function parseWorktreeListPorcelain(output: string): WorktreeEntry[] {
const lines = output.split("\n").map((line) => line.trim())
const entries: WorktreeEntry[] = []
let current: Partial<WorktreeEntry> | undefined
for (const line of lines) {
if (!line) {
if (current?.path) {
entries.push({
path: current.path,
branch: current.branch,
bare: current.bare ?? false,
})
}
current = undefined
continue
}
if (line.startsWith("worktree ")) {
current = { path: line.slice("worktree ".length).trim() }
continue
}
if (!current) continue
if (line.startsWith("branch ")) {
current.branch = line.slice("branch ".length).trim().replace(/^refs\/heads\//, "")
} else if (line === "bare") {
current.bare = true
}
}
if (current?.path) {
entries.push({
path: current.path,
branch: current.branch,
bare: current.bare ?? false,
})
}
return entries
}
export function listWorktrees(directory: string): WorktreeEntry[] {
try {
const output = execFileSync("git", ["worktree", "list", "--porcelain"], {
cwd: directory,
encoding: "utf-8",
timeout: 5000,
stdio: ["pipe", "pipe", "pipe"],
})
return parseWorktreeListPorcelain(output)
} catch {
return []
}
}
export function detectWorktreePath(directory: string): string | null {
try {
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
cwd: directory,
encoding: "utf-8",
timeout: 5000,
stdio: ["pipe", "pipe", "pipe"],
}).trim()
} catch {
return null
}
}