Skip to content

Commit b930a1f

Browse files
committed
Implements getAccountForCommit for Bitbucket
(#4192)
1 parent 05f3d4c commit b930a1f

File tree

3 files changed

+130
-13
lines changed

3 files changed

+130
-13
lines changed

src/plus/integrations/providers/bitbucket.ts

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { AuthenticationSession, CancellationToken } from 'vscode';
22
import { md5 } from '@env/crypto';
33
import { HostingIntegrationId } from '../../../constants.integrations';
4-
import type { Account } from '../../../git/models/author';
4+
import type { Account, UnidentifiedAuthor } from '../../../git/models/author';
55
import type { DefaultBranch } from '../../../git/models/defaultBranch';
66
import type { Issue, IssueShape } from '../../../git/models/issue';
77
import type { IssueOrPullRequest, IssueOrPullRequestType } from '../../../git/models/issueOrPullRequest';
@@ -49,14 +49,24 @@ export class BitbucketIntegration extends HostingIntegration<
4949
}
5050

5151
protected override async getProviderAccountForCommit(
52-
_session: AuthenticationSession,
53-
_repo: BitbucketRepositoryDescriptor,
54-
_rev: string,
55-
_options?: {
52+
{ accessToken }: AuthenticationSession,
53+
repo: BitbucketRepositoryDescriptor,
54+
rev: string,
55+
options?: {
5656
avatarSize?: number;
5757
},
58-
): Promise<Account | undefined> {
59-
return Promise.resolve(undefined);
58+
): Promise<Account | UnidentifiedAuthor | undefined> {
59+
return (await this.container.bitbucket)?.getAccountForCommit(
60+
this,
61+
accessToken,
62+
repo.owner,
63+
repo.name,
64+
rev,
65+
this.apiBaseUrl,
66+
{
67+
avatarSize: options?.avatarSize,
68+
},
69+
);
6070
}
6171

6272
protected override async getProviderAccountForEmail(

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

+65-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
RequestClientError,
1414
RequestNotFoundError,
1515
} from '../../../../errors';
16+
import type { Account, CommitAuthor, UnidentifiedAuthor } from '../../../../git/models/author';
1617
import type { Issue } from '../../../../git/models/issue';
1718
import type { IssueOrPullRequest, IssueOrPullRequestType } from '../../../../git/models/issueOrPullRequest';
1819
import type { PullRequest } from '../../../../git/models/pullRequest';
@@ -28,8 +29,13 @@ import { maybeStopWatch } from '../../../../system/stopwatch';
2829
import type { BitbucketServerPullRequest } from '../bitbucket-server/models';
2930
import { normalizeBitbucketServerPullRequest } from '../bitbucket-server/models';
3031
import { fromProviderPullRequest } from '../models';
31-
import type { BitbucketIssue, BitbucketPullRequest, BitbucketRepository } from './models';
32-
import { bitbucketIssueStateToState, fromBitbucketIssue, fromBitbucketPullRequest } from './models';
32+
import type { BitbucketCommit, BitbucketIssue, BitbucketPullRequest, BitbucketRepository } from './models';
33+
import {
34+
bitbucketIssueStateToState,
35+
fromBitbucketIssue,
36+
fromBitbucketPullRequest,
37+
parseRawBitbucketAuthor,
38+
} from './models';
3339

3440
export class BitbucketApi implements Disposable {
3541
private readonly _disposable: Disposable;
@@ -456,6 +462,63 @@ export class BitbucketApi implements Disposable {
456462
}
457463
}
458464

465+
@debug<BitbucketApi['getAccountForCommit']>({ args: { 0: p => p.name, 1: '<token>' } })
466+
async getAccountForCommit(
467+
provider: Provider,
468+
token: string,
469+
owner: string,
470+
repo: string,
471+
rev: string,
472+
baseUrl: string,
473+
_options?: {
474+
avatarSize?: number;
475+
},
476+
cancellation?: CancellationToken,
477+
): Promise<Account | UnidentifiedAuthor | undefined> {
478+
const scope = getLogScope();
479+
480+
try {
481+
const commit = await this.request<BitbucketCommit>(
482+
provider,
483+
token,
484+
baseUrl,
485+
`repositories/${owner}/${repo}/commit/${rev}`,
486+
{
487+
method: 'GET',
488+
},
489+
scope,
490+
cancellation,
491+
);
492+
if (!commit) {
493+
return undefined;
494+
}
495+
496+
const { name, email } = parseRawBitbucketAuthor(commit.author.raw);
497+
const commitAuthor: CommitAuthor = {
498+
provider: provider,
499+
id: commit.author.user?.account_id,
500+
username: commit.author.user?.nickname,
501+
name: commit.author.user?.display_name || name,
502+
email: email,
503+
avatarUrl: commit.author.user?.links?.avatar?.href,
504+
};
505+
if (commitAuthor.id != null && commitAuthor.username != null) {
506+
return {
507+
...commitAuthor,
508+
id: commitAuthor.id,
509+
} satisfies Account;
510+
}
511+
return {
512+
...commitAuthor,
513+
id: undefined,
514+
username: undefined,
515+
} satisfies UnidentifiedAuthor;
516+
} catch (ex) {
517+
Logger.error(ex, scope);
518+
return undefined;
519+
}
520+
}
521+
459522
private async request<T>(
460523
provider: Provider,
461524
token: string,

src/plus/integrations/providers/bitbucket/models.ts

+48-4
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ export interface BitbucketRepository {
104104
};
105105
}
106106

107+
interface BitbucketCommitAuthor {
108+
type: 'author';
109+
raw: string;
110+
user: BitbucketUser;
111+
}
112+
107113
type BitbucketMergeStrategy =
108114
| 'merge_commit'
109115
| 'squash'
@@ -118,7 +124,45 @@ interface BitbucketBranch {
118124
default_merge_strategy?: BitbucketMergeStrategy;
119125
}
120126

121-
interface BitbucketPullRequestCommit {
127+
// It parses a raw author sitring like "Sergei Shmakov GK <[email protected]>" to name and email
128+
const parseRawBitbucketAuthorRegex = /^(.*) <(.*)>$/;
129+
export function parseRawBitbucketAuthor(raw: string): { name: string; email: string } {
130+
const match = raw.match(parseRawBitbucketAuthorRegex);
131+
if (match) {
132+
return { name: match[1], email: match[2] };
133+
}
134+
return { name: raw, email: '' };
135+
}
136+
137+
export interface BitbucketCommit {
138+
type: 'commit';
139+
author: BitbucketCommitAuthor;
140+
date: string;
141+
hash: string;
142+
links: {
143+
approve: BitbucketLink;
144+
comments: BitbucketLink;
145+
diff: BitbucketLink;
146+
html: BitbucketLink;
147+
self: BitbucketLink;
148+
statuses: BitbucketLink;
149+
};
150+
message: string;
151+
parents: BitbucketBriefCommit[];
152+
participants: BitbucketPullRequestParticipant[];
153+
rendered: {
154+
message: string;
155+
};
156+
repository: BitbucketRepository;
157+
summary: {
158+
type: 'rendered';
159+
raw: string;
160+
markup: string;
161+
html: string;
162+
};
163+
}
164+
165+
interface BitbucketBriefCommit {
122166
type: 'commit';
123167
hash: string;
124168
links: {
@@ -144,7 +188,7 @@ export interface BitbucketPullRequest {
144188
title: string;
145189
description: string;
146190
state: BitbucketPullRequestState;
147-
merge_commit: null | BitbucketPullRequestCommit;
191+
merge_commit: null | BitbucketBriefCommit;
148192
comment_count: number;
149193
task_count: number;
150194
close_source_branch: boolean;
@@ -155,12 +199,12 @@ export interface BitbucketPullRequest {
155199
updated_on: string;
156200
destination: {
157201
branch: BitbucketBranch;
158-
commit: BitbucketPullRequestCommit;
202+
commit: BitbucketBriefCommit;
159203
repository: BitbucketRepository;
160204
};
161205
source: {
162206
branch: BitbucketBranch;
163-
commit: BitbucketPullRequestCommit;
207+
commit: BitbucketBriefCommit;
164208
repository: BitbucketRepository;
165209
};
166210
summary: {

0 commit comments

Comments
 (0)