feat: enhance worktree creation logic with existing worktree checks

- Added functionality to check for existing worktrees for a branch before creating a new one in the create worktree endpoint.
- Introduced a helper function to find existing worktrees by parsing the output of `git worktree list`.
- Updated the auto mode service to utilize the new worktree checking logic, improving efficiency and user experience.
- Removed redundant checks for existing worktrees to streamline the creation process.
This commit is contained in:
Cody Seibert
2025-12-16 16:01:23 -05:00
parent 8c24381759
commit 04d263b1ed
2 changed files with 119 additions and 15 deletions

View File

@@ -1,17 +1,64 @@
/**
* POST /create endpoint - Create a new git worktree
*
* This endpoint handles worktree creation with proper checks:
* 1. First checks if git already has a worktree for the branch (anywhere)
* 2. If found, returns the existing worktree (no error)
* 3. Only creates a new worktree if none exists for the branch
*/
import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import path from "path";
import { mkdir, access } from "fs/promises";
import { mkdir } from "fs/promises";
import { isGitRepo, getErrorMessage, logError, normalizePath } from "../common.js";
import { trackBranch } from "./branch-tracking.js";
const execAsync = promisify(exec);
/**
* Find an existing worktree for a given branch by checking git worktree list
*/
async function findExistingWorktreeForBranch(
projectPath: string,
branchName: string
): Promise<{ path: string; branch: string } | null> {
try {
const { stdout } = await execAsync("git worktree list --porcelain", {
cwd: projectPath,
});
const lines = stdout.split("\n");
let currentPath: string | null = null;
let currentBranch: string | null = null;
for (const line of lines) {
if (line.startsWith("worktree ")) {
currentPath = line.slice(9);
} else if (line.startsWith("branch ")) {
currentBranch = line.slice(7).replace("refs/heads/", "");
} else if (line === "" && currentPath && currentBranch) {
// End of a worktree entry
if (currentBranch === branchName) {
return { path: currentPath, branch: currentBranch };
}
currentPath = null;
currentBranch = null;
}
}
// Check the last entry (if file doesn't end with newline)
if (currentPath && currentBranch && currentBranch === branchName) {
return { path: currentPath, branch: currentBranch };
}
return null;
} catch {
return null;
}
}
export function createCreateHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
@@ -37,6 +84,27 @@ export function createCreateHandler() {
return;
}
// First, check if git already has a worktree for this branch (anywhere)
const existingWorktree = await findExistingWorktreeForBranch(projectPath, branchName);
if (existingWorktree) {
// Worktree already exists, return it as success (not an error)
// This handles manually created worktrees or worktrees from previous runs
console.log(`[Worktree] Found existing worktree for branch "${branchName}" at: ${existingWorktree.path}`);
// Track the branch so it persists in the UI
await trackBranch(projectPath, branchName);
res.json({
success: true,
worktree: {
path: normalizePath(existingWorktree.path),
branch: branchName,
isNew: false, // Not newly created
},
});
return;
}
// Sanitize branch name for directory usage
const sanitizedName = branchName.replace(/[^a-zA-Z0-9_-]/g, "-");
const worktreesDir = path.join(projectPath, ".worktrees");
@@ -45,19 +113,6 @@ export function createCreateHandler() {
// Create worktrees directory if it doesn't exist
await mkdir(worktreesDir, { recursive: true });
// Check if worktree already exists
try {
await access(worktreePath);
// Worktree already exists, return error
res.status(400).json({
success: false,
error: `Worktree "${branchName}" already exists`,
});
return;
} catch {
// Worktree doesn't exist, good to proceed
}
// Check if branch exists
let branchExists = false;
try {

View File

@@ -839,18 +839,67 @@ Format your response as a structured markdown document.`;
// Private helpers
/**
* Find an existing worktree for a given branch by checking git worktree list
*/
private async findExistingWorktreeForBranch(
projectPath: string,
branchName: string
): Promise<string | null> {
try {
const { stdout } = await execAsync("git worktree list --porcelain", {
cwd: projectPath,
});
const lines = stdout.split("\n");
let currentPath: string | null = null;
let currentBranch: string | null = null;
for (const line of lines) {
if (line.startsWith("worktree ")) {
currentPath = line.slice(9);
} else if (line.startsWith("branch ")) {
currentBranch = line.slice(7).replace("refs/heads/", "");
} else if (line === "" && currentPath && currentBranch) {
// End of a worktree entry
if (currentBranch === branchName) {
return currentPath;
}
currentPath = null;
currentBranch = null;
}
}
// Check the last entry (if file doesn't end with newline)
if (currentPath && currentBranch && currentBranch === branchName) {
return currentPath;
}
return null;
} catch {
return null;
}
}
private async setupWorktree(
projectPath: string,
featureId: string,
branchName: string
): Promise<string> {
// First, check if git already has a worktree for this branch (anywhere)
const existingWorktree = await this.findExistingWorktreeForBranch(projectPath, branchName);
if (existingWorktree) {
console.log(`[AutoMode] Found existing worktree for branch "${branchName}" at: ${existingWorktree}`);
return existingWorktree;
}
// Git worktrees stay in project directory
const worktreesDir = path.join(projectPath, ".worktrees");
const worktreePath = path.join(worktreesDir, featureId);
await fs.mkdir(worktreesDir, { recursive: true });
// Check if worktree already exists
// Check if worktree directory already exists (might not be linked to branch)
try {
await fs.access(worktreePath);
return worktreePath;