Critical: - Handle git status failure with clear error instead of silently skipping uncommitted-changes guard - Add path-exists check before removal attempt (fail-fast) Major: - Reuse WorktreeError from create module instead of duplicating WorktreeRemoveError - Add force parameter to TypeBox schema and wire it through to command builder - Add baseDir parameter to isMainRepository for deterministic relative path resolution - Fix environment-dependent test (use absolute paths / explicit baseDir) Minor: - Update stale JSDoc on resolveWorktreeTarget - Add integration test section for execute path - Include branch name in success response - Add test for relative path with explicit baseDir Suggestions: - List remaining worktrees in success response details
101 lines
3.5 KiB
TypeScript
101 lines
3.5 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|