/** * Git worktree remove — pure validation and command-building functions. * No pi SDK dependencies for testability. */ import path from "path"; import type { WorktreeError } from "./pi-worktree-create"; // ─── Type Definitions ────────────────────────────────────────────── // WorktreeError is imported from pi-worktree-create (shared error shape) /** 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 WorktreeError describing the problem */ export function validateRemoveWorktreeInput( worktreePath: string, ): WorktreeError | 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 * @param baseDir - Base directory to resolve relative paths against (defaults to process.cwd()) * @returns true if target is the main repo (removal should be refused) */ export function isMainRepository( targetPath: string, mainRepoRoot: string, baseDir: string = process.cwd(), ): boolean { return path.resolve(baseDir, 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; }