Skip to content

Commit 2c5dcb3

Browse files
authored
Allow Continue On between repos with HTTPS and SSH remotes (microsoft#182352)
* Deduplicate HTTPS and SSH remote URLs * Improve doc
1 parent 06fc826 commit 2c5dcb3

16 files changed

+295
-3
lines changed

extensions/git/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"diffCommand",
1414
"contribEditorContentMenu",
1515
"contribEditSessions",
16+
"canonicalUriIdentityProvider",
1617
"contribViewsWelcome",
1718
"editSessionIdentityProvider",
1819
"quickDiffProvider",

extensions/git/src/editSessionIdentityProvider.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class GitEditSessionIdentityProvider implements vscode.EditSessionIdentit
2424
this.providerRegistration.dispose();
2525
}
2626

27-
async provideEditSessionIdentity(workspaceFolder: vscode.WorkspaceFolder, _token: vscode.CancellationToken): Promise<string | undefined> {
27+
async provideEditSessionIdentity(workspaceFolder: vscode.WorkspaceFolder, token: vscode.CancellationToken): Promise<string | undefined> {
2828
await this.model.openRepository(path.dirname(workspaceFolder.uri.fsPath));
2929

3030
const repository = this.model.getRepository(workspaceFolder.uri);
@@ -34,8 +34,11 @@ export class GitEditSessionIdentityProvider implements vscode.EditSessionIdentit
3434
return undefined;
3535
}
3636

37+
const remoteUrl = repository.remotes.find((remote) => remote.name === repository.HEAD?.upstream?.remote)?.pushUrl?.replace(/^(git@[^\/:]+)(:)/i, 'ssh://$1/');
38+
const remote = remoteUrl ? await vscode.workspace.provideCanonicalUriIdentity(vscode.Uri.parse(remoteUrl), token) : null;
39+
3740
return JSON.stringify({
38-
remote: repository.remotes.find((remote) => remote.name === repository.HEAD?.upstream?.remote)?.pushUrl ?? null,
41+
remote: remote?.toString() ?? remoteUrl,
3942
ref: repository.HEAD?.upstream?.name ?? null,
4043
sha: repository.HEAD?.commit ?? null,
4144
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
declare module 'vscode' {
7+
8+
// https://github.com/microsoft/vscode/issues/180582
9+
10+
export namespace workspace {
11+
/**
12+
*
13+
* @param scheme The URI scheme that this provider can provide canonical URI identities for.
14+
* A canonical URI represents the conversion of a resource's alias into a source of truth URI.
15+
* Multiple aliases may convert to the same source of truth URI.
16+
* @param provider A provider which can convert URIs for workspace folders of scheme @param scheme to
17+
* a canonical URI identifier which is stable across machines.
18+
*/
19+
export function registerCanonicalUriIdentityProvider(scheme: string, provider: CanonicalUriIdentityProvider): Disposable;
20+
21+
/**
22+
*
23+
* @param uri The URI to provide a canonical URI identity for.
24+
* @param token A cancellation token for the request.
25+
*/
26+
export function provideCanonicalUriIdentity(uri: Uri, token: CancellationToken): ProviderResult<Uri>;
27+
}
28+
29+
export interface CanonicalUriIdentityProvider {
30+
/**
31+
*
32+
* @param uri The URI to provide a canonical URI identity for.
33+
* @param token A cancellation token for the request.
34+
* @returns The canonical URI identity for the requested URI.
35+
*/
36+
provideCanonicalUriIdentity(uri: Uri, token: CancellationToken): ProviderResult<Uri>;
37+
}
38+
}

extensions/github/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
},
2828
"enabledApiProposals": [
2929
"contribShareMenu",
30-
"contribEditSessions"
30+
"contribEditSessions",
31+
"canonicalUriIdentityProvider"
3132
],
3233
"contributes": {
3334
"commands": [
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 { CancellationToken, CanonicalUriIdentityProvider, Disposable, Uri, workspace } from 'vscode';
7+
8+
const SUPPORTED_SCHEMES = ['ssh', 'https'];
9+
10+
export class GitHubCanonicalUriIdentityProvider implements CanonicalUriIdentityProvider {
11+
12+
private disposables: Disposable[] = [];
13+
constructor() {
14+
this.disposables.push(...SUPPORTED_SCHEMES.map((scheme) => workspace.registerCanonicalUriIdentityProvider(scheme, this)));
15+
}
16+
17+
dispose() { this.disposables.forEach((disposable) => disposable.dispose()); }
18+
19+
async provideCanonicalUriIdentity(uri: Uri, _token: CancellationToken): Promise<Uri | undefined> {
20+
switch (uri.scheme) {
21+
case 'ssh':
22+
// if this is a git@github.com URI, return the HTTPS equivalent
23+
if (uri.authority === '[email protected]') {
24+
const [owner, repo] = (uri.path.endsWith('.git') ? uri.path.slice(0, -4) : uri.path).split('/').filter((segment) => segment.length > 0);
25+
return Uri.parse(`https://github.com/${owner}/${repo}`);
26+
}
27+
break;
28+
case 'https':
29+
if (uri.authority === 'github.com') {
30+
return uri;
31+
}
32+
break;
33+
}
34+
35+
return undefined;
36+
}
37+
}

extensions/github/src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { GithubPushErrorHandler } from './pushErrorHandler';
1313
import { GitBaseExtension } from './typings/git-base';
1414
import { GithubRemoteSourcePublisher } from './remoteSourcePublisher';
1515
import { GithubBranchProtectionProviderManager } from './branchProtection';
16+
import { GitHubCanonicalUriIdentityProvider } from './canonicalUriIdentityProvider';
1617

1718
export function activate(context: ExtensionContext): void {
1819
const disposables: Disposable[] = [];
@@ -29,6 +30,7 @@ export function activate(context: ExtensionContext): void {
2930

3031
disposables.push(initializeGitBaseExtension());
3132
disposables.push(initializeGitExtension(context, logger));
33+
disposables.push(new GitHubCanonicalUriIdentityProvider());
3234
}
3335

3436
function initializeGitBaseExtension(): Disposable {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
declare module 'vscode' {
7+
8+
// https://github.com/microsoft/vscode/issues/180582
9+
10+
export namespace workspace {
11+
/**
12+
*
13+
* @param scheme The URI scheme that this provider can provide canonical URI identities for.
14+
* A canonical URI represents the conversion of a resource's alias into a source of truth URI.
15+
* Multiple aliases may convert to the same source of truth URI.
16+
* @param provider A provider which can convert URIs for workspace folders of scheme @param scheme to
17+
* a canonical URI identifier which is stable across machines.
18+
*/
19+
export function registerCanonicalUriIdentityProvider(scheme: string, provider: CanonicalUriIdentityProvider): Disposable;
20+
21+
/**
22+
*
23+
* @param uri The URI to provide a canonical URI identity for.
24+
* @param token A cancellation token for the request.
25+
*/
26+
export function provideCanonicalUriIdentity(uri: Uri, token: CancellationToken): ProviderResult<Uri>;
27+
}
28+
29+
export interface CanonicalUriIdentityProvider {
30+
/**
31+
*
32+
* @param uri The URI to provide a canonical URI identity for.
33+
* @param token A cancellation token for the request.
34+
* @returns The canonical URI identity for the requested URI.
35+
*/
36+
provideCanonicalUriIdentity(uri: Uri, token: CancellationToken): ProviderResult<Uri>;
37+
}
38+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 { CancellationToken } from 'vs/base/common/cancellation';
7+
import { IDisposable } from 'vs/base/common/lifecycle';
8+
import { URI, UriComponents } from 'vs/base/common/uri';
9+
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
10+
11+
export interface ICanonicalUriIdentityProvider {
12+
readonly scheme: string;
13+
provideCanonicalUriIdentity(uri: UriComponents, token: CancellationToken): Promise<URI | undefined>;
14+
}
15+
16+
export const ICanonicalUriIdentityService = createDecorator<ICanonicalUriIdentityService>('canonicalUriIdentityService');
17+
18+
export interface ICanonicalUriIdentityService {
19+
readonly _serviceBrand: undefined;
20+
registerCanonicalUriIdentityProvider(provider: ICanonicalUriIdentityProvider): IDisposable;
21+
}

src/vs/workbench/api/browser/mainThreadWorkspace.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { ExtHostContext, ExtHostWorkspaceShape, ITextSearchComplete, IWorkspaceD
2828
import { IEditSessionIdentityService } from 'vs/platform/workspace/common/editSessions';
2929
import { EditorResourceAccessor, SaveReason, SideBySideEditor } from 'vs/workbench/common/editor';
3030
import { coalesce, firstOrDefault } from 'vs/base/common/arrays';
31+
import { ICanonicalUriIdentityService } from 'vs/platform/workspace/common/canonicalUriIdentity';
3132

3233
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
3334
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
@@ -42,6 +43,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
4243
@ISearchService private readonly _searchService: ISearchService,
4344
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
4445
@IEditSessionIdentityService private readonly _editSessionIdentityService: IEditSessionIdentityService,
46+
@ICanonicalUriIdentityService private readonly _canonicalUriIdentityService: ICanonicalUriIdentityService,
4547
@IEditorService private readonly _editorService: IEditorService,
4648
@IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService,
4749
@INotificationService private readonly _notificationService: INotificationService,
@@ -269,4 +271,29 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
269271
disposable?.dispose();
270272
this.registeredEditSessionProviders.delete(handle);
271273
}
274+
275+
// --- canonical uri identities ---
276+
private registeredCanonicalUriIdentityProviders = new Map<number, IDisposable>();
277+
278+
$registerCanonicalUriIdentityProvider(handle: number, scheme: string) {
279+
const disposable = this._canonicalUriIdentityService.registerCanonicalUriIdentityProvider({
280+
scheme: scheme,
281+
provideCanonicalUriIdentity: async (uri: UriComponents, token: CancellationToken) => {
282+
const result = await this._proxy.$provideCanonicalUriIdentity(uri, token);
283+
if (result) {
284+
return URI.revive(result);
285+
}
286+
return result;
287+
}
288+
});
289+
290+
this.registeredCanonicalUriIdentityProviders.set(handle, disposable);
291+
this._toDispose.add(disposable);
292+
}
293+
294+
$unregisterCanonicalUriIdentityProvider(handle: number) {
295+
const disposable = this.registeredCanonicalUriIdentityProviders.get(handle);
296+
disposable?.dispose();
297+
this.registeredCanonicalUriIdentityProviders.delete(handle);
298+
}
272299
}

src/vs/workbench/api/common/extHost.api.impl.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
10971097
checkProposedApiEnabled(extension, 'editSessionIdentityProvider');
10981098
return extHostWorkspace.getOnWillCreateEditSessionIdentityEvent(extension)(listener, thisArgs, disposables);
10991099
},
1100+
registerCanonicalUriIdentityProvider: (scheme: string, provider: vscode.CanonicalUriIdentityProvider) => {
1101+
checkProposedApiEnabled(extension, 'canonicalUriIdentityProvider');
1102+
return extHostWorkspace.registerCanonicalUriIdentityProvider(scheme, provider);
1103+
},
1104+
provideCanonicalUriIdentity: (uri: vscode.Uri, token: vscode.CancellationToken) => {
1105+
checkProposedApiEnabled(extension, 'canonicalUriIdentityProvider');
1106+
return extHostWorkspace.provideCanonicalUriIdentity(uri, token);
1107+
}
11001108
};
11011109

11021110
// namespace: scm

0 commit comments

Comments
 (0)