Critical fixes:
- Align create_branch schema description with runtime default (true)
- Add ^ and . to branch name start-of-name validation regex
Major fixes:
- Move dynamic import('node:fs/promises') to static top-level import
- Add integration tests for git_worktree_create execute flow
- Use WorktreeError + formatValidationError for current-branch error
Minor fixes:
- Replace fragile .toContain() assertions with specific strings
- Add 14 new tests for branch name start/end and dangerous char validation
- Document resolveWorktreeTarget as dead code until switch/remove tools ship
Suggestions implemented:
- Reject leading dot in branch names (git compliance)
- Extract formatSuccessResponse() helper for consistency
146 lines
5 KiB
TypeScript
146 lines
5 KiB
TypeScript
/**
|
|
* Git worktree create — pure validation and command-building functions.
|
|
* No pi SDK dependencies for testability.
|
|
*/
|
|
|
|
// ─── Type Definitions ──────────────────────────────────────────────
|
|
|
|
/** Error returned when a worktree operation fails. */
|
|
export interface WorktreeError {
|
|
/** Human-readable error message from git or validation. */
|
|
message: string;
|
|
/** Suggested fix for the user. */
|
|
suggestion?: string;
|
|
}
|
|
|
|
/** Parameters for creating a new worktree. */
|
|
export interface CreateWorktreeParams {
|
|
/** Path for the new worktree (relative or absolute). */
|
|
path: string;
|
|
/** Branch name to check out. */
|
|
branch: string;
|
|
/** Whether to create the branch if it doesn't exist. Defaults to true. */
|
|
createBranch?: boolean;
|
|
}
|
|
|
|
// ─── Validation ────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Validate input parameters for creating a worktree.
|
|
*
|
|
* Checks that path and branch name are non-empty and well-formed.
|
|
* Branch names follow git conventions: no spaces, no leading/trailing whitespace,
|
|
* no special characters like ~, ^, . at start or ?, *, [, ], ", ' in the middle (except / for nesting).
|
|
*
|
|
* @param worktreePath - Path for the new worktree
|
|
* @param branchName - Branch name to check out
|
|
* @returns null if valid, or a WorktreeError describing the problem
|
|
*/
|
|
export function validateCreateWorktreeInput(
|
|
worktreePath: string,
|
|
branchName: string,
|
|
): WorktreeError | null {
|
|
// Validate path is non-empty
|
|
if (!worktreePath || !worktreePath.trim()) {
|
|
return {
|
|
message: "Path cannot be empty.",
|
|
suggestion: "Provide a valid path for the new worktree (e.g., '../worktrees/feature-x').",
|
|
};
|
|
}
|
|
|
|
// Validate branch name is non-empty
|
|
if (!branchName || !branchName.trim()) {
|
|
return {
|
|
message: "Branch name cannot be empty.",
|
|
suggestion: "Provide a valid branch name (e.g., 'feature-x' or 'bugfix/issue-42').",
|
|
};
|
|
}
|
|
|
|
// Validate branch name doesn't contain spaces
|
|
if (branchName.includes(" ")) {
|
|
return {
|
|
message: `Branch name '${branchName}' contains invalid characters (spaces).`,
|
|
suggestion: "Use hyphens or underscores instead of spaces (e.g., 'feature-x' not 'my feature').",
|
|
};
|
|
}
|
|
|
|
// Validate branch name doesn't start/end with special chars
|
|
if (/^[\/\-~^\.]/.test(branchName) || /[\/\-\.\s]$/.test(branchName)) {
|
|
return {
|
|
message: `Branch name '${branchName}' starts or ends with an invalid character.`,
|
|
suggestion: "Branch names should start with a letter and not end with '/', '-', '.', or whitespace.",
|
|
};
|
|
}
|
|
|
|
// Validate branch name doesn't contain dangerous characters
|
|
if (/[?*\[\]"'\\]/.test(branchName)) {
|
|
return {
|
|
message: `Branch name '${branchName}' contains invalid characters.`,
|
|
suggestion: "Avoid characters like ?, *, [, ], \", ' in branch names.",
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// ─── Path Validation ──────────────────────────────────────────────
|
|
|
|
/**
|
|
* Check if the target path already exists and return an error if so.
|
|
*
|
|
* @param worktreePath - Path to check
|
|
* @param checkExists - Function that checks if a path exists (injected for testability)
|
|
* @returns null if path is free, or a WorktreeError with the exact message format
|
|
*/
|
|
export function validatePathDoesNotExist(
|
|
worktreePath: string,
|
|
checkExists: (path: string) => boolean,
|
|
): WorktreeError | null {
|
|
if (checkExists(worktreePath)) {
|
|
return {
|
|
message: `Path '${worktreePath}' already exists.`,
|
|
suggestion: "Choose a different location.",
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ─── Branch Validation ──────────────────────────────────────────────
|
|
|
|
/**
|
|
* Check if the target branch is the same as the current branch.
|
|
*
|
|
* @param targetBranch - The branch we want to create the worktree on
|
|
* @param currentBranch - The currently checked out branch
|
|
* @returns true if they match (worktree creation should be refused)
|
|
*/
|
|
export function isCurrentBranch(targetBranch: string, currentBranch: string): boolean {
|
|
return targetBranch === currentBranch;
|
|
}
|
|
|
|
// ─── Command Building ──────────────────────────────────────────────
|
|
|
|
/**
|
|
* Build the `git worktree add` command arguments.
|
|
*
|
|
* Uses `-b` flag when createBranch is true to create a new branch.
|
|
* Without `-b`, checks out an existing branch.
|
|
*
|
|
* @param params - Worktree creation parameters
|
|
* @returns Array of command arguments (excluding 'git' itself)
|
|
*/
|
|
export function buildWorktreeAddCommand(params: CreateWorktreeParams): string[] {
|
|
const { path: worktreePath, branch, createBranch = true } = params;
|
|
|
|
const args = ["worktree", "add"];
|
|
|
|
if (createBranch) {
|
|
args.push("-b", branch);
|
|
} else {
|
|
args.push(branch);
|
|
}
|
|
|
|
args.push(worktreePath);
|
|
|
|
return args;
|
|
}
|