Skip to content

Commit d61254b

Browse files
Copilotalexr00
andauthored
Support simplified URI format for checkout-pull-request command (#8094)
* Initial plan * Initial planning for simplified checkout-pull-request URL format Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Add support for simplified URL format with uri parameter Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Update comment to document both URL formats Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Address code review: Add $ anchor to regex and test extra path segments Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Remove changed proposals --------- 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 389363e commit d61254b

File tree

3 files changed

+136
-1
lines changed

3 files changed

+136
-1
lines changed

src/common/uri.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,27 @@ export function fromOpenOrCheckoutPullRequestWebviewUri(uri: vscode.Uri): OpenPu
685685
return;
686686
}
687687
try {
688+
// Check if the query uses the new simplified format: uri=https://github.com/owner/repo/pull/number
689+
const queryParams = new URLSearchParams(uri.query);
690+
const uriParam = queryParams.get('uri');
691+
if (uriParam) {
692+
// Parse the GitHub PR URL - match only exact format ending with the PR number
693+
const match = uriParam.match(/^https?:\/\/github\.com\/([^\/]+)\/([^\/]+)\/pull\/(\d+)$/);
694+
if (match) {
695+
const [, owner, repo, pullRequestNumber] = match;
696+
const params = {
697+
owner,
698+
repo,
699+
pullRequestNumber: parseInt(pullRequestNumber, 10)
700+
};
701+
if (!validateOpenWebviewParams(params.owner, params.repo, params.pullRequestNumber.toString())) {
702+
return;
703+
}
704+
return params;
705+
}
706+
}
707+
708+
// Fall back to the old JSON format for backward compatibility
688709
const query = JSON.parse(uri.query.split('&')[0]);
689710
if (!validateOpenWebviewParams(query.owner, query.repo, query.pullRequestNumber)) {
690711
return;

src/test/common/uri.test.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { default as assert } from 'assert';
7+
import * as vscode from 'vscode';
8+
import { fromOpenOrCheckoutPullRequestWebviewUri } from '../../common/uri';
9+
10+
describe('uri', () => {
11+
describe('fromOpenOrCheckoutPullRequestWebviewUri', () => {
12+
it('should parse the new simplified format with uri parameter', () => {
13+
const uri = vscode.Uri.parse('vscode://github.vscode-pull-request-github/checkout-pull-request?uri=https://github.com/microsoft/vscode-css-languageservice/pull/460');
14+
const result = fromOpenOrCheckoutPullRequestWebviewUri(uri);
15+
16+
assert.strictEqual(result?.owner, 'microsoft');
17+
assert.strictEqual(result?.repo, 'vscode-css-languageservice');
18+
assert.strictEqual(result?.pullRequestNumber, 460);
19+
});
20+
21+
it('should parse the new simplified format with http (not https)', () => {
22+
const uri = vscode.Uri.parse('vscode://github.vscode-pull-request-github/checkout-pull-request?uri=http://github.com/owner/repo/pull/123');
23+
const result = fromOpenOrCheckoutPullRequestWebviewUri(uri);
24+
25+
assert.strictEqual(result?.owner, 'owner');
26+
assert.strictEqual(result?.repo, 'repo');
27+
assert.strictEqual(result?.pullRequestNumber, 123);
28+
});
29+
30+
it('should parse the old JSON format for backward compatibility', () => {
31+
const uri = vscode.Uri.parse('vscode://github.vscode-pull-request-github/checkout-pull-request?%7B%22owner%22%3A%22microsoft%22%2C%22repo%22%3A%22vscode-css-languageservice%22%2C%22pullRequestNumber%22%3A460%7D');
32+
const result = fromOpenOrCheckoutPullRequestWebviewUri(uri);
33+
34+
assert.strictEqual(result?.owner, 'microsoft');
35+
assert.strictEqual(result?.repo, 'vscode-css-languageservice');
36+
assert.strictEqual(result?.pullRequestNumber, 460);
37+
});
38+
39+
it('should work for open-pull-request-webview path', () => {
40+
const uri = vscode.Uri.parse('vscode://github.vscode-pull-request-github/open-pull-request-webview?uri=https://github.com/test/example/pull/789');
41+
const result = fromOpenOrCheckoutPullRequestWebviewUri(uri);
42+
43+
assert.strictEqual(result?.owner, 'test');
44+
assert.strictEqual(result?.repo, 'example');
45+
assert.strictEqual(result?.pullRequestNumber, 789);
46+
});
47+
48+
it('should return undefined for invalid authority', () => {
49+
const uri = vscode.Uri.parse('vscode://invalid-authority/checkout-pull-request?uri=https://github.com/owner/repo/pull/1');
50+
const result = fromOpenOrCheckoutPullRequestWebviewUri(uri);
51+
52+
assert.strictEqual(result, undefined);
53+
});
54+
55+
it('should return undefined for invalid path', () => {
56+
const uri = vscode.Uri.parse('vscode://github.vscode-pull-request-github/invalid-path?uri=https://github.com/owner/repo/pull/1');
57+
const result = fromOpenOrCheckoutPullRequestWebviewUri(uri);
58+
59+
assert.strictEqual(result, undefined);
60+
});
61+
62+
it('should return undefined for invalid GitHub URL format', () => {
63+
const uri = vscode.Uri.parse('vscode://github.vscode-pull-request-github/checkout-pull-request?uri=https://example.com/owner/repo/pull/1');
64+
const result = fromOpenOrCheckoutPullRequestWebviewUri(uri);
65+
66+
assert.strictEqual(result, undefined);
67+
});
68+
69+
it('should return undefined for non-numeric pull request number', () => {
70+
const uri = vscode.Uri.parse('vscode://github.vscode-pull-request-github/checkout-pull-request?uri=https://github.com/owner/repo/pull/abc');
71+
const result = fromOpenOrCheckoutPullRequestWebviewUri(uri);
72+
73+
assert.strictEqual(result, undefined);
74+
});
75+
76+
it('should handle repos with dots and dashes', () => {
77+
const uri = vscode.Uri.parse('vscode://github.vscode-pull-request-github/checkout-pull-request?uri=https://github.com/my-org/my.awesome-repo/pull/42');
78+
const result = fromOpenOrCheckoutPullRequestWebviewUri(uri);
79+
80+
assert.strictEqual(result?.owner, 'my-org');
81+
assert.strictEqual(result?.repo, 'my.awesome-repo');
82+
assert.strictEqual(result?.pullRequestNumber, 42);
83+
});
84+
85+
it('should handle repos with underscores', () => {
86+
const uri = vscode.Uri.parse('vscode://github.vscode-pull-request-github/checkout-pull-request?uri=https://github.com/owner/repo_name/pull/1');
87+
const result = fromOpenOrCheckoutPullRequestWebviewUri(uri);
88+
89+
assert.strictEqual(result?.owner, 'owner');
90+
assert.strictEqual(result?.repo, 'repo_name');
91+
assert.strictEqual(result?.pullRequestNumber, 1);
92+
});
93+
94+
it('should validate owner and repo names', () => {
95+
// Invalid owner (empty)
96+
const uri1 = vscode.Uri.parse('vscode://github.vscode-pull-request-github/checkout-pull-request?uri=https://github.com//repo/pull/1');
97+
const result1 = fromOpenOrCheckoutPullRequestWebviewUri(uri1);
98+
assert.strictEqual(result1, undefined);
99+
});
100+
101+
it('should reject URLs with extra path segments after PR number', () => {
102+
// URL with /files suffix should be rejected
103+
const uri1 = vscode.Uri.parse('vscode://github.vscode-pull-request-github/checkout-pull-request?uri=https://github.com/owner/repo/pull/123/files');
104+
const result1 = fromOpenOrCheckoutPullRequestWebviewUri(uri1);
105+
assert.strictEqual(result1, undefined);
106+
107+
// URL with /commits suffix should be rejected
108+
const uri2 = vscode.Uri.parse('vscode://github.vscode-pull-request-github/checkout-pull-request?uri=https://github.com/owner/repo/pull/456/commits');
109+
const result2 = fromOpenOrCheckoutPullRequestWebviewUri(uri2);
110+
assert.strictEqual(result2, undefined);
111+
});
112+
});
113+
});

src/uriHandler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ export class UriHandler implements vscode.UriHandler {
115115
case UriHandlerPaths.OpenPullRequestWebview:
116116
return this._openPullRequestWebview(uri);
117117
case UriHandlerPaths.CheckoutPullRequest:
118-
// example vscode-insiders://github.vscode-pull-request-github/checkout-pull-request?%7B%22owner%22%3A%22alexr00%22%2C%22repo%22%3A%22playground%22%2C%22pullRequestNumber%22%3A714%7D
118+
// Simplified format example: vscode-insiders://github.vscode-pull-request-github/checkout-pull-request?uri=https://github.com/microsoft/vscode-css-languageservice/pull/460
119+
// Legacy format example: vscode-insiders://github.vscode-pull-request-github/checkout-pull-request?%7B%22owner%22%3A%22alexr00%22%2C%22repo%22%3A%22playground%22%2C%22pullRequestNumber%22%3A714%7D
119120
return this._checkoutPullRequest(uri);
120121
}
121122
}

0 commit comments

Comments
 (0)