Skip to content

Commit 7abeefa

Browse files
Copilotalexr00
andauthored
Support GitHub PR URLs in "Checkout Pull Request by Number" command (#7258)
* Initial plan * Implement PR checkout by URL support Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Improve validation for PR checkout by URL feature Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Address PR review feedback: extract helper function, add repo validation, revert title Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * return type * Revert yarn.lock changes Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --------- 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 6131c9b commit 7abeefa

File tree

2 files changed

+53
-9
lines changed

2 files changed

+53
-9
lines changed

src/commands.ts

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { GHPRComment, GHPRCommentThread, TemporaryComment } from './github/prCom
2929
import { PullRequestModel } from './github/pullRequestModel';
3030
import { PullRequestOverviewPanel } from './github/pullRequestOverview';
3131
import { RepositoriesManager } from './github/repositoriesManager';
32-
import { getIssuesUrl, getPullsUrl, isInCodespaces, vscodeDevPrLink } from './github/utils';
32+
import { getIssuesUrl, getPullsUrl, isInCodespaces, ISSUE_OR_URL_EXPRESSION, parseIssueExpressionOutput, vscodeDevPrLink } from './github/utils';
3333
import { isNotificationTreeItem, NotificationTreeItem } from './notifications/notificationItem';
3434
import { PullRequestsTreeDataProvider } from './view/prsTreeDataProvider';
3535
import { ReviewCommentController } from './view/reviewCommentController';
@@ -1456,6 +1456,31 @@ ${contents}
14561456
}
14571457
}));
14581458

1459+
function validateAndParseInput(input: string, expectedOwner: string, expectedRepo: string): { isValid: true; prNumber: number; errorMessage?: string } | { isValid: false; prNumber?: number; errorMessage: string } {
1460+
const prNumberMatcher = /^#?(\d*)$/;
1461+
const numberMatches = input.match(prNumberMatcher);
1462+
if (numberMatches && (numberMatches.length === 2) && !Number.isNaN(Number(numberMatches[1]))) {
1463+
const num = Number(numberMatches[1]);
1464+
if (num > 0) {
1465+
return { isValid: true, prNumber: num };
1466+
}
1467+
}
1468+
1469+
const urlMatches = input.match(ISSUE_OR_URL_EXPRESSION);
1470+
const parsed = parseIssueExpressionOutput(urlMatches);
1471+
if (parsed && parsed.issueNumber && parsed.issueNumber > 0) {
1472+
// Check if the repository owner and name match
1473+
if (parsed.owner && parsed.name) {
1474+
if (parsed.owner !== expectedOwner || parsed.name !== expectedRepo) {
1475+
return { isValid: false, errorMessage: vscode.l10n.t('Repository in URL does not match the selected repository') };
1476+
}
1477+
}
1478+
return { isValid: true, prNumber: parsed.issueNumber };
1479+
}
1480+
1481+
return { isValid: false, errorMessage: vscode.l10n.t('Value must be a pull request number or GitHub URL') };
1482+
}
1483+
14591484
context.subscriptions.push(
14601485
vscode.commands.registerCommand('pr.checkoutByNumber', async () => {
14611486

@@ -1473,21 +1498,24 @@ ${contents}
14731498
if (!githubRepo) {
14741499
return;
14751500
}
1476-
const prNumberMatcher = /^#?(\d*)$/;
14771501
const prNumber = await vscode.window.showInputBox({
1478-
ignoreFocusOut: true, prompt: vscode.l10n.t('Enter the pull request number'),
1502+
ignoreFocusOut: true, prompt: vscode.l10n.t('Enter the pull request number or URL'),
14791503
validateInput: (input: string) => {
1480-
const matches = input.match(prNumberMatcher);
1481-
if (!matches || (matches.length !== 2) || Number.isNaN(Number(matches[1]))) {
1482-
return vscode.l10n.t('Value must be a number');
1483-
}
1484-
return undefined;
1504+
const result = validateAndParseInput(input, githubRepo.repo.remote.owner, githubRepo.repo.remote.repositoryName);
1505+
return result.isValid ? undefined : result.errorMessage;
14851506
}
14861507
});
14871508
if ((prNumber === undefined) || prNumber === '#') {
14881509
return;
14891510
}
1490-
const prModel = await githubRepo.manager.fetchById(githubRepo.repo, Number(prNumber.match(prNumberMatcher)![1]));
1511+
1512+
// Extract PR number from input (either direct number or URL)
1513+
const parseResult = validateAndParseInput(prNumber, githubRepo.repo.remote.owner, githubRepo.repo.remote.repositoryName);
1514+
if (!parseResult.isValid) {
1515+
return vscode.window.showErrorMessage(parseResult.errorMessage || vscode.l10n.t('Invalid pull request number or URL'));
1516+
}
1517+
1518+
const prModel = await githubRepo.manager.fetchById(githubRepo.repo, parseResult.prNumber);
14911519
if (prModel) {
14921520
return ReviewManager.getReviewManagerForFolderManager(reviewsManager.reviewManagers, githubRepo.manager)?.switch(prModel);
14931521
}

src/test/issues/issuesUtils.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,21 @@ describe('Issues utilities', function () {
4848
const notIssue = '#a4';
4949
const notIssueParsed = parseIssueExpressionOutput(notIssue.match(ISSUE_OR_URL_EXPRESSION));
5050
assert.strictEqual(notIssueParsed, undefined);
51+
52+
// Test PR URL parsing
53+
const prUrl = 'https://github.com/microsoft/vscode/pull/123';
54+
const prUrlParsed = parseIssueExpressionOutput(prUrl.match(ISSUE_OR_URL_EXPRESSION));
55+
assert.strictEqual(prUrlParsed?.issueNumber, 123);
56+
assert.strictEqual(prUrlParsed?.commentNumber, undefined);
57+
assert.strictEqual(prUrlParsed?.name, 'vscode');
58+
assert.strictEqual(prUrlParsed?.owner, 'microsoft');
59+
60+
// Test HTTP PR URL (without S)
61+
const prUrlHttp = 'http://github.com/owner/repo/pull/456';
62+
const prUrlHttpParsed = parseIssueExpressionOutput(prUrlHttp.match(ISSUE_OR_URL_EXPRESSION));
63+
assert.strictEqual(prUrlHttpParsed?.issueNumber, 456);
64+
assert.strictEqual(prUrlHttpParsed?.commentNumber, undefined);
65+
assert.strictEqual(prUrlHttpParsed?.name, 'repo');
66+
assert.strictEqual(prUrlHttpParsed?.owner, 'owner');
5167
});
5268
});

0 commit comments

Comments
 (0)