pi-worktrees/.pi/extensions/pi-worktree-remove.ts
David Kong 2cc2fa5ba3 fix: address all review findings for git_worktree_remove
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
2026-07-25 12:12:55 +10:00

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;
}