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:
@@ -621,6 +621,52 @@ describe.skipIf(isPlatform("win32"))("worktree-core POSIX-only", () => {
|
||||
expect(result.worktree.branchName).toBe("pr-123");
|
||||
});
|
||||
|
||||
test("tracks a same-repo PR branch from a single-branch clone", async () => {
|
||||
const { tempDir, repoDir, remoteDir, paseoHome } = createSameRepoGitHubPrRemoteRepo();
|
||||
cleanupPaths.push(tempDir);
|
||||
execFileSync("git", ["config", "--unset-all", "remote.origin.fetch"], {
|
||||
cwd: repoDir,
|
||||
stdio: "pipe",
|
||||
});
|
||||
execFileSync(
|
||||
"git",
|
||||
["config", "--add", "remote.origin.fetch", "+refs/heads/main:refs/remotes/origin/main"],
|
||||
{ cwd: repoDir, stdio: "pipe" },
|
||||
);
|
||||
execFileSync("git", ["update-ref", "-d", "refs/remotes/origin/daemon-shutdown-diagnostics"], {
|
||||
cwd: repoDir,
|
||||
stdio: "pipe",
|
||||
});
|
||||
const github = {
|
||||
...createGitHubServiceStub(),
|
||||
getPullRequestCheckoutTarget: async () => ({
|
||||
number: 1790,
|
||||
baseRefName: "main",
|
||||
headRefName: "daemon-shutdown-diagnostics",
|
||||
headOwnerLogin: "getpaseo",
|
||||
headRepositorySshUrl: remoteDir,
|
||||
headRepositoryUrl: remoteDir,
|
||||
isCrossRepository: false,
|
||||
}),
|
||||
};
|
||||
|
||||
const result = await createCoreWorktree(
|
||||
{
|
||||
cwd: repoDir,
|
||||
action: "checkout",
|
||||
githubPrNumber: 1790,
|
||||
paseoHome,
|
||||
runSetup: false,
|
||||
},
|
||||
createCoreDeps({ github }),
|
||||
);
|
||||
|
||||
expect(result.worktree.branchName).toBe("daemon-shutdown-diagnostics");
|
||||
expect(getBranchUpstream(result.worktree.worktreePath)).toBe(
|
||||
"origin/daemon-shutdown-diagnostics",
|
||||
);
|
||||
});
|
||||
|
||||
test("checks out a GitLab MR source branch through the resolved forge service", async () => {
|
||||
const { tempDir, repoDir, paseoHome } = createGitRepoWithOriginFeatureBranch();
|
||||
cleanupPaths.push(tempDir);
|
||||
|
||||
@@ -1511,7 +1511,33 @@ async function tryFetchWorktreeTrackingRemote(options: {
|
||||
acceptExitCodes: [0, 1, 128],
|
||||
},
|
||||
);
|
||||
return result.exitCode === 0 ? { name: options.remoteName, headRef: options.headRef } : undefined;
|
||||
if (result.exitCode !== 0) {
|
||||
return undefined;
|
||||
}
|
||||
await ensureRemoteFetchesBranch(options);
|
||||
return { name: options.remoteName, headRef: options.headRef };
|
||||
}
|
||||
|
||||
async function ensureRemoteFetchesBranch(options: {
|
||||
cwd: string;
|
||||
remoteName: string;
|
||||
headRef: string;
|
||||
}): Promise<void> {
|
||||
const configKey = `remote.${options.remoteName}.fetch`;
|
||||
const exactRefspec = `refs/heads/${options.headRef}:refs/remotes/${options.remoteName}/${options.headRef}`;
|
||||
const wildcardRefspec = `refs/heads/*:refs/remotes/${options.remoteName}/*`;
|
||||
const { stdout } = await runGitCommand(["config", "--get-all", configKey], {
|
||||
cwd: options.cwd,
|
||||
acceptExitCodes: [0, 1],
|
||||
});
|
||||
const alreadyTracked = stdout
|
||||
.split("\n")
|
||||
.map((refspec) => refspec.trim().replace(/^\+/, ""))
|
||||
.some((refspec) => refspec === exactRefspec || refspec === wildcardRefspec);
|
||||
if (alreadyTracked) {
|
||||
return;
|
||||
}
|
||||
await runGitCommand(["config", "--add", configKey, `+${exactRefspec}`], { cwd: options.cwd });
|
||||
}
|
||||
|
||||
async function getWorktreeRemotePushUrl(
|
||||
|
||||
Reference in New Issue
Block a user