/** * Git worktree list parser — pure functions, no pi SDK dependencies. * * Parses raw `git worktree list` output into structured entries. */ import path from "path"; // ─── Type Definitions ────────────────────────────────────────────── /** A single worktree entry parsed from `git worktree list`. */ export interface WorktreeEntry { /** Full path to the worktree directory. */ path: string; /** Short commit hash checked out in this worktree. */ commit: string; /** Branch name, or "(detached)" for detached HEAD. */ branch: string; /** Whether this worktree is the current working directory. */ isCurrent: boolean; } // ─── Parser ──────────────────────────────────────────────────────── /** * Parse raw `git worktree list` output into structured entries. * * Expected format (whitespace-separated): * /path/to/repo abc1234 [main] * /tmp/feature-x def5678 [feature-x] * /tmp/detached fedcba HEAD * * @param rawOutput - Raw stdout from `git worktree list` * @param currentDir - Current working directory for comparison * @returns Array of parsed worktree entries */ export function parseWorktreeList(rawOutput: string, currentDir: string): WorktreeEntry[] { const lines = rawOutput.trim().split("\n").filter((line) => line.trim()); return lines.map((line) => { // Split on whitespace: path, commit_hash, [branch] or HEAD const parts = line.trim().split(/\s+/); // Guard against malformed lines — need at least 3 tokens (path, commit, branch/HEAD) if (parts.length < 3) { return null; } const worktreePath = parts[0]; const commitHash = parts[1]; const branchRaw = parts.slice(2).join(" "); // Validate: branch must be in brackets [name] or HEAD for detached if (!branchRaw.match(/^\[.+\]$/) && !branchRaw.match(/^HEAD$/)) { return null; } // Extract branch name from brackets, or mark as detached const bracketMatch = branchRaw.match(/^\[(.+)\]$/); const branchName = bracketMatch ? bracketMatch[1] : "(detached)"; return { path: worktreePath, commit: commitHash, branch: branchName, isCurrent: path.resolve(worktreePath) === path.resolve(currentDir), }; }).filter((entry): entry is WorktreeEntry => entry !== null); }