Add validation and error handling for removing linked worktrees: - validateRemoveWorktreeInput() — validates path is non-empty - isMainRepository() — refuses removal of main repo root - hasUncommittedChanges() — checks git status --porcelain output - buildWorktreeRemoveCommand() — builds git worktree remove args Wire up createRemoveWorktreeTool with full validation pipeline: 1. Validate input parameters (path) 2. Check if target is the main repo — refuse with prune suggestion 3. Check for uncommitted changes — refuse with stash/commit message 4. Run git worktree remove <path> 5. Surface git errors with helpful suggestions
101 lines
3.4 KiB
TypeScript
101 lines
3.4 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|