Skip to content

Commit df15357

Browse files
committed
Generates Bitbucket Data Center PR URL and retrieves repoId for cross-forks
(#4142)
1 parent c3a9496 commit df15357

File tree

5 files changed

+67
-21
lines changed

5 files changed

+67
-21
lines changed

src/git/remotes/bitbucket-server.ts

+45-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { Range, Uri } from 'vscode';
22
import type { AutolinkReference, DynamicAutolinkReference } from '../../autolinks/models/autolinks';
3+
import type { Container } from '../../container';
4+
import { HostingIntegration } from '../../plus/integrations/integration';
5+
import { remoteProviderIdToIntegrationId } from '../../plus/integrations/integrationService';
36
import type { Brand, Unbrand } from '../../system/brand';
47
import type { Repository } from '../models/repository';
58
import type { GkProviderId } from '../models/repositoryIdentities';
@@ -11,7 +14,14 @@ const fileRegex = /^\/([^/]+)\/([^/]+?)\/src(.+)$/i;
1114
const rangeRegex = /^lines-(\d+)(?::(\d+))?$/;
1215

1316
export class BitbucketServerRemote extends RemoteProvider {
14-
constructor(domain: string, path: string, protocol?: string, name?: string, custom: boolean = false) {
17+
constructor(
18+
private readonly container: Container,
19+
domain: string,
20+
path: string,
21+
protocol?: string,
22+
name?: string,
23+
custom: boolean = false,
24+
) {
1525
super(domain, path, protocol, name, custom);
1626
}
1727

@@ -54,14 +64,12 @@ export class BitbucketServerRemote extends RemoteProvider {
5464
return `${this.protocol}://${this.domain}/projects/${project}/repos/${repo}`;
5565
}
5666

57-
protected override splitPath(): [string, string] {
58-
if (this.path.startsWith('scm/') && this.path.indexOf('/') !== this.path.lastIndexOf('/')) {
59-
const path = this.path.replace('scm/', '');
60-
const index = path.indexOf('/');
61-
return [path.substring(0, index), path.substring(index + 1)];
67+
protected override splitArgPath(argPath: string): [string, string] {
68+
if (argPath.startsWith('scm/') && argPath.indexOf('/') !== argPath.lastIndexOf('/')) {
69+
return super.splitArgPath(argPath.replace('scm/', ''));
6270
}
6371

64-
return super.splitPath();
72+
return super.splitArgPath(argPath);
6573
}
6674

6775
override get icon(): string {
@@ -158,25 +166,48 @@ export class BitbucketServerRemote extends RemoteProvider {
158166
}
159167

160168
protected override getUrlForComparison(base: string, head: string, _notation: '..' | '...'): string {
161-
return this.encodeUrl(`${this.baseUrl}/branches/compare/${base}%0D${head}`).replaceAll('%250D', '%0D');
169+
return this.encodeUrl(`${this.baseUrl}/branches/compare/${head}\r${base}`);
162170
}
163171

164-
protected override getUrlForCreatePullRequest(
172+
override async isReadyForForCrossForkPullRequestUrls(): Promise<boolean> {
173+
const integrationId = remoteProviderIdToIntegrationId(this.id);
174+
const integration = integrationId && (await this.container.integrations.get(integrationId));
175+
return integration?.maybeConnected ?? integration?.isConnected() ?? false;
176+
}
177+
178+
protected override async getUrlForCreatePullRequest(
165179
base: { branch?: string; remote: { path: string; url: string } },
166180
head: { branch: string; remote: { path: string; url: string } },
167181
options?: { title?: string; description?: string },
168-
): string | undefined {
182+
): Promise<string | undefined> {
169183
const query = new URLSearchParams({ sourceBranch: head.branch, targetBranch: base.branch ?? '' });
170-
// TODO: figure this out
171-
// query.set('targetRepoId', base.repoId);
184+
const [baseOwner, baseName] = this.splitArgPath(base.remote.path);
185+
if (base.remote.url !== head.remote.url) {
186+
const targetDesc = {
187+
owner: baseOwner,
188+
name: baseName,
189+
};
190+
const integrationId = remoteProviderIdToIntegrationId(this.id);
191+
const integration = integrationId && (await this.container.integrations.get(integrationId));
192+
let targetRepoId = undefined;
193+
if (integration?.isConnected && integration instanceof HostingIntegration) {
194+
targetRepoId = (await integration.getRepoInfo?.(targetDesc))?.id;
195+
}
196+
if (!targetRepoId) {
197+
return undefined;
198+
}
199+
query.set('targetRepoId', targetRepoId);
200+
}
172201
if (options?.title) {
173202
query.set('title', options.title);
174203
}
175204
if (options?.description) {
176205
query.set('description', options.description);
177206
}
178-
179-
return `${this.encodeUrl(`${this.baseUrl}/pull-requests?create`)}&${query.toString()}`;
207+
const [headOwner, headName] = this.splitArgPath(head.remote.path);
208+
return `${this.encodeUrl(
209+
`${this.protocol}://${this.domain}/projects/${headOwner}/repos/${headName}/pull-requests?create`,
210+
)}&${query.toString()}`;
180211
}
181212

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

src/git/remotes/remoteProvider.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,12 @@ export abstract class RemoteProvider<T extends ResourceDescriptor = ResourceDesc
174174
}
175175

176176
protected splitPath(): [string, string] {
177-
const index = this.path.indexOf('/');
178-
return [this.path.substring(0, index), this.path.substring(index + 1)];
177+
return this.splitArgPath(this.path);
178+
}
179+
180+
protected splitArgPath(argPath: string): [string, string] {
181+
const index = argPath.indexOf('/');
182+
return [argPath.substring(0, index), argPath.substring(index + 1)];
179183
}
180184

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

src/git/remotes/remoteProviders.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ const builtInProviders: RemoteProviders = [
4747
{
4848
custom: true,
4949
matcher: /^(.+\/(?:bitbucket|stash))\/scm\/(.+)$/i,
50-
creator: (_container: Container, domain: string, path: string) => new BitbucketServerRemote(domain, path),
50+
creator: (container: Container, domain: string, path: string) =>
51+
new BitbucketServerRemote(container, domain, path),
5152
},
5253
{
5354
custom: false,
@@ -85,8 +86,8 @@ const cloudProviderCreatorsMap: Record<
8586
new GitHubRemote(domain, path),
8687
[SelfHostedIntegrationId.CloudGitLabSelfHosted]: (container: Container, domain: string, path: string) =>
8788
new GitLabRemote(container, domain, path),
88-
[SelfHostedIntegrationId.BitbucketServer]: (_container: Container, domain: string, path: string) =>
89-
new BitbucketServerRemote(domain, path),
89+
[SelfHostedIntegrationId.BitbucketServer]: (container: Container, domain: string, path: string) =>
90+
new BitbucketServerRemote(container, domain, path),
9091
};
9192

9293
export function loadRemoteProviders(
@@ -152,8 +153,8 @@ function getCustomProviderCreator(cfg: RemotesConfig) {
152153
return (_container: Container, domain: string, path: string) =>
153154
new BitbucketRemote(domain, path, cfg.protocol, cfg.name, true);
154155
case 'BitbucketServer':
155-
return (_container: Container, domain: string, path: string) =>
156-
new BitbucketServerRemote(domain, path, cfg.protocol, cfg.name, true);
156+
return (container: Container, domain: string, path: string) =>
157+
new BitbucketServerRemote(container, domain, path, cfg.protocol, cfg.name, true);
157158
case 'Custom':
158159
return (_container: Container, domain: string, path: string) =>
159160
new CustomRemote(domain, path, cfg.urls!, cfg.protocol, cfg.name);

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

+9
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

+1
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)