Skip to content

Commit bf71603

Browse files
committed
Generates Bitbucket Data Center PR URL and retrieves repoId for cross-forks
(#4142, #4184)
1 parent 8b4a7b4 commit bf71603

File tree

7 files changed

+54
-22
lines changed

7 files changed

+54
-22
lines changed

src/git/remotes/bitbucket-server.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { Range, Uri } from 'vscode';
22
import type { AutolinkReference, DynamicAutolinkReference } from '../../autolinks/models/autolinks';
33
import type { Source } from '../../constants.telemetry';
44
import type { Container } from '../../container';
5+
import { HostingIntegration } from '../../plus/integrations/integration';
6+
import { remoteProviderIdToIntegrationId } from '../../plus/integrations/integrationService';
57
import type { Brand, Unbrand } from '../../system/brand';
68
import type { CreatePullRequestRemoteResource } from '../models/remoteResource';
79
import type { Repository } from '../models/repository';
@@ -62,18 +64,16 @@ export class BitbucketServerRemote extends RemoteProvider {
6264
}
6365

6466
protected override get baseUrl(): string {
65-
const [project, repo] = this.splitPath();
67+
const [project, repo] = this.splitPath(this.path);
6668
return `${this.protocol}://${this.domain}/projects/${project}/repos/${repo}`;
6769
}
6870

69-
protected override splitPath(): [string, string] {
70-
if (this.path.startsWith('scm/') && this.path.indexOf('/') !== this.path.lastIndexOf('/')) {
71-
const path = this.path.replace('scm/', '');
72-
const index = path.indexOf('/');
73-
return [path.substring(0, index), path.substring(index + 1)];
71+
protected override splitPath(path: string): [string, string] {
72+
if (path.startsWith('scm/') && path.indexOf('/') !== path.lastIndexOf('/')) {
73+
return super.splitPath(path.replace('scm/', ''));
7474
}
7575

76-
return super.splitPath();
76+
return super.splitPath(path);
7777
}
7878

7979
override get icon(): string {
@@ -191,7 +191,13 @@ export class BitbucketServerRemote extends RemoteProvider {
191191
}
192192

193193
protected override getUrlForComparison(base: string, head: string, _notation: GitRevisionRangeNotation): string {
194-
return this.encodeUrl(`${this.baseUrl}/branches/compare/${base}%0D${head}`).replaceAll('%250D', '%0D');
194+
return this.encodeUrl(`${this.baseUrl}/branches/compare/${head}\r${base}`);
195+
}
196+
197+
override async isReadyForForCrossForkPullRequestUrls(): Promise<boolean> {
198+
const integrationId = remoteProviderIdToIntegrationId(this.id);
199+
const integration = integrationId && (await this.container.integrations.get(integrationId));
200+
return integration?.maybeConnected ?? integration?.isConnected() ?? false;
195201
}
196202

197203
protected override async getUrlForCreatePullRequest(
@@ -210,17 +216,33 @@ export class BitbucketServerRemote extends RemoteProvider {
210216
}
211217

212218
const query = new URLSearchParams({ sourceBranch: head.branch, targetBranch: base.branch ?? '' });
213-
// TODO: figure this out
214-
// query.set('targetRepoId', base.repoId);
215-
219+
const [baseOwner, baseName] = this.splitPath(base.remote.path);
220+
if (base.remote.url !== head.remote.url) {
221+
const targetDesc = {
222+
owner: baseOwner,
223+
name: baseName,
224+
};
225+
const integrationId = remoteProviderIdToIntegrationId(this.id);
226+
const integration = integrationId && (await this.container.integrations.get(integrationId));
227+
let targetRepoId = undefined;
228+
if (integration?.isConnected && integration instanceof HostingIntegration) {
229+
targetRepoId = (await integration.getRepoInfo?.(targetDesc))?.id;
230+
}
231+
if (!targetRepoId) {
232+
return undefined;
233+
}
234+
query.set('targetRepoId', targetRepoId);
235+
}
216236
if (details?.title) {
217237
query.set('title', details.title);
218238
}
219239
if (details?.description) {
220240
query.set('description', details.description);
221241
}
222-
223-
return `${this.encodeUrl(`${this.baseUrl}/pull-requests?create`)}&${query.toString()}`;
242+
const [headOwner, headName] = this.splitPath(head.remote.path);
243+
return `${this.encodeUrl(
244+
`${this.protocol}://${this.domain}/projects/${headOwner}/repos/${headName}/pull-requests?create`,
245+
)}&${query.toString()}`;
224246
}
225247

226248
protected getUrlForFile(fileName: string, branch?: string, sha?: string, range?: Range): string {

src/git/remotes/custom.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class CustomRemote extends RemoteProvider {
128128
}
129129

130130
private getContext(additionalContext?: Record<string, string>) {
131-
const [repoBase, repoPath] = this.splitPath();
131+
const [repoBase, repoPath] = this.splitPath(this.path);
132132
const context: Record<string, string> = {
133133
repo: this.path,
134134
repoBase: repoBase,

src/git/remotes/github.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export class GitHubRemote extends RemoteProvider<GitHubRepositoryDescriptor> {
183183
}
184184

185185
override get avatarUri(): Uri {
186-
const [owner] = this.splitPath();
186+
const [owner] = this.splitPath(this.path);
187187
return Uri.parse(`https://avatars.githubusercontent.com/${owner}`);
188188
}
189189

@@ -207,7 +207,7 @@ export class GitHubRemote extends RemoteProvider<GitHubRepositoryDescriptor> {
207207

208208
@memoize()
209209
override get repoDesc(): GitHubRepositoryDescriptor {
210-
const [owner, repo] = this.splitPath();
210+
const [owner, repo] = this.splitPath(this.path);
211211
return { key: this.remoteKey, owner: owner, name: repo };
212212
}
213213

src/git/remotes/gitlab.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ export class GitLabRemote extends RemoteProvider<GitLabRepositoryDescriptor> {
305305

306306
@memoize()
307307
override get repoDesc(): GitLabRepositoryDescriptor {
308-
const [owner, repo] = this.splitPath();
308+
const [owner, repo] = this.splitPath(this.path);
309309
return { key: this.remoteKey, owner: owner, name: repo };
310310
}
311311

src/git/remotes/remoteProvider.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export abstract class RemoteProvider<T extends ResourceDescriptor = ResourceDesc
8282
}
8383

8484
get owner(): string | undefined {
85-
return this.splitPath()[0];
85+
return this.splitPath(this.path)[0];
8686
}
8787

8888
@memoize()
@@ -108,7 +108,7 @@ export abstract class RemoteProvider<T extends ResourceDescriptor = ResourceDesc
108108
}
109109

110110
get repoName(): string | undefined {
111-
return this.splitPath()[1];
111+
return this.splitPath(this.path)[1];
112112
}
113113

114114
abstract get id(): RemoteProviderId;
@@ -186,9 +186,9 @@ export abstract class RemoteProvider<T extends ResourceDescriptor = ResourceDesc
186186
return `${name}${this.custom ? ` (${this.domain})` : ''}`;
187187
}
188188

189-
protected splitPath(): [string, string] {
190-
const index = this.path.indexOf('/');
191-
return [this.path.substring(0, index), this.path.substring(index + 1)];
189+
protected splitPath(path: string): [string, string] {
190+
const index = path.indexOf('/');
191+
return [path.substring(0, index), path.substring(index + 1)];
192192
}
193193

194194
protected abstract getUrlForBranch(branch: string): string;

src/plus/integrations/providers/bitbucket-server.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { IntegrationAuthenticationService } from '../authentication/integra
1313
import type { ProviderAuthenticationSession } from '../authentication/models';
1414
import { HostingIntegration } from '../integration';
1515
import type { BitbucketRepositoryDescriptor } from './bitbucket/models';
16+
import type { ProviderRepository } from './models';
1617
import { fromProviderPullRequest, providersMetadata } from './models';
1718
import type { ProvidersApi } from './providersApi';
1819

@@ -155,6 +156,14 @@ export class BitbucketServerIntegration extends HostingIntegration<
155156
return Promise.resolve(undefined);
156157
}
157158

159+
public override async getRepoInfo(repo: { owner: string; name: string }): Promise<ProviderRepository | undefined> {
160+
const api = await this.getProvidersApi();
161+
return api.getRepo(this.id, repo.owner, repo.name, undefined, {
162+
accessToken: this._session?.accessToken,
163+
baseUrl: this.apiBaseUrl,
164+
});
165+
}
166+
158167
protected override async getProviderRepositoryMetadata(
159168
_session: AuthenticationSession,
160169
_repo: BitbucketRepositoryDescriptor,

src/plus/integrations/providers/providersApi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ export class ProvidersApi {
221221
[SelfHostedIntegrationId.BitbucketServer]: {
222222
...providersMetadata[SelfHostedIntegrationId.BitbucketServer],
223223
provider: providerApis.bitbucketServer,
224+
getRepoFn: providerApis.bitbucketServer.getRepo.bind(providerApis.bitbucketServer),
224225
getCurrentUserFn: providerApis.bitbucketServer.getCurrentUser.bind(
225226
providerApis.bitbucketServer,
226227
) as GetCurrentUserFn,

0 commit comments

Comments
 (0)