Fix New Workspace PR worktree creation (#1565)
* Fix PR worktree creation from New Workspace * Use fixture default branch in GitHub E2E clone
This commit is contained in:
@@ -48,11 +48,17 @@ export interface GhRepoFixture {
|
|||||||
owner: string;
|
owner: string;
|
||||||
name: string;
|
name: string;
|
||||||
fullName: string;
|
fullName: string;
|
||||||
|
defaultBranch: string;
|
||||||
prs: GhPrFixture[];
|
prs: GhPrFixture[];
|
||||||
issues: GhIssueFixture[];
|
issues: GhIssueFixture[];
|
||||||
cleanup(): Promise<void>;
|
cleanup(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GhDefaultBranchClone {
|
||||||
|
path: string;
|
||||||
|
cleanup(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
function gh(args: string[], opts?: { cwd?: string }): string {
|
function gh(args: string[], opts?: { cwd?: string }): string {
|
||||||
return execFileSync("gh", args, {
|
return execFileSync("gh", args, {
|
||||||
cwd: opts?.cwd,
|
cwd: opts?.cwd,
|
||||||
@@ -77,8 +83,9 @@ async function seedPr(args: {
|
|||||||
authedUrl: string;
|
authedUrl: string;
|
||||||
fullName: string;
|
fullName: string;
|
||||||
repoName: string;
|
repoName: string;
|
||||||
|
defaultBranch: string;
|
||||||
}): Promise<{ fixture: GhPrFixture; localPath: string }> {
|
}): Promise<{ fixture: GhPrFixture; localPath: string }> {
|
||||||
const { spec, branch, index, basePath, authedUrl, fullName, repoName } = args;
|
const { spec, branch, index, basePath, authedUrl, fullName, repoName, defaultBranch } = args;
|
||||||
|
|
||||||
const createArgs = [
|
const createArgs = [
|
||||||
"pr",
|
"pr",
|
||||||
@@ -86,7 +93,7 @@ async function seedPr(args: {
|
|||||||
"--title",
|
"--title",
|
||||||
spec.title,
|
spec.title,
|
||||||
"--base",
|
"--base",
|
||||||
"main",
|
defaultBranch,
|
||||||
"--head",
|
"--head",
|
||||||
branch,
|
branch,
|
||||||
"--body",
|
"--body",
|
||||||
@@ -166,10 +173,11 @@ export async function createTempGithubRepo(options: {
|
|||||||
const { category, prs = [], issues = [] } = options;
|
const { category, prs = [], issues = [] } = options;
|
||||||
const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
||||||
const repoName = `${TEMP_GITHUB_REPO_PREFIX}${category}-${uniqueSuffix}`;
|
const repoName = `${TEMP_GITHUB_REPO_PREFIX}${category}-${uniqueSuffix}`;
|
||||||
|
const defaultBranch = "main";
|
||||||
|
|
||||||
// Bootstrap local git repo
|
// Bootstrap local git repo
|
||||||
const basePath = await mkdtemp(path.join("/tmp", `${repoName}-base-`));
|
const basePath = await mkdtemp(path.join("/tmp", `${repoName}-base-`));
|
||||||
git(["init", "-b", "main"], basePath);
|
git(["init", "-b", defaultBranch], basePath);
|
||||||
git(["config", "user.email", "e2e@paseo.test"], basePath);
|
git(["config", "user.email", "e2e@paseo.test"], basePath);
|
||||||
git(["config", "user.name", "Paseo E2E"], basePath);
|
git(["config", "user.name", "Paseo E2E"], basePath);
|
||||||
git(["config", "commit.gpgsign", "false"], basePath);
|
git(["config", "commit.gpgsign", "false"], basePath);
|
||||||
@@ -197,7 +205,7 @@ export async function createTempGithubRepo(options: {
|
|||||||
await writeFile(path.join(basePath, `pr-${i + 1}.txt`), `PR ${i + 1}\n`);
|
await writeFile(path.join(basePath, `pr-${i + 1}.txt`), `PR ${i + 1}\n`);
|
||||||
git(["add", `pr-${i + 1}.txt`], basePath);
|
git(["add", `pr-${i + 1}.txt`], basePath);
|
||||||
git(["commit", "-m", `Add PR ${i + 1}`], basePath);
|
git(["commit", "-m", `Add PR ${i + 1}`], basePath);
|
||||||
git(["checkout", "main"], basePath);
|
git(["checkout", defaultBranch], basePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (branches.length > 0) {
|
if (branches.length > 0) {
|
||||||
@@ -217,6 +225,7 @@ export async function createTempGithubRepo(options: {
|
|||||||
authedUrl,
|
authedUrl,
|
||||||
fullName,
|
fullName,
|
||||||
repoName,
|
repoName,
|
||||||
|
defaultBranch,
|
||||||
});
|
});
|
||||||
localPaths.push(localPath);
|
localPaths.push(localPath);
|
||||||
prFixtures.push(fixture);
|
prFixtures.push(fixture);
|
||||||
@@ -232,6 +241,7 @@ export async function createTempGithubRepo(options: {
|
|||||||
owner,
|
owner,
|
||||||
name: repoName,
|
name: repoName,
|
||||||
fullName,
|
fullName,
|
||||||
|
defaultBranch,
|
||||||
prs: prFixtures,
|
prs: prFixtures,
|
||||||
issues: issueFixtures,
|
issues: issueFixtures,
|
||||||
cleanup: async () => {
|
cleanup: async () => {
|
||||||
@@ -247,3 +257,27 @@ export async function createTempGithubRepo(options: {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function cloneGithubRepoDefaultBranchOnly(
|
||||||
|
repo: Pick<GhRepoFixture, "fullName" | "name" | "defaultBranch">,
|
||||||
|
): Promise<GhDefaultBranchClone> {
|
||||||
|
const token = gh(["auth", "token"]);
|
||||||
|
const authedUrl = `https://x-access-token:${token}@github.com/${repo.fullName}.git`;
|
||||||
|
const clonePath = await mkdtemp(path.join("/tmp", `${repo.name}-${repo.defaultBranch}-only-`));
|
||||||
|
|
||||||
|
execFileSync(
|
||||||
|
"git",
|
||||||
|
["clone", "--quiet", "--single-branch", "--branch", repo.defaultBranch, authedUrl, clonePath],
|
||||||
|
{ stdio: ["ignore", "pipe", "pipe"] },
|
||||||
|
);
|
||||||
|
git(["config", "user.email", "e2e@paseo.test"], clonePath);
|
||||||
|
git(["config", "user.name", "Paseo E2E"], clonePath);
|
||||||
|
git(["config", "commit.gpgsign", "false"], clonePath);
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: clonePath,
|
||||||
|
cleanup: async () => {
|
||||||
|
await rm(clonePath, { recursive: true, force: true });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ import {
|
|||||||
submitNewWorkspacePrompt,
|
submitNewWorkspacePrompt,
|
||||||
} from "./helpers/new-workspace";
|
} from "./helpers/new-workspace";
|
||||||
import { createTempGitRepo, readWorktreeBranchInfo } from "./helpers/workspace";
|
import { createTempGitRepo, readWorktreeBranchInfo } from "./helpers/workspace";
|
||||||
import { createTempGithubRepo, hasGithubAuth } from "./helpers/github-fixtures";
|
import {
|
||||||
|
cloneGithubRepoDefaultBranchOnly,
|
||||||
|
createTempGithubRepo,
|
||||||
|
hasGithubAuth,
|
||||||
|
} from "./helpers/github-fixtures";
|
||||||
import { getServerId } from "./helpers/server-id";
|
import { getServerId } from "./helpers/server-id";
|
||||||
import {
|
import {
|
||||||
expectSidebarWorkspaceSelected,
|
expectSidebarWorkspaceSelected,
|
||||||
@@ -751,4 +755,50 @@ test.describe("New workspace flow", () => {
|
|||||||
await ghRepo.cleanup();
|
await ghRepo.cleanup();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("selected GitHub PR creates the worktree from the PR head even when the head branch is not fetched", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
test.skip(!hasGithubAuth(), "Requires GitHub authentication (gh auth login)");
|
||||||
|
|
||||||
|
const ghRepo = await createTempGithubRepo({
|
||||||
|
category: "new-workspace-pr-worktree",
|
||||||
|
prs: [{ title: "Checkout PR worktree", state: "open" }],
|
||||||
|
});
|
||||||
|
const pr = ghRepo.prs[0]!;
|
||||||
|
const mainCheckout = await cloneGithubRepoDefaultBranchOnly(ghRepo);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const openedProject = await openProjectViaDaemon(client, mainCheckout.path);
|
||||||
|
localWorkspaceIds.add(openedProject.workspaceId);
|
||||||
|
|
||||||
|
await gotoAppShell(page);
|
||||||
|
await waitForSidebarHydration(page);
|
||||||
|
await openNewWorkspaceComposer(page, {
|
||||||
|
projectKey: openedProject.projectKey,
|
||||||
|
projectDisplayName: openedProject.projectDisplayName,
|
||||||
|
});
|
||||||
|
await selectWorkspaceIsolation(page, "worktree");
|
||||||
|
await openStartingRefPicker(page);
|
||||||
|
await selectGitHubPrInPicker(page, pr.number);
|
||||||
|
await submitNewWorkspaceWithoutPrompt(page);
|
||||||
|
|
||||||
|
const worktree = await assertNewWorkspaceSidebarAndHeader(page, {
|
||||||
|
serverId: getServerId(),
|
||||||
|
client,
|
||||||
|
previousWorkspaceId: openedProject.workspaceId,
|
||||||
|
projectDisplayName: openedProject.projectDisplayName,
|
||||||
|
});
|
||||||
|
createdWorktreeDirectories.add(worktree.workspaceDirectory);
|
||||||
|
|
||||||
|
const branchInfo = await readWorktreeBranchInfo({
|
||||||
|
worktreePath: worktree.workspaceDirectory,
|
||||||
|
});
|
||||||
|
expect(branchInfo.currentBranch).toBe(pr.branch);
|
||||||
|
expect(existsSync(path.join(worktree.workspaceDirectory, "pr-1.txt"))).toBe(true);
|
||||||
|
} finally {
|
||||||
|
await mainCheckout.cleanup();
|
||||||
|
await ghRepo.cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ export function pickerItemToCheckoutRequest(
|
|||||||
switch (item.kind) {
|
switch (item.kind) {
|
||||||
case "branch":
|
case "branch":
|
||||||
return { action: "branch-off", refName: item.name };
|
return { action: "branch-off", refName: item.name };
|
||||||
case "github-pr":
|
case "github-pr": {
|
||||||
|
const headRefName = item.item.headRefName?.trim();
|
||||||
return {
|
return {
|
||||||
action: "checkout",
|
action: "checkout",
|
||||||
refName: item.item.headRefName ?? "",
|
...(headRefName ? { refName: headRefName } : {}),
|
||||||
githubPrNumber: item.item.number,
|
githubPrNumber: item.item.number,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -804,8 +804,8 @@ async function createMultiplicityWorkspace(input: {
|
|||||||
createFailedMessage: string;
|
createFailedMessage: string;
|
||||||
}): Promise<ReturnType<typeof normalizeWorkspaceDescriptor>> {
|
}): Promise<ReturnType<typeof normalizeWorkspaceDescriptor>> {
|
||||||
const isWorktree = input.isolation === "worktree";
|
const isWorktree = input.isolation === "worktree";
|
||||||
const baseBranch = isWorktree
|
const checkoutRequest = isWorktree
|
||||||
? (resolveCheckoutRequest(input.selectedItem, input.currentBranch)?.refName ?? undefined)
|
? resolveCheckoutRequest(input.selectedItem, input.currentBranch)
|
||||||
: undefined;
|
: undefined;
|
||||||
const firstAgentContext = buildFirstAgentContext({
|
const firstAgentContext = buildFirstAgentContext({
|
||||||
prompt: input.prompt,
|
prompt: input.prompt,
|
||||||
@@ -818,7 +818,7 @@ async function createMultiplicityWorkspace(input: {
|
|||||||
cwd: input.project.iconWorkingDir,
|
cwd: input.project.iconWorkingDir,
|
||||||
projectId: input.project.projectKey,
|
projectId: input.project.projectKey,
|
||||||
worktreeSlug: createNameId(),
|
worktreeSlug: createNameId(),
|
||||||
...(baseBranch ? { baseBranch } : {}),
|
...checkoutRequest,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
kind: "directory",
|
kind: "directory",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import type {
|
|||||||
import { createWorktree } from "../utils/worktree.js";
|
import { createWorktree } from "../utils/worktree.js";
|
||||||
import type { WorkspaceGitRuntimeSnapshot } from "./workspace-git-service.js";
|
import type { WorkspaceGitRuntimeSnapshot } from "./workspace-git-service.js";
|
||||||
import type { GeneratedWorkspaceName } from "./worktree-branch-name-generator.js";
|
import type { GeneratedWorkspaceName } from "./worktree-branch-name-generator.js";
|
||||||
|
import type { GitHubService } from "../services/github-service.js";
|
||||||
import { createNoopWorkspaceGitService } from "./test-utils/workspace-git-service-stub.js";
|
import { createNoopWorkspaceGitService } from "./test-utils/workspace-git-service-stub.js";
|
||||||
import {
|
import {
|
||||||
asSessionLogger,
|
asSessionLogger,
|
||||||
@@ -49,6 +50,8 @@ import {
|
|||||||
FileBackedWorkspaceRegistry,
|
FileBackedWorkspaceRegistry,
|
||||||
createPersistedProjectRecord,
|
createPersistedProjectRecord,
|
||||||
createPersistedWorkspaceRecord,
|
createPersistedWorkspaceRecord,
|
||||||
|
type PersistedProjectRecord,
|
||||||
|
type PersistedWorkspaceRecord,
|
||||||
} from "./workspace-registry.js";
|
} from "./workspace-registry.js";
|
||||||
|
|
||||||
const REPO_CWD = path.resolve("/tmp/repo");
|
const REPO_CWD = path.resolve("/tmp/repo");
|
||||||
@@ -486,6 +489,9 @@ function createSessionForWorkspaceTests(
|
|||||||
terminalManager?: TerminalManager | null;
|
terminalManager?: TerminalManager | null;
|
||||||
projectRegistry?: SessionOptions["projectRegistry"];
|
projectRegistry?: SessionOptions["projectRegistry"];
|
||||||
workspaceRegistry?: SessionOptions["workspaceRegistry"];
|
workspaceRegistry?: SessionOptions["workspaceRegistry"];
|
||||||
|
github?: GitHubService;
|
||||||
|
paseoHome?: string;
|
||||||
|
worktreesRoot?: string;
|
||||||
renameCurrentBranch?: (
|
renameCurrentBranch?: (
|
||||||
cwd: string,
|
cwd: string,
|
||||||
newName: string,
|
newName: string,
|
||||||
@@ -510,7 +516,8 @@ function createSessionForWorkspaceTests(
|
|||||||
logger: asSessionLogger(logger),
|
logger: asSessionLogger(logger),
|
||||||
downloadTokenStore: asDownloadTokenStore(),
|
downloadTokenStore: asDownloadTokenStore(),
|
||||||
pushTokenStore: asPushTokenStore(),
|
pushTokenStore: asPushTokenStore(),
|
||||||
paseoHome: "/tmp/paseo-test",
|
paseoHome: options.paseoHome ?? "/tmp/paseo-test",
|
||||||
|
worktreesRoot: options.worktreesRoot,
|
||||||
agentManager: asAgentManager({
|
agentManager: asAgentManager({
|
||||||
subscribe: () => () => {},
|
subscribe: () => () => {},
|
||||||
listAgents: () => [],
|
listAgents: () => [],
|
||||||
@@ -604,6 +611,7 @@ function createSessionForWorkspaceTests(
|
|||||||
}),
|
}),
|
||||||
dispose: () => {},
|
dispose: () => {},
|
||||||
}),
|
}),
|
||||||
|
github: options.github,
|
||||||
workspaceGitService: options.workspaceGitService ?? createNoopWorkspaceGitService(),
|
workspaceGitService: options.workspaceGitService ?? createNoopWorkspaceGitService(),
|
||||||
renameCurrentBranch: options.renameCurrentBranch,
|
renameCurrentBranch: options.renameCurrentBranch,
|
||||||
generateWorkspaceName: options.generateWorkspaceName,
|
generateWorkspaceName: options.generateWorkspaceName,
|
||||||
@@ -5986,11 +5994,195 @@ test("removing a contributing terminal clears workspace status", async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Worktree-source forwarding (action/refName/githubPrNumber/worktreeSlug) is
|
interface WorkspaceCreatePrRepoFixture {
|
||||||
// covered end-to-end against a real git repo in
|
tempDir: string;
|
||||||
// workspace-create-worktree-source.e2e.test.ts, where the created worktree's
|
repoDir: string;
|
||||||
// observable branch proves the request fields reached createWorktreeCore. We do
|
paseoHome: string;
|
||||||
// not intercept the private workflow here.
|
headRef: string;
|
||||||
|
prFileName: string;
|
||||||
|
prNumber: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWorkspaceCreatePrRepo(): WorkspaceCreatePrRepoFixture {
|
||||||
|
const tempDir = realpathSync(mkdtempSync(path.join(tmpdir(), "workspace-create-pr-")));
|
||||||
|
const repoDir = path.join(tempDir, "repo");
|
||||||
|
const remoteDir = path.join(tempDir, "origin.git");
|
||||||
|
const paseoHome = path.join(tempDir, ".paseo");
|
||||||
|
const prNumber = 123;
|
||||||
|
const headRef = "feature/review-pr";
|
||||||
|
const prFileName = "pr-123.txt";
|
||||||
|
|
||||||
|
execFileSync("git", ["init", "-b", "main", repoDir], { stdio: "pipe" });
|
||||||
|
execFileSync("git", ["config", "user.email", "test@getpaseo.local"], {
|
||||||
|
cwd: repoDir,
|
||||||
|
stdio: "pipe",
|
||||||
|
});
|
||||||
|
execFileSync("git", ["config", "user.name", "Paseo Test"], { cwd: repoDir, stdio: "pipe" });
|
||||||
|
execFileSync("git", ["config", "commit.gpgsign", "false"], { cwd: repoDir, stdio: "pipe" });
|
||||||
|
writeFileSync(path.join(repoDir, "README.md"), "main\n");
|
||||||
|
execFileSync("git", ["add", "README.md"], { cwd: repoDir, stdio: "pipe" });
|
||||||
|
execFileSync("git", ["commit", "-m", "initial"], { cwd: repoDir, stdio: "pipe" });
|
||||||
|
|
||||||
|
execFileSync("git", ["checkout", "-b", headRef], { cwd: repoDir, stdio: "pipe" });
|
||||||
|
writeFileSync(path.join(repoDir, prFileName), "review branch\n");
|
||||||
|
execFileSync("git", ["add", prFileName], { cwd: repoDir, stdio: "pipe" });
|
||||||
|
execFileSync("git", ["commit", "-m", "review branch"], { cwd: repoDir, stdio: "pipe" });
|
||||||
|
const prHead = execFileSync("git", ["rev-parse", "HEAD"], { cwd: repoDir, stdio: "pipe" })
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
execFileSync("git", ["clone", "--bare", repoDir, remoteDir], { stdio: "pipe" });
|
||||||
|
execFileSync(
|
||||||
|
"git",
|
||||||
|
[`--git-dir=${remoteDir}`, "update-ref", `refs/pull/${prNumber}/head`, prHead],
|
||||||
|
{
|
||||||
|
stdio: "pipe",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
execFileSync("git", [`--git-dir=${remoteDir}`, "update-ref", "-d", `refs/heads/${headRef}`], {
|
||||||
|
stdio: "pipe",
|
||||||
|
});
|
||||||
|
execFileSync("git", ["checkout", "main"], { cwd: repoDir, stdio: "pipe" });
|
||||||
|
execFileSync("git", ["branch", "-D", headRef], { cwd: repoDir, stdio: "pipe" });
|
||||||
|
execFileSync("git", ["remote", "add", "origin", remoteDir], { cwd: repoDir, stdio: "pipe" });
|
||||||
|
|
||||||
|
return { tempDir, repoDir, paseoHome, headRef, prFileName, prNumber };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPrCheckoutGitHubService(params: { headRef: string }): GitHubService {
|
||||||
|
return {
|
||||||
|
listPullRequests: async () => [],
|
||||||
|
listIssues: async () => [],
|
||||||
|
getPullRequest: async ({ number }) => ({
|
||||||
|
number,
|
||||||
|
title: `PR ${number}`,
|
||||||
|
url: `https://github.com/acme/repo/pull/${number}`,
|
||||||
|
state: "OPEN",
|
||||||
|
body: null,
|
||||||
|
baseRefName: "main",
|
||||||
|
headRefName: params.headRef,
|
||||||
|
labels: [],
|
||||||
|
updatedAt: "2026-03-01T12:00:00Z",
|
||||||
|
}),
|
||||||
|
getPullRequestHeadRef: async () => params.headRef,
|
||||||
|
getPullRequestCheckoutTarget: async ({ number }) => ({
|
||||||
|
number,
|
||||||
|
baseRefName: "main",
|
||||||
|
headRefName: params.headRef,
|
||||||
|
headOwnerLogin: null,
|
||||||
|
headRepositorySshUrl: null,
|
||||||
|
headRepositoryUrl: null,
|
||||||
|
isCrossRepository: false,
|
||||||
|
}),
|
||||||
|
getCurrentPullRequestStatus: async () => null,
|
||||||
|
getPullRequestTimeline: async ({ prNumber }) => ({
|
||||||
|
prNumber,
|
||||||
|
repoOwner: "acme",
|
||||||
|
repoName: "repo",
|
||||||
|
items: [],
|
||||||
|
truncated: false,
|
||||||
|
error: null,
|
||||||
|
}),
|
||||||
|
getGitHubCheckDetails: async ({ checkRunId, workflowRunId }) => ({
|
||||||
|
checkRunId,
|
||||||
|
workflowRunId: workflowRunId ?? null,
|
||||||
|
name: "test",
|
||||||
|
status: null,
|
||||||
|
conclusion: null,
|
||||||
|
url: null,
|
||||||
|
detailsUrl: null,
|
||||||
|
output: null,
|
||||||
|
annotations: [],
|
||||||
|
failedJobs: [],
|
||||||
|
truncated: false,
|
||||||
|
}),
|
||||||
|
searchIssuesAndPrs: async () => ({ items: [], githubFeaturesEnabled: true }),
|
||||||
|
createPullRequest: async () => ({ number: 1, url: "https://github.com/acme/repo/pull/1" }),
|
||||||
|
mergePullRequest: async () => ({ success: true }),
|
||||||
|
enablePullRequestAutoMerge: async () => ({ success: true }),
|
||||||
|
disablePullRequestAutoMerge: async () => ({ success: true }),
|
||||||
|
isAuthenticated: async () => true,
|
||||||
|
invalidate: () => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCurrentBranch(cwd: string): string {
|
||||||
|
return execFileSync("git", ["branch", "--show-current"], { cwd, stdio: "pipe" })
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
test("workspace.create worktree source checks out a GitHub PR from githubPrNumber", async () => {
|
||||||
|
const emitted: SessionOutboundMessage[] = [];
|
||||||
|
const fixture = createWorkspaceCreatePrRepo();
|
||||||
|
const projects = new Map<string, PersistedProjectRecord>();
|
||||||
|
const workspaces = new Map<string, PersistedWorkspaceRecord>();
|
||||||
|
const projectRegistry: SessionOptions["projectRegistry"] = {
|
||||||
|
initialize: async () => {},
|
||||||
|
existsOnDisk: async () => true,
|
||||||
|
list: async () => Array.from(projects.values()),
|
||||||
|
get: async (projectId: string) => projects.get(projectId) ?? null,
|
||||||
|
upsert: async (record) => {
|
||||||
|
projects.set(record.projectId, record);
|
||||||
|
},
|
||||||
|
archive: async () => {},
|
||||||
|
remove: async () => {},
|
||||||
|
};
|
||||||
|
const workspaceRegistry: SessionOptions["workspaceRegistry"] = {
|
||||||
|
initialize: async () => {},
|
||||||
|
existsOnDisk: async () => true,
|
||||||
|
list: async () => Array.from(workspaces.values()),
|
||||||
|
get: async (workspaceId: string) => workspaces.get(workspaceId) ?? null,
|
||||||
|
upsert: async (record) => {
|
||||||
|
workspaces.set(record.workspaceId, record);
|
||||||
|
},
|
||||||
|
archive: async () => {},
|
||||||
|
remove: async () => {},
|
||||||
|
};
|
||||||
|
const session = createSessionForWorkspaceTests({
|
||||||
|
onMessage: (message) => emitted.push(message),
|
||||||
|
github: createPrCheckoutGitHubService({
|
||||||
|
headRef: fixture.headRef,
|
||||||
|
}),
|
||||||
|
paseoHome: fixture.paseoHome,
|
||||||
|
projectRegistry,
|
||||||
|
workspaceRegistry,
|
||||||
|
workspaceGitService: createNoopWorkspaceGitService({
|
||||||
|
resolveRepoRoot: async () => fixture.repoDir,
|
||||||
|
resolveDefaultBranch: async () => "main",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await session.handleMessage({
|
||||||
|
type: "workspace.create.request",
|
||||||
|
requestId: "req-workspace-create-pr",
|
||||||
|
source: {
|
||||||
|
kind: "worktree",
|
||||||
|
cwd: fixture.repoDir,
|
||||||
|
action: "checkout",
|
||||||
|
githubPrNumber: fixture.prNumber,
|
||||||
|
worktreeSlug: "review-pr-workspace",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = findByType(emitted, "workspace.create.response");
|
||||||
|
expect(response?.payload.error).toBeNull();
|
||||||
|
expect(response?.payload.workspace).toMatchObject({
|
||||||
|
workspaceDirectory: expect.any(String),
|
||||||
|
gitRuntime: { currentBranch: fixture.headRef },
|
||||||
|
});
|
||||||
|
const workspaceDirectory = response?.payload.workspace?.workspaceDirectory as string;
|
||||||
|
expect(readCurrentBranch(workspaceDirectory)).toBe(fixture.headRef);
|
||||||
|
expect(existsSync(path.join(workspaceDirectory, fixture.prFileName))).toBe(true);
|
||||||
|
} finally {
|
||||||
|
await flushTerminalContributionWork();
|
||||||
|
rmSync(fixture.tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Worktree-source forwarding for action/refName/worktreeSlug is also covered
|
||||||
|
// end-to-end against a daemon in workspace-create-worktree-source.e2e.test.ts.
|
||||||
|
|
||||||
test("failed local create_agent_request does not schedule workspace title generation", async () => {
|
test("failed local create_agent_request does not schedule workspace title generation", async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
|
|||||||
Reference in New Issue
Block a user