Start new workspaces from pasted pull requests (#2290)

* fix(workspaces): start pasted pull requests from their branch

Recognized pull request links now select their branch directly instead of
waiting for a separate confirmation. Ensure single-branch clones also record
the fetched branch so the new worktree can track it.

* fix(workspaces): keep pasted PR checkout target-safe

* fix(workspaces): make pasted PR resolution deterministic

* fix(workspaces): preserve explicit checkout selection

A recognized pull request selects itself only when its attachment is added. Once the user chooses a branch, later composer edits must not derive checkout state from existing attachments.

* fix(composer): ignore stale pull request lookups

A lookup may finish after the workspace target changes. Only apply its attachment and selection event when the host, working directory, client, and remote still match the target that started it.

* fix(workspaces): make checkout choices authoritative

Arm automatic pull request selection only when a new PR link is detected. An explicit picker choice cancels that pending selection, existing draft attachments never infer checkout state, and target changes suppress carried-over lookups.

* fix(workspaces): stabilize pull request auto-selection

Keep the first PR selected when one edit contains several links, and accept a completed lookup when only the transport client changed for the same host and checkout.

* fix(composer): release removed pull request lookups

Release each auto-attach invocation when its composer state is abandoned, while continuing to discard any stale response.
This commit is contained in:
Mohamed Boudra
2026-07-21 15:20:05 +02:00
committed by GitHub
parent fd4e13735c
commit 2cb1b041dd
20 changed files with 768 additions and 313 deletions
+61 -1
View File
@@ -1,5 +1,5 @@
import { execFileSync, execSync } from "node:child_process";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import path from "node:path";
export function hasGithubAuth(): boolean {
@@ -59,6 +59,12 @@ export interface GhDefaultBranchClone {
cleanup(): Promise<void>;
}
export interface LocalGhPrFixture {
pr: GhPrFixture;
mainCheckout: GhDefaultBranchClone;
cleanup(): Promise<void>;
}
function gh(args: string[], opts?: { cwd?: string }): string {
return execFileSync("gh", args, {
cwd: opts?.cwd,
@@ -281,3 +287,57 @@ export async function cloneGithubRepoDefaultBranchOnly(
},
};
}
export async function createLocalGithubPrFixture(): Promise<LocalGhPrFixture> {
const fixtureRoot = await mkdtemp(path.join("/tmp", "paseo-e2e-local-github-pr-"));
const basePath = path.join(fixtureRoot, "base");
const remotePath = path.join(fixtureRoot, "remote.git");
const checkoutPath = path.join(fixtureRoot, "main-only");
const githubUrl = "https://github.com/paseo-e2e/local-fixture.git";
await mkdir(basePath);
git(["init", "-b", "main"], basePath);
git(["config", "user.email", "e2e@paseo.test"], basePath);
git(["config", "user.name", "Paseo E2E"], basePath);
git(["config", "commit.gpgsign", "false"], basePath);
await writeFile(path.join(basePath, "README.md"), "# Local GitHub fixture\n");
git(["add", "README.md"], basePath);
git(["commit", "-m", "Initial commit"], basePath);
git(["checkout", "-b", "pr-branch-1"], basePath);
await writeFile(path.join(basePath, "pr-1.txt"), "PR 1\n");
git(["add", "pr-1.txt"], basePath);
git(["commit", "-m", "Add PR 1"], basePath);
git(["checkout", "main"], basePath);
execFileSync("git", ["clone", "--quiet", "--bare", basePath, remotePath], {
stdio: ["ignore", "pipe", "pipe"],
});
git(["update-ref", "refs/pull/1/head", "refs/heads/pr-branch-1"], remotePath);
execFileSync(
"git",
["clone", "--quiet", "--single-branch", "--branch", "main", remotePath, checkoutPath],
{ stdio: ["ignore", "pipe", "pipe"] },
);
git(["remote", "set-url", "origin", githubUrl], checkoutPath);
git(["config", `url.${remotePath}.insteadOf`, githubUrl], checkoutPath);
git(["config", "user.email", "e2e@paseo.test"], checkoutPath);
git(["config", "user.name", "Paseo E2E"], checkoutPath);
git(["config", "commit.gpgsign", "false"], checkoutPath);
return {
pr: {
number: 1,
title: "Use pasted PR as start ref",
url: "https://github.com/paseo-e2e/local-fixture/pull/1",
branch: "pr-branch-1",
localPath: basePath,
},
mainCheckout: {
path: checkoutPath,
cleanup: async () => {},
},
cleanup: async () => {
await rm(fixtureRoot, { recursive: true, force: true });
},
};
}