/** * Git worktree remove — pure validation and command-building functions. * No pi SDK dependencies for testability. */ import path from "path"; // ─── Type Definitions ────────────────────────────────────────────── /** Error returned when a worktree operation fails. */ export interface WorktreeRemoveError { /** Human-readable error message. */ message: string; /** Suggested fix for the user. */ suggestion?: string; } /** Options for removing a worktree. */ export interface RemoveWorktreeOptions { /** Force removal even if the worktree has uncommitted changes. Defaults to false. */ force?: boolean; } // ─── Input Validation ────────────────────────────────────────────── /** * Validate input parameters for removing a worktree. * * Checks that path is non-empty and well-formed. * * @param worktreePath - Path of the worktree to remove * @returns null if valid, or a WorktreeRemoveError describing the problem */ export function validateRemoveWorktreeInput( worktreePath: string, ): WorktreeRemoveError | null { if (!worktreePath || !worktreePath.trim()) { return { message: "Path cannot be empty.", suggestion: "Provide a valid worktree path (e.g., '../worktrees/feature-x').", }; } return null; } // ─── Main Repository Check ──────────────────────────────────────── /** * Check if the target path is the main repository root. * * Compares resolved paths to handle trailing slashes and relative paths. * Removing the main repo is not allowed — use `git worktree prune` instead. * * @param targetPath - Path of the worktree to remove * @param mainRepoRoot - Root path of the main repository * @returns true if target is the main repo (removal should be refused) */ export function isMainRepository(targetPath: string, mainRepoRoot: string): boolean { return path.resolve(targetPath) === path.resolve(mainRepoRoot); } // ─── Uncommitted Changes Check ────────────────────────────────────── /** * Check if the worktree has uncommitted changes based on `git status --porcelain` output. * * Any non-empty porcelain output indicates changes (staged, unstaged, or untracked). * * @param statusOutput - Raw stdout from `git status --porcelain` in the target worktree * @returns true if there are uncommitted changes */ export function hasUncommittedChanges(statusOutput: string): boolean { return statusOutput.trim().length > 0; } // ─── Command Building ────────────────────────────────────────────── /** * Build the `git worktree remove` command arguments. * * Uses `--force` flag when force option is true to bypass uncommitted changes check. * * @param worktreePath - Path of the worktree to remove * @param options - Optional removal configuration * @returns Array of command arguments (excluding 'git' itself) */ export function buildWorktreeRemoveCommand( worktreePath: string, options?: RemoveWorktreeOptions, ): string[] { const args = ["worktree", "remove"]; if (options?.force) { args.push("--force"); } args.push(worktreePath); return args; }