fix(server): preserve worktree include cleanup

This commit is contained in:
paseo-bot[bot]
2026-08-25 17:55:27 +00:00
parent a29729ed48
commit aa06701c9d
3 changed files with 51 additions and 10 deletions
@@ -39,7 +39,6 @@ export interface CreatePaseoWorktreeInput extends CreateWorktreeCoreInput {
export interface CreatePaseoWorktreeResult {
worktree: WorktreeConfig;
worktreeIncludeSummary?: WorktreeIncludeSummary;
worktreeIncludeSummary?: WorktreeIncludeSummary;
intent: WorktreeCreationIntent;
workspace: PersistedWorkspaceRecord;
repoRoot: string;
@@ -121,7 +120,6 @@ async function createPaseoWorktreeWithPriority(
return {
worktree: createdWorktree.worktree,
worktreeIncludeSummary: createdWorktree.worktreeIncludeSummary,
worktreeIncludeSummary: createdWorktree.worktreeIncludeSummary,
intent: createdWorktree.intent,
workspace,
repoRoot: createdWorktree.repoRoot,
+6 -2
View File
@@ -1,7 +1,12 @@
import { createNameId } from "mnemonic-id";
import type { ForgeService } from "../services/forge-service.js";
import { createWorktree, slugify, validateBranchSlug, type WorktreeConfig } from "../utils/worktree.js";
import {
createWorktree,
slugify,
validateBranchSlug,
type WorktreeConfig,
} from "../utils/worktree.js";
import type { WorktreeIncludeSummary } from "../utils/worktree-include.js";
import {
resolveWorktreeCreationIntent,
@@ -39,7 +44,6 @@ export interface CreateWorktreeCoreDeps {
export interface CreateWorktreeCoreResult {
worktree: WorktreeConfig;
worktreeIncludeSummary?: WorktreeIncludeSummary;
worktreeIncludeSummary?: WorktreeIncludeSummary;
intent: WorktreeCreationIntent;
repoRoot: string;
created: boolean;
+45 -6
View File
@@ -1154,12 +1154,18 @@ export async function deletePaseoWorktree({
}
export async function rollbackCreatedPaseoWorktree(
options: DeletePaseoWorktreeOptions,
options: DeletePaseoWorktreeOptions & { createdBranchName?: string },
cause: unknown,
): Promise<never> {
let cleanupError: unknown;
try {
await deletePaseoWorktree(options);
if (options.createdBranchName && options.cwd) {
await runGitCommand(["branch", "--delete", "--force", options.createdBranchName], {
cwd: options.cwd,
acceptExitCodes: [0, 1],
});
}
} catch (error) {
cleanupError = error;
}
@@ -1228,10 +1234,22 @@ export const createWorktree = async ({
}: CreateWorktreeOptions): Promise<CreatedWorktreeConfig> => {
const sourcePlan = await resolveWorktreeSourcePlan({ cwd, source, desiredSlug: worktreeSlug });
const paseoWorktreesBaseRoot = resolvePaseoWorktreesBaseRoot({ paseoHome, worktreesRoot });
const worktreeIncludePlan = await readWorktreeIncludePlan({
sourceRoot: cwd,
excludedSourceRoots: [paseoWorktreesBaseRoot],
});
const worktreeIncludePlan = await (async () => {
try {
return await readWorktreeIncludePlan({
sourceRoot: cwd,
excludedSourceRoots: [paseoWorktreesBaseRoot],
});
} catch (error) {
if (sourcePlan.createdBranchName) {
await runGitCommand(["branch", "--delete", "--force", sourcePlan.createdBranchName], {
cwd,
acceptExitCodes: [0, 1],
});
}
throw error;
}
})();
let worktreePath = join(await getPaseoWorktreesRoot(cwd, paseoHome, worktreesRoot), worktreeSlug);
mkdirSync(dirname(worktreePath), { recursive: true });
@@ -1289,7 +1307,14 @@ export const createWorktree = async ({
};
} catch (error) {
return rollbackCreatedPaseoWorktree(
{ cwd, worktreePath, teardownCwds: [], paseoHome, worktreesBaseRoot: worktreesRoot },
{
cwd,
worktreePath,
teardownCwds: [],
paseoHome,
worktreesBaseRoot: worktreesRoot,
createdBranchName: sourcePlan.createdBranchName,
},
error,
);
}
@@ -1317,6 +1342,7 @@ interface ResolveWorktreeSourcePlanOptions {
interface WorktreeSourcePlan {
branchName: string;
createdBranchName?: string;
// Display name and exact ref are two different facts. The name cannot round-trip to a
// commit — "main" resolves local-first even when the worktree was cut from a fork's
// upstream — so comparisons and actions read the ref and the UI reads the name.
@@ -1354,6 +1380,7 @@ async function resolveWorktreeSourcePlan({
return {
branchName: newBranchName,
createdBranchName: newBranchName,
metadataBaseRefName: normalizedBaseBranch,
metadataBaseRef: resolvedBaseBranch,
changeRequestLookupTarget: createPaseoWorktreeChangeRequestHint({
@@ -1374,11 +1401,22 @@ async function resolveWorktreeSourcePlan({
} catch {
throw new UnknownBranchError({ branchName: source.branchName, cwd });
}
return {
branchName: source.branchName,
createdBranchName: source.branchName,
metadataBaseRefName: source.branchName,
changeRequestLookupTarget: createPaseoWorktreeChangeRequestHint({
headRef: source.branchName,
localBranchName: source.branchName,
}),
addArguments: [source.branchName],
};
}
if (await isBranchCheckedOut(cwd, source.branchName)) {
const branchName = await resolveUniqueLocalBranchName(cwd, source.branchName);
return {
branchName,
createdBranchName: branchName,
metadataBaseRefName: source.branchName,
changeRequestLookupTarget: createPaseoWorktreeChangeRequestHint({
headRef: branchName,
@@ -1447,6 +1485,7 @@ async function resolveWorktreeSourcePlan({
return {
branchName: localBranchName,
createdBranchName: localBranchName,
metadataBaseRefName: normalizedBaseRefName,
changeRequestLookupTarget: createPaseoWorktreeChangeRequestHint({
headRef: source.headRef,