Persist agent deletion and support subdir worktree cleanup
This commit is contained in:
@@ -305,6 +305,25 @@ describe("paseo worktree manager", () => {
|
||||
expect(remaining.map((worktree) => worktree.path)).toEqual([second.worktreePath]);
|
||||
});
|
||||
|
||||
it("deletes a paseo worktree even when given a subdirectory path", async () => {
|
||||
const created = await createWorktree({
|
||||
branchName: "main",
|
||||
cwd: repoDir,
|
||||
baseBranch: "main",
|
||||
worktreeSlug: "alpha",
|
||||
paseoHome,
|
||||
});
|
||||
|
||||
const nestedDir = join(created.worktreePath, "nested", "dir");
|
||||
execSync(`mkdir -p ${nestedDir}`);
|
||||
|
||||
await deletePaseoWorktree({ cwd: repoDir, worktreePath: nestedDir, paseoHome });
|
||||
expect(existsSync(created.worktreePath)).toBe(false);
|
||||
|
||||
const remaining = await listPaseoWorktrees({ cwd: repoDir, paseoHome });
|
||||
expect(remaining.some((worktree) => worktree.path === created.worktreePath)).toBe(false);
|
||||
});
|
||||
|
||||
it("ensures .paseo is ignored in .gitignore", async () => {
|
||||
await ensurePaseoIgnored(repoDir);
|
||||
await ensurePaseoIgnored(repoDir);
|
||||
|
||||
@@ -514,6 +514,57 @@ export async function listPaseoWorktrees({
|
||||
.filter((entry) => entry.path.startsWith(rootPrefix));
|
||||
}
|
||||
|
||||
export async function resolvePaseoWorktreeRootForCwd(
|
||||
cwd: string,
|
||||
options?: { paseoHome?: string }
|
||||
): Promise<{ repoRoot: string; worktreeRoot: string; worktreePath: string } | null> {
|
||||
let repoInfo: RepoInfo;
|
||||
try {
|
||||
repoInfo = await detectRepoInfo(cwd);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
const worktreesRoot = await getPaseoWorktreesRoot(repoInfo.path, options?.paseoHome);
|
||||
const resolvedRoot = normalizePathForOwnership(worktreesRoot) + sep;
|
||||
|
||||
let worktreeRoot: string | null = null;
|
||||
try {
|
||||
const { stdout } = await execAsync(
|
||||
"git rev-parse --path-format=absolute --show-toplevel",
|
||||
{ cwd, env: READ_ONLY_GIT_ENV }
|
||||
);
|
||||
const trimmed = stdout.trim();
|
||||
worktreeRoot = trimmed.length > 0 ? trimmed : null;
|
||||
} catch {
|
||||
worktreeRoot = null;
|
||||
}
|
||||
|
||||
if (!worktreeRoot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const resolvedWorktreeRoot = normalizePathForOwnership(worktreeRoot);
|
||||
if (!resolvedWorktreeRoot.startsWith(resolvedRoot)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const knownWorktrees = await listPaseoWorktrees({
|
||||
cwd: repoInfo.path,
|
||||
paseoHome: options?.paseoHome,
|
||||
});
|
||||
const match = knownWorktrees.find((entry) => entry.path === resolvedWorktreeRoot);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
repoRoot: repoInfo.path,
|
||||
worktreeRoot: worktreesRoot,
|
||||
worktreePath: match.path,
|
||||
};
|
||||
}
|
||||
|
||||
export async function deletePaseoWorktree({
|
||||
cwd,
|
||||
worktreePath,
|
||||
@@ -531,21 +582,23 @@ export async function deletePaseoWorktree({
|
||||
|
||||
const repoInfo = await detectRepoInfo(cwd);
|
||||
const worktreesRoot = await getPaseoWorktreesRoot(repoInfo.path, paseoHome);
|
||||
const targetPath = worktreePath ?? join(worktreesRoot, worktreeSlug!);
|
||||
const resolvedRoot = normalizePathForOwnership(worktreesRoot) + sep;
|
||||
const resolvedTarget = normalizePathForOwnership(targetPath);
|
||||
const requestedPath = worktreePath ?? join(worktreesRoot, worktreeSlug!);
|
||||
const resolvedRequested = normalizePathForOwnership(requestedPath);
|
||||
const resolvedWorktree =
|
||||
(await resolvePaseoWorktreeRootForCwd(requestedPath, { paseoHome }))?.worktreePath ??
|
||||
resolvedRequested;
|
||||
|
||||
if (!resolvedTarget.startsWith(resolvedRoot)) {
|
||||
if (!resolvedWorktree.startsWith(resolvedRoot)) {
|
||||
throw new Error("Refusing to delete non-Paseo worktree");
|
||||
}
|
||||
|
||||
const canonicalTargetPath = normalizePathForOwnership(targetPath);
|
||||
await execAsync(`git worktree remove "${canonicalTargetPath}" --force`, {
|
||||
await execAsync(`git worktree remove "${resolvedWorktree}" --force`, {
|
||||
cwd: repoInfo.path,
|
||||
});
|
||||
|
||||
if (existsSync(canonicalTargetPath)) {
|
||||
rmSync(canonicalTargetPath, { recursive: true, force: true });
|
||||
if (existsSync(resolvedWorktree)) {
|
||||
rmSync(resolvedWorktree, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user