Skip to content

Commit b1dc15f

Browse files
authored
Merge branch 'main' into 2959/autosquash
2 parents ab4767d + 1d41f04 commit b1dc15f

22 files changed

+950
-421
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
1212
- Adds AI model status and model switcher to the _Home_ view ([#4064](https://github.com/gitkraken/vscode-gitlens/issues/4064))
1313
- Adds Anthropic Claude 3.7 Sonnet model for GitLens' AI features ([#4101](https://github.com/gitkraken/vscode-gitlens/issues/4101))
1414
- Adds Google Gemini 2.0 Flash-Lite model for GitLens' AI features ([#4104](https://github.com/gitkraken/vscode-gitlens/issues/4104))
15+
- Adds integration with Bitbucket Cloud by showing enriched links to PRs and issues [#4045](https://github.com/gitkraken/vscode-gitlens/issues/4045)
1516

1617
### Changed
1718

package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -20253,7 +20253,7 @@
2025320253
"@types/slug": "5.0.9",
2025420254
"@types/sortablejs": "1.15.8",
2025520255
"@types/vscode": "1.92.0",
20256-
"@typescript-eslint/parser": "8.24.1",
20256+
"@typescript-eslint/parser": "8.25.0",
2025720257
"@vscode/test-cli": "^0.0.10",
2025820258
"@vscode/test-electron": "2.4.1",
2025920259
"@vscode/test-web": "0.0.66",
@@ -20290,15 +20290,15 @@
2029020290
"playwright": "1.50.1",
2029120291
"prettier": "3.1.0",
2029220292
"regex-to-strings": "2.1.0",
20293-
"sass": "1.85.0",
20293+
"sass": "1.85.1",
2029420294
"sass-loader": "16.0.5",
2029520295
"schema-utils": "4.3.0",
2029620296
"sharp": "0.33.5",
2029720297
"svgo": "3.3.2",
2029820298
"terser-webpack-plugin": "5.3.11",
2029920299
"ts-loader": "9.5.2",
2030020300
"typescript": "5.8.1-rc",
20301-
"typescript-eslint": "8.24.1",
20301+
"typescript-eslint": "8.25.0",
2030220302
"webpack": "5.98.0",
2030320303
"webpack-bundle-analyzer": "4.10.2",
2030420304
"webpack-cli": "6.0.1",
@@ -20311,5 +20311,5 @@
2031120311
"node-fetch": "2.7.0",
2031220312
"semver-regex": "4.0.5"
2031320313
},
20314-
"packageManager": "pnpm@10.4.1"
20314+
"packageManager": "pnpm@10.5.0"
2031520315
}

pnpm-lock.yaml

+86-86
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/autolinks/autolinksProvider.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,12 @@ export class AutolinksProvider implements Disposable {
248248
? integration.getIssueOrPullRequest(
249249
link.descriptor ?? remote.provider.repoDesc,
250250
this.getAutolinkEnrichableId(link),
251+
{ type: link.type },
251252
)
252253
: link.descriptor != null
253-
? linkIntegration?.getIssueOrPullRequest(link.descriptor, this.getAutolinkEnrichableId(link))
254+
? linkIntegration?.getIssueOrPullRequest(link.descriptor, this.getAutolinkEnrichableId(link), {
255+
type: link.type,
256+
})
254257
: undefined;
255258
enrichedAutolinks.set(id, [issueOrPullRequestPromise, link]);
256259
}

src/container.ts

+25
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { ConfiguredIntegrationService } from './plus/integrations/authentication
3636
import { IntegrationAuthenticationService } from './plus/integrations/authentication/integrationAuthenticationService';
3737
import { IntegrationService } from './plus/integrations/integrationService';
3838
import type { AzureDevOpsApi } from './plus/integrations/providers/azure/azure';
39+
import type { BitbucketApi } from './plus/integrations/providers/bitbucket/bitbucket';
3940
import type { GitHubApi } from './plus/integrations/providers/github/github';
4041
import type { GitLabApi } from './plus/integrations/providers/gitlab/gitlab';
4142
import { EnrichmentService } from './plus/launchpad/enrichmentService';
@@ -487,6 +488,30 @@ export class Container {
487488
return this._azure;
488489
}
489490

491+
private _bitbucket: Promise<BitbucketApi | undefined> | undefined;
492+
get bitbucket(): Promise<BitbucketApi | undefined> {
493+
if (this._bitbucket == null) {
494+
async function load(this: Container) {
495+
try {
496+
const bitbucket = new (
497+
await import(
498+
/* webpackChunkName: "integrations" */ './plus/integrations/providers/bitbucket/bitbucket'
499+
)
500+
).BitbucketApi(this);
501+
this._disposables.push(bitbucket);
502+
return bitbucket;
503+
} catch (ex) {
504+
Logger.error(ex);
505+
return undefined;
506+
}
507+
}
508+
509+
this._bitbucket = load.call(this);
510+
}
511+
512+
return this._bitbucket;
513+
}
514+
490515
private _github: Promise<GitHubApi | undefined> | undefined;
491516
get github(): Promise<GitHubApi | undefined> {
492517
if (this._github == null) {

src/git/models/issue.ts

-5
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,6 @@ export interface IssueRepository {
7676
id?: string;
7777
}
7878

79-
export interface SearchedIssue {
80-
issue: IssueShape;
81-
reasons: string[];
82-
}
83-
8479
export type IssueRepositoryIdentityDescriptor = RequireSomeWithProps<
8580
RequireSome<RepositoryIdentityDescriptor<string>, 'provider'>,
8681
'provider',

src/git/models/pullRequest.ts

-5
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,3 @@ export type PullRequestRepositoryIdentityDescriptor = RequireSomeWithProps<
185185
'id' | 'domain' | 'repoDomain' | 'repoName'
186186
> &
187187
RequireSomeWithProps<RequireSome<RepositoryIdentityDescriptor<string>, 'remote'>, 'remote', 'domain'>;
188-
189-
export interface SearchedPullRequest {
190-
pullRequest: PullRequest;
191-
reasons: string[];
192-
}

src/plus/integrations/integration.ts

+23-22
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,9 @@ import { AuthenticationError, CancellationError, RequestClientError } from '../.
1414
import type { PagedResult } from '../../git/gitProvider';
1515
import type { Account, UnidentifiedAuthor } from '../../git/models/author';
1616
import type { DefaultBranch } from '../../git/models/defaultBranch';
17-
import type { Issue, SearchedIssue } from '../../git/models/issue';
18-
import type { IssueOrPullRequest } from '../../git/models/issueOrPullRequest';
19-
import type {
20-
PullRequest,
21-
PullRequestMergeMethod,
22-
PullRequestState,
23-
SearchedPullRequest,
24-
} from '../../git/models/pullRequest';
17+
import type { Issue, IssueShape } from '../../git/models/issue';
18+
import type { IssueOrPullRequest, IssueOrPullRequestType } from '../../git/models/issueOrPullRequest';
19+
import type { PullRequest, PullRequestMergeMethod, PullRequestState } from '../../git/models/pullRequest';
2520
import type { RepositoryMetadata } from '../../git/models/repositoryMetadata';
2621
import type { PullRequestUrlIdentity } from '../../git/utils/pullRequest.utils';
2722
import { showIntegrationDisconnectedTooManyFailedRequestsWarningMessage } from '../../messages';
@@ -418,16 +413,16 @@ export abstract class IntegrationBase<
418413
async searchMyIssues(
419414
resource?: ResourceDescriptor,
420415
cancellation?: CancellationToken,
421-
): Promise<SearchedIssue[] | undefined>;
416+
): Promise<IssueShape[] | undefined>;
422417
async searchMyIssues(
423418
resources?: ResourceDescriptor[],
424419
cancellation?: CancellationToken,
425-
): Promise<SearchedIssue[] | undefined>;
420+
): Promise<IssueShape[] | undefined>;
426421
@debug()
427422
async searchMyIssues(
428423
resources?: ResourceDescriptor | ResourceDescriptor[],
429424
cancellation?: CancellationToken,
430-
): Promise<SearchedIssue[] | undefined> {
425+
): Promise<IssueShape[] | undefined> {
431426
const scope = getLogScope();
432427
const connected = this.maybeConnected ?? (await this.isConnected());
433428
if (!connected) return undefined;
@@ -441,21 +436,21 @@ export abstract class IntegrationBase<
441436
this.resetRequestExceptionCount();
442437
return issues;
443438
} catch (ex) {
444-
return this.handleProviderException<SearchedIssue[] | undefined>(ex, scope, undefined);
439+
return this.handleProviderException<IssueShape[] | undefined>(ex, scope, undefined);
445440
}
446441
}
447442

448443
protected abstract searchProviderMyIssues(
449444
session: ProviderAuthenticationSession,
450445
resources?: ResourceDescriptor[],
451446
cancellation?: CancellationToken,
452-
): Promise<SearchedIssue[] | undefined>;
447+
): Promise<IssueShape[] | undefined>;
453448

454449
@debug()
455450
async getIssueOrPullRequest(
456451
resource: T,
457452
id: string,
458-
options?: { expiryOverride?: boolean | number },
453+
options?: { expiryOverride?: boolean | number; type?: IssueOrPullRequestType },
459454
): Promise<IssueOrPullRequest | undefined> {
460455
const scope = getLogScope();
461456

@@ -469,7 +464,12 @@ export abstract class IntegrationBase<
469464
() => ({
470465
value: (async () => {
471466
try {
472-
const result = await this.getProviderIssueOrPullRequest(this._session!, resource, id);
467+
const result = await this.getProviderIssueOrPullRequest(
468+
this._session!,
469+
resource,
470+
id,
471+
options?.type,
472+
);
473473
this.resetRequestExceptionCount();
474474
return result;
475475
} catch (ex) {
@@ -486,6 +486,7 @@ export abstract class IntegrationBase<
486486
session: ProviderAuthenticationSession,
487487
resource: T,
488488
id: string,
489+
type: undefined | IssueOrPullRequestType,
489490
): Promise<IssueOrPullRequest | undefined>;
490491

491492
@debug()
@@ -660,7 +661,7 @@ export abstract class IssueIntegration<
660661
async getIssuesForProject(
661662
project: T,
662663
options?: { user?: string; filters?: IssueFilter[] },
663-
): Promise<SearchedIssue[] | undefined> {
664+
): Promise<IssueShape[] | undefined> {
664665
const connected = this.maybeConnected ?? (await this.isConnected());
665666
if (!connected) return undefined;
666667

@@ -669,15 +670,15 @@ export abstract class IssueIntegration<
669670
this.resetRequestExceptionCount();
670671
return issues;
671672
} catch (ex) {
672-
return this.handleProviderException<SearchedIssue[] | undefined>(ex, undefined, undefined);
673+
return this.handleProviderException<IssueShape[] | undefined>(ex, undefined, undefined);
673674
}
674675
}
675676

676677
protected abstract getProviderIssuesForProject(
677678
session: ProviderAuthenticationSession,
678679
project: T,
679680
options?: { user?: string; filters?: IssueFilter[] },
680-
): Promise<SearchedIssue[] | undefined>;
681+
): Promise<IssueShape[] | undefined>;
681682
}
682683

683684
export abstract class HostingIntegration<
@@ -1293,18 +1294,18 @@ export abstract class HostingIntegration<
12931294
repo?: T,
12941295
cancellation?: CancellationToken,
12951296
silent?: boolean,
1296-
): Promise<IntegrationResult<SearchedPullRequest[] | undefined>>;
1297+
): Promise<IntegrationResult<PullRequest[] | undefined>>;
12971298
async searchMyPullRequests(
12981299
repos?: T[],
12991300
cancellation?: CancellationToken,
13001301
silent?: boolean,
1301-
): Promise<IntegrationResult<SearchedPullRequest[] | undefined>>;
1302+
): Promise<IntegrationResult<PullRequest[] | undefined>>;
13021303
@debug()
13031304
async searchMyPullRequests(
13041305
repos?: T | T[],
13051306
cancellation?: CancellationToken,
13061307
silent?: boolean,
1307-
): Promise<IntegrationResult<SearchedPullRequest[] | undefined>> {
1308+
): Promise<IntegrationResult<PullRequest[] | undefined>> {
13081309
const scope = getLogScope();
13091310
const connected = this.maybeConnected ?? (await this.isConnected());
13101311
if (!connected) return undefined;
@@ -1329,7 +1330,7 @@ export abstract class HostingIntegration<
13291330
repos?: T[],
13301331
cancellation?: CancellationToken,
13311332
silent?: boolean,
1332-
): Promise<SearchedPullRequest[] | undefined>;
1333+
): Promise<PullRequest[] | undefined>;
13331334

13341335
async searchPullRequests(
13351336
searchQuery: string,

src/plus/integrations/integrationService.ts

+14-16
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import type { Source } from '../../constants.telemetry';
1111
import { sourceToContext } from '../../constants.telemetry';
1212
import type { Container } from '../../container';
1313
import type { Account } from '../../git/models/author';
14-
import type { SearchedIssue } from '../../git/models/issue';
15-
import type { SearchedPullRequest } from '../../git/models/pullRequest';
14+
import type { IssueShape } from '../../git/models/issue';
15+
import type { PullRequest } from '../../git/models/pullRequest';
1616
import type { GitRemote } from '../../git/models/remote';
1717
import type { RemoteProvider, RemoteProviderId } from '../../git/remotes/remoteProvider';
1818
import { configuration } from '../../system/-webview/configuration';
@@ -726,7 +726,7 @@ export class IntegrationService implements Disposable {
726726
| SupportedSelfHostedIntegrationIds
727727
)[],
728728
options?: { openRepositoriesOnly?: boolean; cancellation?: CancellationToken },
729-
): Promise<SearchedIssue[] | undefined> {
729+
): Promise<IssueShape[] | undefined> {
730730
const integrations: Map<Integration, ResourceDescriptor[] | undefined> = new Map();
731731
const hostingIntegrationIds = integrationIds?.filter(
732732
id => id in HostingIntegrationId || id in SelfHostedIntegrationId,
@@ -796,8 +796,8 @@ export class IntegrationService implements Disposable {
796796
private async getMyIssuesCore(
797797
integrations: Map<Integration, ResourceDescriptor[] | undefined>,
798798
cancellation?: CancellationToken,
799-
): Promise<SearchedIssue[] | undefined> {
800-
const promises: Promise<SearchedIssue[] | undefined>[] = [];
799+
): Promise<IssueShape[] | undefined> {
800+
const promises: Promise<IssueShape[] | undefined>[] = [];
801801
for (const [integration, repos] of integrations) {
802802
if (integration == null) continue;
803803

@@ -808,12 +808,12 @@ export class IntegrationService implements Disposable {
808808
return [...flatten(filterMap(results, r => (r.status === 'fulfilled' ? r.value : undefined)))];
809809
}
810810

811-
async getMyIssuesForRemotes(remote: GitRemote): Promise<SearchedIssue[] | undefined>;
812-
async getMyIssuesForRemotes(remotes: GitRemote[]): Promise<SearchedIssue[] | undefined>;
811+
async getMyIssuesForRemotes(remote: GitRemote): Promise<IssueShape[] | undefined>;
812+
async getMyIssuesForRemotes(remotes: GitRemote[]): Promise<IssueShape[] | undefined>;
813813
@debug<IntegrationService['getMyIssuesForRemotes']>({
814814
args: { 0: (r: GitRemote | GitRemote[]) => (Array.isArray(r) ? r.map(rp => rp.name) : r.name) },
815815
})
816-
async getMyIssuesForRemotes(remoteOrRemotes: GitRemote | GitRemote[]): Promise<SearchedIssue[] | undefined> {
816+
async getMyIssuesForRemotes(remoteOrRemotes: GitRemote | GitRemote[]): Promise<IssueShape[] | undefined> {
817817
if (!Array.isArray(remoteOrRemotes)) {
818818
remoteOrRemotes = [remoteOrRemotes];
819819
}
@@ -874,7 +874,7 @@ export class IntegrationService implements Disposable {
874874
integrationIds?: (HostingIntegrationId | CloudSelfHostedIntegrationId)[],
875875
cancellation?: CancellationToken,
876876
silent?: boolean,
877-
): Promise<IntegrationResult<SearchedPullRequest[] | undefined>> {
877+
): Promise<IntegrationResult<PullRequest[] | undefined>> {
878878
const integrations: Map<HostingIntegration, ResourceDescriptor[] | undefined> = new Map();
879879
for (const integrationId of integrationIds?.length ? integrationIds : Object.values(HostingIntegrationId)) {
880880
let integration;
@@ -894,10 +894,10 @@ export class IntegrationService implements Disposable {
894894
integrations: Map<HostingIntegration, ResourceDescriptor[] | undefined>,
895895
cancellation?: CancellationToken,
896896
silent?: boolean,
897-
): Promise<IntegrationResult<SearchedPullRequest[] | undefined>> {
897+
): Promise<IntegrationResult<PullRequest[] | undefined>> {
898898
const start = Date.now();
899899

900-
const promises: Promise<IntegrationResult<SearchedPullRequest[] | undefined>>[] = [];
900+
const promises: Promise<IntegrationResult<PullRequest[] | undefined>>[] = [];
901901
for (const [integration, repos] of integrations) {
902902
if (integration == null) continue;
903903

@@ -932,16 +932,14 @@ export class IntegrationService implements Disposable {
932932
};
933933
}
934934

935-
async getMyPullRequestsForRemotes(remote: GitRemote): Promise<IntegrationResult<SearchedPullRequest[] | undefined>>;
936-
async getMyPullRequestsForRemotes(
937-
remotes: GitRemote[],
938-
): Promise<IntegrationResult<SearchedPullRequest[] | undefined>>;
935+
async getMyPullRequestsForRemotes(remote: GitRemote): Promise<IntegrationResult<PullRequest[] | undefined>>;
936+
async getMyPullRequestsForRemotes(remotes: GitRemote[]): Promise<IntegrationResult<PullRequest[] | undefined>>;
939937
@debug<IntegrationService['getMyPullRequestsForRemotes']>({
940938
args: { 0: (r: GitRemote | GitRemote[]) => (Array.isArray(r) ? r.map(rp => rp.name) : r.name) },
941939
})
942940
async getMyPullRequestsForRemotes(
943941
remoteOrRemotes: GitRemote | GitRemote[],
944-
): Promise<IntegrationResult<SearchedPullRequest[] | undefined>> {
942+
): Promise<IntegrationResult<PullRequest[] | undefined>> {
945943
if (!Array.isArray(remoteOrRemotes)) {
946944
remoteOrRemotes = [remoteOrRemotes];
947945
}

src/plus/integrations/providers/azure/azure.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -376,22 +376,22 @@ export class AzureDevOpsApi implements Disposable {
376376
throw new RequestNotFoundError(ex);
377377
case 401: // Unauthorized
378378
throw new AuthenticationError('azureDevOps', AuthenticationErrorReason.Unauthorized, ex);
379-
// TODO: Learn the Azure API docs and put it in order:
380-
// case 403: // Forbidden
381-
// if (ex.message.includes('rate limit')) {
382-
// let resetAt: number | undefined;
383-
384-
// const reset = ex.response?.headers?.get('x-ratelimit-reset');
385-
// if (reset != null) {
386-
// resetAt = parseInt(reset, 10);
387-
// if (Number.isNaN(resetAt)) {
388-
// resetAt = undefined;
389-
// }
390-
// }
391-
392-
// throw new RequestRateLimitError(ex, token, resetAt);
393-
// }
394-
// throw new AuthenticationError('azure', AuthenticationErrorReason.Forbidden, ex);
379+
case 403: // Forbidden
380+
// TODO: Learn the Azure API docs and put it in order:
381+
// if (ex.message.includes('rate limit')) {
382+
// let resetAt: number | undefined;
383+
384+
// const reset = ex.response?.headers?.get('x-ratelimit-reset');
385+
// if (reset != null) {
386+
// resetAt = parseInt(reset, 10);
387+
// if (Number.isNaN(resetAt)) {
388+
// resetAt = undefined;
389+
// }
390+
// }
391+
392+
// throw new RequestRateLimitError(ex, token, resetAt);
393+
// }
394+
throw new AuthenticationError('azure', AuthenticationErrorReason.Forbidden, ex);
395395
case 500: // Internal Server Error
396396
Logger.error(ex, scope);
397397
if (ex.response != null) {

0 commit comments

Comments
 (0)