Skip to content
Merged
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Adds AI model status and model switcher to the _Home_ view ([#4064](https://github.com/gitkraken/vscode-gitlens/issues/4064))
- Adds Anthropic Claude 3.7 Sonnet model for GitLens' AI features ([#4101](https://github.com/gitkraken/vscode-gitlens/issues/4101))
- Adds Google Gemini 2.0 Flash-Lite model for GitLens' AI features ([#4104](https://github.com/gitkraken/vscode-gitlens/issues/4104))
- Adds integration with Bitbucket Cloud ([#3916](https://github.com/gitkraken/vscode-gitlens/issues/3916))
- Adds integration with Bitbucket Cloud and Data Center ([#3916](https://github.com/gitkraken/vscode-gitlens/issues/3916))
- shows enriched links to PRs and issues [#4045](https://github.com/gitkraken/vscode-gitlens/issues/4045)
- shows Bitbucket PRs in Launchpad [#4046](https://github.com/gitkraken/vscode-gitlens/issues/4046)
- supports Bitbucket issues in Start Work and lets associate issues with branches [#4047](https://github.com/gitkraken/vscode-gitlens/issues/4047)
- shows Bitbucket Cloud and Data Center PRs in Launchpad [#4046](https://github.com/gitkraken/vscode-gitlens/issues/4046)
- supports Bitbucket issues in Start Work and lets associate issues with branches [#4047](https://github.com/gitkraken/vscode-gitlens/issues/4047), [#4107](https://github.com/gitkraken/vscode-gitlens/issues/4107)
- Adds ability to control how worktrees are displayed in the views
- Adds a `gitlens.views.worktrees.worktrees.viewAs` setting to specify whether to show worktrees by name, path, or relative path
- Adds a `gitlens.views.worktrees.branches.layout` setting to specify whether to show branch worktrees as a list or tree, similar to branches
Expand Down
14 changes: 7 additions & 7 deletions docs/telemetry-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ or
```typescript
{
'hostingProvider.key': string,
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
}
```

Expand All @@ -389,7 +389,7 @@ or
```typescript
{
'hostingProvider.key': string,
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
}
```

Expand All @@ -400,7 +400,7 @@ or
```typescript
{
'issueProvider.key': string,
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
}
```

Expand All @@ -411,7 +411,7 @@ or
```typescript
{
'issueProvider.key': string,
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
}
```

Expand All @@ -432,7 +432,7 @@ or

```typescript
{
'integration.id': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
'integration.id': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
}
```

Expand Down Expand Up @@ -1553,7 +1553,7 @@ void
```typescript
{
'hostingProvider.key': string,
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted',
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted',
// @deprecated: true
'remoteProviders.key': string
}
Expand All @@ -1566,7 +1566,7 @@ void
```typescript
{
'hostingProvider.key': string,
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted',
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'bitbucket-server' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted',
// @deprecated: true
'remoteProviders.key': string
}
Expand Down
10 changes: 10 additions & 0 deletions src/constants.integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum HostingIntegrationId {
}

export enum SelfHostedIntegrationId {
BitbucketServer = 'bitbucket-server',
GitHubEnterprise = 'github-enterprise',
CloudGitHubEnterprise = 'cloud-github-enterprise',
CloudGitLabSelfHosted = 'cloud-gitlab-self-hosted',
Expand All @@ -14,6 +15,7 @@ export enum SelfHostedIntegrationId {

export type CloudSelfHostedIntegrationId =
| SelfHostedIntegrationId.CloudGitHubEnterprise
| SelfHostedIntegrationId.BitbucketServer
| SelfHostedIntegrationId.CloudGitLabSelfHosted;

export enum IssueIntegrationId {
Expand All @@ -31,6 +33,7 @@ export const supportedOrderedCloudIntegrationIds = [
SelfHostedIntegrationId.CloudGitLabSelfHosted,
HostingIntegrationId.AzureDevOps,
HostingIntegrationId.Bitbucket,
SelfHostedIntegrationId.BitbucketServer,
IssueIntegrationId.Jira,
];

Expand Down Expand Up @@ -92,6 +95,13 @@ export const supportedCloudIntegrationDescriptors: IntegrationDescriptor[] = [
supports: ['prs', 'issues'],
requiresPro: false,
},
{
id: SelfHostedIntegrationId.BitbucketServer,
name: 'Bitbucket Data Center',
icon: 'gl-provider-bitbucket',
supports: ['prs'],
requiresPro: true,
},
{
id: IssueIntegrationId.Jira,
name: 'Jira',
Expand Down
2 changes: 1 addition & 1 deletion src/constants.storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export type GlobalStorage = {
[key in `azure:${string}:projects`]: Stored<StoredAzureProject[] | undefined>;
} & { [key in `bitbucket:${string}:account`]: Stored<StoredBitbucketAccount | undefined> } & {
[key in `bitbucket:${string}:workspaces`]: Stored<StoredBitbucketWorkspace[] | undefined>;
};
} & { [key in `bitbucket-server:${string}:account`]: Stored<StoredBitbucketAccount | undefined> };

export type StoredIntegrationConfigurations = Record<string, StoredConfiguredIntegrationDescriptor[] | undefined>;

Expand Down
1 change: 1 addition & 0 deletions src/git/models/pullRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class PullRequest implements PullRequestShape {
public readonly assignees?: PullRequestMember[],
public readonly statusCheckRollupState?: PullRequestStatusCheckRollupState,
public readonly project?: IssueProject,
public readonly version?: number,
) {}

get closed(): boolean {
Expand Down
19 changes: 14 additions & 5 deletions src/git/remotes/remoteProviders.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { RemotesConfig } from '../../config';
import type { CloudSelfHostedIntegrationId } from '../../constants.integrations';
import { SelfHostedIntegrationId } from '../../constants.integrations';
import type { Container } from '../../container';
import type { ConfiguredIntegrationDescriptor } from '../../plus/integrations/authentication/models';
Expand Down Expand Up @@ -76,6 +77,15 @@ const builtInProviders: RemoteProviders = [
},
];

const cloudRemotesMap: Record<
CloudSelfHostedIntegrationId,
typeof GitHubRemote | typeof GitLabRemote | typeof BitbucketServerRemote
> = {
[SelfHostedIntegrationId.CloudGitHubEnterprise]: GitHubRemote,
[SelfHostedIntegrationId.CloudGitLabSelfHosted]: GitLabRemote,
[SelfHostedIntegrationId.BitbucketServer]: BitbucketServerRemote,
};

export function loadRemoteProviders(
cfg: RemotesConfig[] | null | undefined,
configuredIntegrations?: ConfiguredIntegrationDescriptor[],
Expand Down Expand Up @@ -105,12 +115,11 @@ export function loadRemoteProviders(

if (configuredIntegrations?.length) {
for (const ci of configuredIntegrations) {
if (isCloudSelfHostedIntegrationId(ci.integrationId) && ci.domain) {
const integrationId = ci.integrationId;
if (isCloudSelfHostedIntegrationId(integrationId) && ci.domain) {
const matcher = ci.domain.toLocaleLowerCase();
const providerCreator = (_container: Container, domain: string, path: string) =>
ci.integrationId === SelfHostedIntegrationId.CloudGitHubEnterprise
? new GitHubRemote(domain, path)
: new GitLabRemote(domain, path);
const providerCreator = (_container: Container, domain: string, path: string): RemoteProvider =>
new cloudRemotesMap[integrationId](domain, path);
const provider = {
custom: false,
matcher: matcher,
Expand Down
8 changes: 7 additions & 1 deletion src/plus/integrations/authentication/bitbucket.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { HostingIntegrationId } from '../../../constants.integrations';
import { HostingIntegrationId, SelfHostedIntegrationId } from '../../../constants.integrations';
import { CloudIntegrationAuthenticationProvider } from './integrationAuthenticationProvider';

export class BitbucketAuthenticationProvider extends CloudIntegrationAuthenticationProvider<HostingIntegrationId.Bitbucket> {
protected override get authProviderId(): HostingIntegrationId.Bitbucket {
return HostingIntegrationId.Bitbucket;
}
}

export class BitbucketServerAuthenticationProvider extends CloudIntegrationAuthenticationProvider<SelfHostedIntegrationId.BitbucketServer> {
protected override get authProviderId(): SelfHostedIntegrationId.BitbucketServer {
return SelfHostedIntegrationId.BitbucketServer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface StoredSession {
cloud?: boolean;
expiresAt?: string;
domain?: string;
protocol?: string;
}

export type ConfiguredIntegrationType = 'cloud' | 'local';
Expand Down Expand Up @@ -396,5 +397,6 @@ function convertStoredSessionToSession(
cloud: storedSession.cloud ?? cloudIfMissing,
expiresAt: storedSession.expiresAt ? new Date(storedSession.expiresAt) : undefined,
domain: storedSession.domain ?? descriptor.domain,
protocol: storedSession.protocol,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ export abstract class CloudIntegrationAuthenticationProvider<

if (!session) return undefined;

const sessionProtocol = new URL(session.domain).protocol;

// TODO: Once we care about domains, we should try to match the domain here against ours, and if it fails, return undefined
return {
id: this.configuredIntegrationService.getSessionId(descriptor),
Expand All @@ -287,6 +289,7 @@ export abstract class CloudIntegrationAuthenticationProvider<
expiresAt: new Date(session.expiresIn * 1000 + Date.now()),
// Note: do not use the session's domain, because the format is different than in our model
domain: descriptor.domain,
protocol: sessionProtocol ?? undefined,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ export class IntegrationAuthenticationService implements Disposable {
await import(/* webpackChunkName: "integrations" */ './bitbucket')
).BitbucketAuthenticationProvider(this.container, this, this.configuredIntegrationService);
break;
case SelfHostedIntegrationId.BitbucketServer:
provider = new (
await import(/* webpackChunkName: "integrations" */ './bitbucket')
).BitbucketServerAuthenticationProvider(this.container, this, this.configuredIntegrationService);
break;
case HostingIntegrationId.GitHub:
provider = isSupportedCloudIntegrationId(HostingIntegrationId.GitHub)
? new (
Expand Down
4 changes: 4 additions & 0 deletions src/plus/integrations/authentication/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface ProviderAuthenticationSession extends AuthenticationSession {
readonly cloud: boolean;
readonly expiresAt?: Date;
readonly domain: string;
readonly protocol?: string;
}

export interface ConfiguredIntegrationDescriptor {
Expand Down Expand Up @@ -47,6 +48,7 @@ export type CloudIntegrationType =
| 'gitlab'
| 'github'
| 'bitbucket'
| 'bitbucketServer'
| 'azure'
| 'githubEnterprise'
| 'gitlabSelfHosted';
Expand All @@ -73,6 +75,7 @@ export const toIntegrationId: { [key in CloudIntegrationType]: IntegrationId } =
githubEnterprise: SelfHostedIntegrationId.CloudGitHubEnterprise,
gitlabSelfHosted: SelfHostedIntegrationId.CloudGitLabSelfHosted,
bitbucket: HostingIntegrationId.Bitbucket,
bitbucketServer: SelfHostedIntegrationId.BitbucketServer,
azure: HostingIntegrationId.AzureDevOps,
};

Expand All @@ -85,6 +88,7 @@ export const toCloudIntegrationType: { [key in IntegrationId]: CloudIntegrationT
[HostingIntegrationId.AzureDevOps]: 'azure',
[SelfHostedIntegrationId.CloudGitHubEnterprise]: 'githubEnterprise',
[SelfHostedIntegrationId.CloudGitLabSelfHosted]: 'gitlabSelfHosted',
[SelfHostedIntegrationId.BitbucketServer]: 'bitbucketServer',
[SelfHostedIntegrationId.GitHubEnterprise]: undefined,
[SelfHostedIntegrationId.GitLabSelfHosted]: undefined,
};
47 changes: 47 additions & 0 deletions src/plus/integrations/integrationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,47 @@ export class IntegrationService implements Disposable {
await import(/* webpackChunkName: "integrations" */ './providers/bitbucket')
).BitbucketIntegration(this.container, this.authenticationService, this.getProvidersApi.bind(this));
break;
case SelfHostedIntegrationId.BitbucketServer:
if (domain == null) {
integration = this.findCachedById(id);
if (integration != null) {
// return immediately in order to not to cache it after the "switch" block:
return integration;
}

const existingConfigured = await this.getConfigured({
id: SelfHostedIntegrationId.BitbucketServer,
});
if (existingConfigured.length) {
const { domain: configuredDomain } = existingConfigured[0];
if (configuredDomain == null) {
throw new Error(`Domain is required for '${id}' integration`);
}
integration = new (
await import(/* webpackChunkName: "integrations" */ './providers/bitbucket-server')
).BitbucketServerIntegration(
this.container,
this.authenticationService,
this.getProvidersApi.bind(this),
configuredDomain,
);
// assign domain because it's part of caching key:
domain = configuredDomain;
break;
}

return undefined;
}

integration = new (
await import(/* webpackChunkName: "integrations" */ './providers/bitbucket-server')
).BitbucketServerIntegration(
this.container,
this.authenticationService,
this.getProvidersApi.bind(this),
domain,
);
break;
case HostingIntegrationId.AzureDevOps:
integration = new (
await import(/* webpackChunkName: "integrations" */ './providers/azureDevOps')
Expand Down Expand Up @@ -685,6 +726,11 @@ export class IntegrationService implements Disposable {
return get(HostingIntegrationId.Bitbucket) as RT;
}
return (getOrGetCached === this.get ? Promise.resolve(undefined) : undefined) as RT;
case 'bitbucket-server':
if (!isBitbucketCloudDomain(remote.provider.domain)) {
return get(SelfHostedIntegrationId.BitbucketServer) as RT;
}
return (getOrGetCached === this.get ? Promise.resolve(undefined) : undefined) as RT;
case 'github':
if (remote.provider.domain != null && !isGitHubDotCom(remote.provider.domain)) {
return get(
Expand Down Expand Up @@ -1051,6 +1097,7 @@ export function remoteProviderIdToIntegrationId(
case 'gitlab':
return HostingIntegrationId.GitLab;
case 'bitbucket-server':
return SelfHostedIntegrationId.BitbucketServer;
default:
return undefined;
}
Expand Down
Loading