|
|
@ -21,6 +21,7 @@ import { |
|
|
|
deepLinkStateToProgress, |
|
|
|
deepLinkStateTransitionTable, |
|
|
|
DeepLinkType, |
|
|
|
deepLinkTypeToString, |
|
|
|
parseDeepLinkUri, |
|
|
|
} from './deepLink'; |
|
|
|
|
|
|
@ -46,7 +47,7 @@ export class DeepLinkService implements Disposable { |
|
|
|
await this.container.git.isDiscoveringRepositories; |
|
|
|
} |
|
|
|
|
|
|
|
if (!link.type || (!link.repoId && !link.remoteUrl && !link.repoPath)) { |
|
|
|
if (!link.type || (!link.mainId && !link.remoteUrl && !link.repoPath)) { |
|
|
|
void window.showErrorMessage('Unable to resolve link'); |
|
|
|
Logger.warn(`Unable to resolve link - missing basic properties: ${uri.toString()}`); |
|
|
|
return; |
|
|
@ -58,9 +59,9 @@ export class DeepLinkService implements Disposable { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (link.type !== DeepLinkType.Repository && link.targetId == null) { |
|
|
|
if (link.type !== DeepLinkType.Repository && link.targetId == null && link.mainId == null) { |
|
|
|
void window.showErrorMessage('Unable to resolve link'); |
|
|
|
Logger.warn(`Unable to resolve link - no target id provided: ${uri.toString()}`); |
|
|
|
Logger.warn(`Unable to resolve link - no main/target id provided: ${uri.toString()}`); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
@ -92,7 +93,7 @@ export class DeepLinkService implements Disposable { |
|
|
|
this._context = { |
|
|
|
state: DeepLinkServiceState.Idle, |
|
|
|
url: undefined, |
|
|
|
repoId: undefined, |
|
|
|
mainId: undefined, |
|
|
|
repo: undefined, |
|
|
|
remoteUrl: undefined, |
|
|
|
remote: undefined, |
|
|
@ -109,7 +110,7 @@ export class DeepLinkService implements Disposable { |
|
|
|
private setContextFromDeepLink(link: DeepLink, url: string) { |
|
|
|
this._context = { |
|
|
|
...this._context, |
|
|
|
repoId: link.repoId, |
|
|
|
mainId: link.mainId, |
|
|
|
targetType: link.type, |
|
|
|
url: url, |
|
|
|
remoteUrl: link.remoteUrl, |
|
|
@ -359,9 +360,13 @@ export class DeepLinkService implements Disposable { |
|
|
|
): Promise<void> { |
|
|
|
let message = ''; |
|
|
|
let action = initialAction; |
|
|
|
if (action === DeepLinkServiceAction.DeepLinkCancelled && this._context.state === DeepLinkServiceState.Idle) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
//Repo match
|
|
|
|
let matchingLocalRepoPaths: string[] = []; |
|
|
|
const { targetType } = this._context; |
|
|
|
|
|
|
|
queueMicrotask( |
|
|
|
() => |
|
|
@ -369,7 +374,7 @@ export class DeepLinkService implements Disposable { |
|
|
|
{ |
|
|
|
cancellable: true, |
|
|
|
location: ProgressLocation.Notification, |
|
|
|
title: `Opening repository for link: ${this._context.url}}`, |
|
|
|
title: `Opening ${deepLinkTypeToString(targetType ?? DeepLinkType.Repository)} link...`, |
|
|
|
}, |
|
|
|
(progress, token) => { |
|
|
|
progress.report({ increment: 0 }); |
|
|
@ -379,14 +384,12 @@ export class DeepLinkService implements Disposable { |
|
|
|
resolve(); |
|
|
|
}); |
|
|
|
|
|
|
|
this._disposables.push( |
|
|
|
this._onDeepLinkProgressUpdated.event(({ message, increment }) => { |
|
|
|
progress.report({ message: message, increment: increment }); |
|
|
|
if (increment === 100) { |
|
|
|
resolve(); |
|
|
|
} |
|
|
|
}), |
|
|
|
); |
|
|
|
this._onDeepLinkProgressUpdated.event(({ message, increment }) => { |
|
|
|
progress.report({ message: message, increment: increment }); |
|
|
|
if (increment === 100) { |
|
|
|
resolve(); |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
}, |
|
|
|
), |
|
|
@ -396,7 +399,7 @@ export class DeepLinkService implements Disposable { |
|
|
|
this._context.state = deepLinkStateTransitionTable[this._context.state][action]; |
|
|
|
const { |
|
|
|
state, |
|
|
|
repoId, |
|
|
|
mainId, |
|
|
|
repo, |
|
|
|
url, |
|
|
|
remoteUrl, |
|
|
@ -422,9 +425,18 @@ export class DeepLinkService implements Disposable { |
|
|
|
this.resetContext(); |
|
|
|
return; |
|
|
|
} |
|
|
|
case DeepLinkServiceState.TypeMatch: { |
|
|
|
if (targetType === DeepLinkType.Workspace) { |
|
|
|
action = DeepLinkServiceAction.LinkIsWorkspaceType; |
|
|
|
} else { |
|
|
|
action = DeepLinkServiceAction.LinkIsRepoType; |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
case DeepLinkServiceState.RepoMatch: |
|
|
|
case DeepLinkServiceState.AddedRepoMatch: { |
|
|
|
if (!repoId && !remoteUrl && !repoPath) { |
|
|
|
if (!mainId && !remoteUrl && !repoPath) { |
|
|
|
action = DeepLinkServiceAction.DeepLinkErrored; |
|
|
|
message = 'No repository id, remote url or path was provided.'; |
|
|
|
break; |
|
|
@ -459,10 +471,10 @@ export class DeepLinkService implements Disposable { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (repoId != null && repoId !== '-') { |
|
|
|
if (mainId != null && mainId !== '-') { |
|
|
|
// Repo ID can be any valid SHA in the repo, though standard practice is to use the
|
|
|
|
// first commit SHA.
|
|
|
|
if (await this.container.git.validateReference(repo.path, repoId)) { |
|
|
|
if (await this.container.git.validateReference(repo.path, mainId)) { |
|
|
|
this._context.repo = repo; |
|
|
|
action = DeepLinkServiceAction.RepoMatched; |
|
|
|
break; |
|
|
@ -506,7 +518,7 @@ export class DeepLinkService implements Disposable { |
|
|
|
break; |
|
|
|
} |
|
|
|
case DeepLinkServiceState.CloneOrAddRepo: { |
|
|
|
if (!repoId && !remoteUrl && !repoPath) { |
|
|
|
if (!mainId && !remoteUrl && !repoPath) { |
|
|
|
action = DeepLinkServiceAction.DeepLinkErrored; |
|
|
|
message = 'Missing repository id, remote url and path.'; |
|
|
|
break; |
|
|
@ -872,6 +884,22 @@ export class DeepLinkService implements Disposable { |
|
|
|
action = DeepLinkServiceAction.DeepLinkResolved; |
|
|
|
break; |
|
|
|
} |
|
|
|
case DeepLinkServiceState.OpenWorkspace: { |
|
|
|
if (!mainId) { |
|
|
|
action = DeepLinkServiceAction.DeepLinkErrored; |
|
|
|
message = 'Missing workspace id.'; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
await this.container.workspacesView.revealWorkspaceNode(mainId, { |
|
|
|
select: true, |
|
|
|
focus: true, |
|
|
|
expand: true, |
|
|
|
}); |
|
|
|
|
|
|
|
action = DeepLinkServiceAction.DeepLinkResolved; |
|
|
|
break; |
|
|
|
} |
|
|
|
default: { |
|
|
|
action = DeepLinkServiceAction.DeepLinkErrored; |
|
|
|
message = 'Unknown state.'; |
|
|
@ -881,6 +909,7 @@ export class DeepLinkService implements Disposable { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async copyDeepLinkUrl(workspaceId: string): Promise<void>; |
|
|
|
async copyDeepLinkUrl(ref: GitReference, remoteUrl: string): Promise<void>; |
|
|
|
async copyDeepLinkUrl( |
|
|
|
repoPath: string, |
|
|
@ -889,17 +918,20 @@ export class DeepLinkService implements Disposable { |
|
|
|
compareWithRef?: StoredNamedRef, |
|
|
|
): Promise<void>; |
|
|
|
async copyDeepLinkUrl( |
|
|
|
refOrRepoPath: string | GitReference, |
|
|
|
remoteUrl: string, |
|
|
|
refOrIdOrRepoPath: string | GitReference, |
|
|
|
remoteUrl?: string, |
|
|
|
compareRef?: StoredNamedRef, |
|
|
|
compareWithRef?: StoredNamedRef, |
|
|
|
): Promise<void> { |
|
|
|
const url = await (typeof refOrRepoPath === 'string' |
|
|
|
? this.generateDeepLinkUrl(refOrRepoPath, remoteUrl, compareRef, compareWithRef) |
|
|
|
: this.generateDeepLinkUrl(refOrRepoPath, remoteUrl)); |
|
|
|
const url = await (typeof refOrIdOrRepoPath === 'string' |
|
|
|
? remoteUrl != null |
|
|
|
? this.generateDeepLinkUrl(refOrIdOrRepoPath, remoteUrl, compareRef, compareWithRef) |
|
|
|
: this.generateDeepLinkUrl(refOrIdOrRepoPath) |
|
|
|
: this.generateDeepLinkUrl(refOrIdOrRepoPath, remoteUrl!)); |
|
|
|
await env.clipboard.writeText(url.toString()); |
|
|
|
} |
|
|
|
|
|
|
|
async generateDeepLinkUrl(workspaceId: string): Promise<URL>; |
|
|
|
async generateDeepLinkUrl(ref: GitReference, remoteUrl: string): Promise<URL>; |
|
|
|
async generateDeepLinkUrl( |
|
|
|
repoPath: string, |
|
|
@ -908,37 +940,50 @@ export class DeepLinkService implements Disposable { |
|
|
|
compareWithRef?: StoredNamedRef, |
|
|
|
): Promise<URL>; |
|
|
|
async generateDeepLinkUrl( |
|
|
|
refOrRepoPath: string | GitReference, |
|
|
|
remoteUrl: string, |
|
|
|
refOrIdOrRepoPath: string | GitReference, |
|
|
|
remoteUrl?: string, |
|
|
|
compareRef?: StoredNamedRef, |
|
|
|
compareWithRef?: StoredNamedRef, |
|
|
|
): Promise<URL> { |
|
|
|
const repoPath = typeof refOrRepoPath !== 'string' ? refOrRepoPath.repoPath : refOrRepoPath; |
|
|
|
let repoId; |
|
|
|
let repoId: string | undefined; |
|
|
|
let targetType: DeepLinkType | undefined; |
|
|
|
let targetId: string | undefined; |
|
|
|
let compareWithTargetId: string | undefined; |
|
|
|
const schemeOverride = configuration.get('deepLinks.schemeOverride'); |
|
|
|
const scheme = !schemeOverride ? 'vscode' : schemeOverride === true ? env.uriScheme : schemeOverride; |
|
|
|
let modePrefixString = ''; |
|
|
|
if (this.container.env === 'dev') { |
|
|
|
modePrefixString = 'dev.'; |
|
|
|
} else if (this.container.env === 'staging') { |
|
|
|
modePrefixString = 'staging.'; |
|
|
|
} |
|
|
|
|
|
|
|
if (remoteUrl == null && typeof refOrIdOrRepoPath === 'string') { |
|
|
|
return new URL(`https://${modePrefixString}gitkraken.dev/link/workspaces/${refOrIdOrRepoPath}`); |
|
|
|
} |
|
|
|
|
|
|
|
const repoPath = typeof refOrIdOrRepoPath !== 'string' ? refOrIdOrRepoPath.repoPath : refOrIdOrRepoPath; |
|
|
|
try { |
|
|
|
repoId = await this.container.git.getUniqueRepositoryId(repoPath); |
|
|
|
} catch { |
|
|
|
repoId = '-'; |
|
|
|
} |
|
|
|
|
|
|
|
let targetType: DeepLinkType | undefined; |
|
|
|
let targetId: string | undefined; |
|
|
|
let compareWithTargetId: string | undefined; |
|
|
|
if (typeof refOrRepoPath !== 'string') { |
|
|
|
switch (refOrRepoPath.refType) { |
|
|
|
if (typeof refOrIdOrRepoPath !== 'string') { |
|
|
|
switch (refOrIdOrRepoPath.refType) { |
|
|
|
case 'branch': |
|
|
|
targetType = DeepLinkType.Branch; |
|
|
|
targetId = refOrRepoPath.remote |
|
|
|
? getBranchNameWithoutRemote(refOrRepoPath.name) |
|
|
|
: refOrRepoPath.name; |
|
|
|
targetId = refOrIdOrRepoPath.remote |
|
|
|
? getBranchNameWithoutRemote(refOrIdOrRepoPath.name) |
|
|
|
: refOrIdOrRepoPath.name; |
|
|
|
break; |
|
|
|
case 'revision': |
|
|
|
targetType = DeepLinkType.Commit; |
|
|
|
targetId = refOrRepoPath.ref; |
|
|
|
targetId = refOrIdOrRepoPath.ref; |
|
|
|
break; |
|
|
|
case 'tag': |
|
|
|
targetType = DeepLinkType.Tag; |
|
|
|
targetId = refOrRepoPath.name; |
|
|
|
targetId = refOrIdOrRepoPath.name; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
@ -949,9 +994,6 @@ export class DeepLinkService implements Disposable { |
|
|
|
compareWithTargetId = compareWithRef.label ?? compareWithRef.ref; |
|
|
|
} |
|
|
|
|
|
|
|
const schemeOverride = configuration.get('deepLinks.schemeOverride'); |
|
|
|
|
|
|
|
const scheme = !schemeOverride ? 'vscode' : schemeOverride === true ? env.uriScheme : schemeOverride; |
|
|
|
let target; |
|
|
|
if (targetType === DeepLinkType.Comparison) { |
|
|
|
target = `/${targetType}/${compareWithTargetId}...${targetId}`; |
|
|
@ -968,16 +1010,9 @@ export class DeepLinkService implements Disposable { |
|
|
|
}/${repoId}${target}`,
|
|
|
|
); |
|
|
|
|
|
|
|
// Add the remote URL as a query parameter
|
|
|
|
deepLink.searchParams.set('url', remoteUrl); |
|
|
|
const params = new URLSearchParams(); |
|
|
|
params.set('url', remoteUrl); |
|
|
|
|
|
|
|
let modePrefixString = ''; |
|
|
|
if (this.container.env === 'dev') { |
|
|
|
modePrefixString = 'dev.'; |
|
|
|
} else if (this.container.env === 'staging') { |
|
|
|
modePrefixString = 'staging.'; |
|
|
|
if (remoteUrl != null) { |
|
|
|
// Add the remote URL as a query parameter
|
|
|
|
deepLink.searchParams.set('url', remoteUrl); |
|
|
|
} |
|
|
|
|
|
|
|
const deepLinkRedirectUrl = new URL( |
|
|
|