Skip to content

Commit 5c21ca7

Browse files
committed
add support for pulling bitbucket repos and UI support for bitbucket
1 parent 9fb9973 commit 5c21ca7

File tree

6 files changed

+97
-16
lines changed

6 files changed

+97
-16
lines changed

packages/backend/src/bitbucket.ts

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ interface BitbucketClient {
3535
getPaginated: <T, V extends CloudGetRequestPath>(path: V, get: (url: V) => Promise<PaginatedResponse<T>>) => Promise<T[]>;
3636
getReposForWorkspace: (client: BitbucketClient, workspace: string) => Promise<{validRepos: BitbucketRepository[], notFoundWorkspaces: string[]}>;
3737
getReposForProjects: (client: BitbucketClient, projects: string[]) => Promise<{validRepos: BitbucketRepository[], notFoundProjects: string[]}>;
38+
getRepos: (client: BitbucketClient, repos: string[]) => Promise<{validRepos: BitbucketRepository[], notFoundRepos: string[]}>;
3839
/*
39-
getRepos: (client: BitbucketClient, workspace: string, project: string) => Promise<Repo[]>;
4040
countForks: (client: BitbucketClient, repo: Repo) => Promise<number>;
4141
countWatchers: (client: BitbucketClient, repo: Repo) => Promise<number>;
4242
getBranches: (client: BitbucketClient, repo: string) => Promise<string[]>;
@@ -89,6 +89,12 @@ export const getBitbucketReposFromConfig = async (config: BitbucketConnectionCon
8989
notFound.repos = notFoundProjects;
9090
}
9191

92+
if (config.repos) {
93+
const { validRepos, notFoundRepos } = await client.getRepos(client, config.repos);
94+
allRepos = allRepos.concat(validRepos);
95+
notFound.repos = notFoundRepos;
96+
}
97+
9298
return {
9399
validRepos: allRepos,
94100
notFound,
@@ -114,6 +120,7 @@ function cloudClient(token: string | undefined): BitbucketClient {
114120
getPaginated: getPaginatedCloud,
115121
getReposForWorkspace: cloudGetReposForWorkspace,
116122
getReposForProjects: cloudGetReposForProjects,
123+
getRepos: cloudGetRepos,
117124
/*
118125
getRepos: cloudGetRepos,
119126
countForks: cloudCountForks,
@@ -170,7 +177,7 @@ async function cloudGetReposForWorkspace(client: BitbucketClient, workspace: str
170177
});
171178
const { data, error } = response;
172179
if (error) {
173-
throw new Error (`Failed to fetch projects for workspace ${workspace}: ${error.type}`);
180+
throw new Error(`Failed to fetch projects for workspace ${workspace}: ${JSON.stringify(error)}`);
174181
}
175182
return data;
176183
});
@@ -192,8 +199,15 @@ async function cloudGetReposForWorkspace(client: BitbucketClient, workspace: str
192199
async function cloudGetReposForProjects(client: BitbucketClient, projects: string[]): Promise<{validRepos: CloudRepository[], notFoundProjects: string[]}> {
193200
const results = await Promise.allSettled(projects.map(async (project) => {
194201
const [workspace, project_name] = project.split('/');
195-
logger.debug(`Fetching all repos for project ${project} for workspace ${workspace}...`);
202+
if (!workspace || !project_name) {
203+
logger.error(`Invalid project ${project}`);
204+
return {
205+
type: 'notFound' as const,
206+
value: project
207+
}
208+
}
196209

210+
logger.debug(`Fetching all repos for project ${project} for workspace ${workspace}...`);
197211
try {
198212
const path = `/repositories/${workspace}?q=project.key="${project_name}"` as CloudGetRequestPath;
199213
const repos = await client.getPaginated<CloudRepository, typeof path>(path, async (url) => {
@@ -233,3 +247,50 @@ async function cloudGetReposForProjects(client: BitbucketClient, projects: strin
233247
notFoundProjects
234248
}
235249
}
250+
251+
async function cloudGetRepos(client: BitbucketClient, repos: string[]): Promise<{validRepos: CloudRepository[], notFoundRepos: string[]}> {
252+
const results = await Promise.allSettled(repos.map(async (repo) => {
253+
const [workspace, repo_slug] = repo.split('/');
254+
if (!workspace || !repo_slug) {
255+
logger.error(`Invalid repo ${repo}`);
256+
return {
257+
type: 'notFound' as const,
258+
value: repo
259+
};
260+
}
261+
262+
logger.debug(`Fetching repo ${repo_slug} for workspace ${workspace}...`);
263+
try {
264+
const path = `/repositories/${workspace}/${repo_slug}` as CloudGetRequestPath;
265+
const response = await client.apiClient.GET(path);
266+
const { data, error } = response;
267+
if (error) {
268+
throw new Error(`Failed to fetch repo ${repo}: ${error.type}`);
269+
}
270+
return {
271+
type: 'valid' as const,
272+
data: [data]
273+
};
274+
} catch (e: any) {
275+
Sentry.captureException(e);
276+
logger.error(`Failed to fetch repo ${repo}: ${e}`);
277+
278+
const status = e?.cause?.response?.status;
279+
if (status === 404) {
280+
logger.error(`Repo ${repo} not found in ${workspace} or invalid access`);
281+
return {
282+
type: 'notFound' as const,
283+
value: repo
284+
};
285+
}
286+
throw e;
287+
}
288+
}));
289+
290+
throwIfAnyFailed(results);
291+
const { validItems: validRepos, notFoundItems: notFoundRepos } = processPromiseResults(results);
292+
return {
293+
validRepos,
294+
notFoundRepos
295+
};
296+
}

packages/backend/src/repoCompileUtils.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -373,25 +373,21 @@ export const compileBitbucketConfig = async (
373373
'zoekt.public': marshalBool(serverRepo.public),
374374
'zoekt.display-name': repoDisplayName,
375375
},
376-
},
376+
} satisfies RepoMetadata,
377377
}
378378
} else {
379379
const cloudRepo = repo as BitbucketCloudRepository;
380380

381381
const repoDisplayName = cloudRepo.full_name!;
382382
const repoName = path.join(repoNameRoot, repoDisplayName);
383383

384-
const cloneInfo = cloudRepo.links!.clone![0];
385-
const webInfo = cloudRepo.links!.self!;
386-
const cloneUrl = new URL(cloneInfo.href!);
387-
const webUrl = new URL(webInfo.href!);
388-
384+
const htmlUrl = cloudRepo.links!.html?.href!;
389385
record = {
390386
external_id: cloudRepo.uuid!,
391387
external_codeHostType: 'bitbucket-cloud',
392388
external_codeHostUrl: hostUrl,
393-
cloneUrl: cloneUrl.toString(),
394-
webUrl: webUrl.toString(),
389+
cloneUrl: htmlUrl,
390+
webUrl: htmlUrl,
395391
name: repoName,
396392
displayName: repoDisplayName,
397393
isFork: false,
@@ -409,14 +405,14 @@ export const compileBitbucketConfig = async (
409405
metadata: {
410406
gitConfig: {
411407
'zoekt.web-url-type': 'bitbucket-cloud',
412-
'zoekt.web-url': webUrl.toString(),
408+
'zoekt.web-url': htmlUrl,
413409
'zoekt.name': repoName,
414410
'zoekt.archived': marshalBool(false),
415411
'zoekt.fork': marshalBool(false),
416412
'zoekt.public': marshalBool(cloudRepo.is_private === false),
417413
'zoekt.display-name': repoDisplayName,
418414
},
419-
},
415+
} satisfies RepoMetadata,
420416
}
421417
}
422418

packages/backend/src/repoManager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ export class RepoManager implements IRepoManager {
181181
switch (repo.external_codeHostType) {
182182
case 'gitlab':
183183
return 'oauth2';
184-
case 'bitbucket':
184+
case 'bitbucket-server':
185+
case 'bitbucket-cloud':
185186
return 'x-token-auth';
186187
case 'github':
187188
case 'gitea':

packages/web/public/bitbucket.svg

Lines changed: 5 additions & 0 deletions
Loading

packages/web/src/lib/utils.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import githubLogo from "@/public/github.svg";
44
import gitlabLogo from "@/public/gitlab.svg";
55
import giteaLogo from "@/public/gitea.svg";
66
import gerritLogo from "@/public/gerrit.svg";
7+
import bitbucketLogo from "@/public/bitbucket.svg";
78
import { ServiceError } from "./serviceError";
89
import { Repository, RepositoryQuery } from "./types";
910

@@ -31,7 +32,7 @@ export const createPathWithQueryParams = (path: string, ...queryParams: [string,
3132
return `${path}?${queryString}`;
3233
}
3334

34-
export type CodeHostType = "github" | "gitlab" | "gitea" | "gerrit";
35+
export type CodeHostType = "github" | "gitlab" | "gitea" | "gerrit" | "bitbucket";
3536

3637
type CodeHostInfo = {
3738
type: CodeHostType;
@@ -110,6 +111,18 @@ const _getCodeHostInfoInternal = (type: string, displayName: string, cloneUrl: s
110111
iconClassName: className,
111112
}
112113
}
114+
case "bitbucket-server":
115+
case "bitbucket-cloud": {
116+
const { src, className } = getCodeHostIcon('bitbucket')!;
117+
return {
118+
type: "bitbucket",
119+
displayName: displayName,
120+
codeHostName: "Bitbucket",
121+
repoLink: cloneUrl,
122+
icon: src,
123+
iconClassName: className,
124+
}
125+
}
113126
}
114127
}
115128

@@ -132,6 +145,10 @@ export const getCodeHostIcon = (codeHostType: CodeHostType): { src: string, clas
132145
return {
133146
src: gerritLogo,
134147
}
148+
case "bitbucket":
149+
return {
150+
src: bitbucketLogo,
151+
}
135152
default:
136153
return null;
137154
}
@@ -142,6 +159,7 @@ export const isAuthSupportedForCodeHost = (codeHostType: CodeHostType): boolean
142159
case "github":
143160
case "gitlab":
144161
case "gitea":
162+
case "bitbucket":
145163
return true;
146164
case "gerrit":
147165
return false;

vendor/zoekt

0 commit comments

Comments
 (0)