Skip to content

Commit b0bf1a5

Browse files
Copilotalexr00
andauthored
Add Codespaces checkout option to PR dropdown and tree context menu (#8298)
* Initial plan * Initial plan for Codespaces checkout feature Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Add Codespaces checkout command and utility function Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * on -> in * Fix URL format * Revert "on -> in" This reverts commit 4233995. * Add Codespaces checkout to PR tree context menu Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Refactor: extract common logic from pr.pickOnVscodeDev and pr.pickOnCodespaces Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Add async keyword to command callbacks for clarity Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * in -> on --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
1 parent 666fb14 commit b0bf1a5

4 files changed

Lines changed: 78 additions & 16 deletions

File tree

package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,12 @@
996996
"category": "%command.pull.request.category%",
997997
"icon": "$(globe)"
998998
},
999+
{
1000+
"command": "pr.pickOnCodespaces",
1001+
"title": "%command.pr.pickOnCodespaces.title%",
1002+
"category": "%command.pull.request.category%",
1003+
"icon": "$(cloud)"
1004+
},
9991005
{
10001006
"command": "pr.exit",
10011007
"title": "%command.pr.exit.title%",
@@ -1442,6 +1448,11 @@
14421448
"title": "%command.pr.checkoutOnVscodeDevFromDescription.title%",
14431449
"category": "%command.pull.request.category%"
14441450
},
1451+
{
1452+
"command": "pr.checkoutOnCodespacesFromDescription",
1453+
"title": "%command.pr.checkoutOnCodespacesFromDescription.title%",
1454+
"category": "%command.pull.request.category%"
1455+
},
14451456
{
14461457
"command": "pr.openSessionLogFromDescription",
14471458
"title": "%command.pr.openSessionLogFromDescription.title%",
@@ -2075,6 +2086,10 @@
20752086
"command": "pr.pickOnVscodeDev",
20762087
"when": "false"
20772088
},
2089+
{
2090+
"command": "pr.pickOnCodespaces",
2091+
"when": "false"
2092+
},
20782093
{
20792094
"command": "pr.exit",
20802095
"when": "github:inReviewMode"
@@ -2874,6 +2889,11 @@
28742889
"when": "view == pr:github && viewItem =~ /pullrequest(:local)?:nonactive/ && (!isWeb || remoteName != codespaces && virtualWorkspace != vscode-vfs)",
28752890
"group": "1_pullrequest@2"
28762891
},
2892+
{
2893+
"command": "pr.pickOnCodespaces",
2894+
"when": "view == pr:github && viewItem =~ /pullrequest(:local)?:nonactive/ && (!isWeb || remoteName != codespaces && virtualWorkspace != vscode-vfs)",
2895+
"group": "1_pullrequest@3"
2896+
},
28772897
{
28782898
"command": "pr.openChanges",
28792899
"when": "view =~ /(pr|prStatus):github/ && viewItem =~ /(pullrequest|description)/ && config.multiDiffEditor.experimental.enabled",
@@ -3627,6 +3647,11 @@
36273647
"group": "checkout@1",
36283648
"when": "webviewId == PullRequestOverview && github:checkoutMenu"
36293649
},
3650+
{
3651+
"command": "pr.checkoutOnCodespacesFromDescription",
3652+
"group": "checkout@2",
3653+
"when": "webviewId == PullRequestOverview && github:checkoutMenu"
3654+
},
36303655
{
36313656
"command": "pr.openSessionLogFromDescription",
36323657
"when": "webviewId == PullRequestOverview && github:codingAgentMenu"

package.nls.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@
200200
"command.pr.pick.title": "Checkout Pull Request",
201201
"command.pr.openChanges.title": "Open Changes",
202202
"command.pr.pickOnVscodeDev.title": "Checkout Pull Request on vscode.dev",
203+
"command.pr.pickOnCodespaces.title": "Checkout Pull Request on Codespaces",
203204
"command.pr.exit.title": "Checkout Default Branch",
204205
"command.pr.dismissNotification.title": "Dismiss Notification",
205206
"command.pr.markAllCopilotNotificationsAsRead.title": "Dismiss All Copilot Notifications",
@@ -299,6 +300,7 @@
299300
"command.pr.checkoutFromDescription.title": "Checkout",
300301
"command.pr.applyChangesFromDescription.title": "Apply Changes",
301302
"command.pr.checkoutOnVscodeDevFromDescription.title": "Checkout on vscode.dev",
303+
"command.pr.checkoutOnCodespacesFromDescription.title": "Checkout on Codespaces",
302304
"command.pr.openSessionLogFromDescription.title": "View Session",
303305
"command.issue.openDescription.title": "View Issue Description",
304306
"command.issue.copyGithubDevLink.title": "Copy github.dev Link",

src/commands.ts

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { PullRequestModel } from './github/pullRequestModel';
3131
import { PullRequestOverviewPanel } from './github/pullRequestOverview';
3232
import { chooseItem } from './github/quickPicks';
3333
import { RepositoriesManager } from './github/repositoriesManager';
34-
import { getIssuesUrl, getPullsUrl, isInCodespaces, ISSUE_OR_URL_EXPRESSION, parseIssueExpressionOutput, vscodeDevPrLink } from './github/utils';
34+
import { codespacesPrLink, getIssuesUrl, getPullsUrl, isInCodespaces, ISSUE_OR_URL_EXPRESSION, parseIssueExpressionOutput, vscodeDevPrLink } from './github/utils';
3535
import { OverviewContext } from './github/views';
3636
import { isNotificationTreeItem, NotificationTreeItem } from './notifications/notificationItem';
3737
import { NotificationsManager } from './notifications/notificationsManager';
@@ -694,24 +694,38 @@ export function registerCommands(
694694
}
695695
}));
696696

697-
context.subscriptions.push(
698-
vscode.commands.registerCommand('pr.pickOnVscodeDev', async (pr: PRNode | RepositoryChangesNode | PullRequestModel) => {
699-
if (pr === undefined) {
700-
// This is unexpected, but has happened a few times.
701-
Logger.error('Unexpectedly received undefined when picking a PR.', logId);
702-
return vscode.window.showErrorMessage(vscode.l10n.t('No pull request was selected to checkout, please try again.'));
703-
}
697+
const pickPullRequest = async (pr: PRNode | RepositoryChangesNode | PullRequestModel, linkGenerator: (pr: PullRequestModel) => string, requiresHead: boolean = false) => {
698+
if (pr === undefined) {
699+
// This is unexpected, but has happened a few times.
700+
Logger.error('Unexpectedly received undefined when picking a PR.', logId);
701+
return vscode.window.showErrorMessage(vscode.l10n.t('No pull request was selected to checkout, please try again.'));
702+
}
704703

705-
let pullRequestModel: PullRequestModel;
704+
let pullRequestModel: PullRequestModel;
706705

707-
if (pr instanceof PRNode || pr instanceof RepositoryChangesNode) {
708-
pullRequestModel = pr.pullRequestModel;
709-
} else {
710-
pullRequestModel = pr;
711-
}
706+
if (pr instanceof PRNode || pr instanceof RepositoryChangesNode) {
707+
pullRequestModel = pr.pullRequestModel;
708+
} else {
709+
pullRequestModel = pr;
710+
}
712711

713-
return vscode.env.openExternal(vscode.Uri.parse(vscodeDevPrLink(pullRequestModel)));
714-
}),
712+
if (requiresHead && !pullRequestModel.head) {
713+
return vscode.window.showErrorMessage(vscode.l10n.t('Unable to checkout pull request: missing head branch information.'));
714+
}
715+
716+
return vscode.env.openExternal(vscode.Uri.parse(linkGenerator(pullRequestModel)));
717+
};
718+
719+
context.subscriptions.push(
720+
vscode.commands.registerCommand('pr.pickOnVscodeDev', async (pr: PRNode | RepositoryChangesNode | PullRequestModel) =>
721+
pickPullRequest(pr, vscodeDevPrLink)
722+
),
723+
);
724+
725+
context.subscriptions.push(
726+
vscode.commands.registerCommand('pr.pickOnCodespaces', async (pr: PRNode | RepositoryChangesNode | PullRequestModel) =>
727+
pickPullRequest(pr, codespacesPrLink, true)
728+
),
715729
);
716730

717731
context.subscriptions.push(vscode.commands.registerCommand('pr.checkoutOnVscodeDevFromDescription', async (context: OverviewContext | undefined) => {
@@ -725,6 +739,20 @@ export function registerCommands(
725739
return vscode.env.openExternal(vscode.Uri.parse(vscodeDevPrLink(resolved.pr)));
726740
}));
727741

742+
context.subscriptions.push(vscode.commands.registerCommand('pr.checkoutOnCodespacesFromDescription', async (context: OverviewContext | undefined) => {
743+
if (!context) {
744+
return vscode.window.showErrorMessage(vscode.l10n.t('No pull request context provided for checkout.'));
745+
}
746+
const resolved = await resolvePr(context);
747+
if (!resolved) {
748+
return vscode.window.showErrorMessage(vscode.l10n.t('Unable to resolve pull request for checkout.'));
749+
}
750+
if (!resolved.pr.head) {
751+
return vscode.window.showErrorMessage(vscode.l10n.t('Unable to checkout pull request: missing head branch information.'));
752+
}
753+
return vscode.env.openExternal(vscode.Uri.parse(codespacesPrLink(resolved.pr)));
754+
}));
755+
728756
context.subscriptions.push(vscode.commands.registerCommand('pr.openSessionLogFromDescription', async (context: SessionLinkInfo | undefined) => {
729757
if (!context) {
730758
return vscode.window.showErrorMessage(vscode.l10n.t('No pull request context provided for checkout.'));

src/github/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
} from './interface';
4040
import { IssueModel } from './issueModel';
4141
import { GHPRComment, GHPRCommentThread } from './prComment';
42+
import { PullRequestModel } from './pullRequestModel';
4243
import { RemoteInfo } from '../../common/types';
4344
import { Repository } from '../api/api';
4445
import { GitApiImpl } from '../api/api1';
@@ -1787,6 +1788,12 @@ export function vscodeDevPrLink(pullRequest: IssueModel) {
17871788
return `https://${vscode.env.appName.toLowerCase().includes('insider') ? 'insiders.' : ''}vscode.dev/github${itemUri.path}`;
17881789
}
17891790

1791+
export function codespacesPrLink(pullRequest: PullRequestModel): string {
1792+
const repoFullName = `${pullRequest.head!.owner}/${pullRequest.remote.repositoryName}`;
1793+
const branch = pullRequest.head!.ref;
1794+
return `https://github.com/codespaces/new/${encodeURIComponent(repoFullName)}/tree/${encodeURIComponent(branch)}`;
1795+
}
1796+
17901797
export function makeLabel(label: ILabel): string {
17911798
const isDarkTheme = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark;
17921799
const labelColor = gitHubLabelColor(label.color, isDarkTheme, true);

0 commit comments

Comments
 (0)