Skip to content

Commit 7a8ac0a

Browse files
committed
add gerrit config compilation
1 parent 710b99e commit 7a8ac0a

File tree

4 files changed

+102
-63
lines changed

4 files changed

+102
-63
lines changed

packages/backend/src/connectionManager.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ConnectionConfig } from "@sourcebot/schemas/v3/connection.type";
55
import { createLogger } from "./logger.js";
66
import os from 'os';
77
import { Redis } from 'ioredis';
8-
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig } from "./repoCompileUtils.js";
8+
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
99

1010
interface IConnectionManager {
1111
scheduleConnectionSync: (connection: Connection) => Promise<void>;
@@ -89,11 +89,14 @@ export class ConnectionManager implements IConnectionManager {
8989
case 'gitea': {
9090
return await compileGiteaConfig(config, job.data.connectionId, orgId, this.db);
9191
}
92+
case 'gerrit': {
93+
return await compileGerritConfig(config, job.data.connectionId, orgId);
94+
}
9295
default: {
9396
return [];
9497
}
9598
}
96-
)();
99+
})();
97100

98101
// Filter out any duplicates by external_id and external_codeHostUrl.
99102
repoData.filter((repo, index, self) => {

packages/backend/src/gerrit.ts

Lines changed: 33 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import fetch from 'cross-fetch';
22
import { GerritConfig } from "@sourcebot/schemas/v2/index.type"
3-
import { AppContext, GitRepository } from './types.js';
43
import { createLogger } from './logger.js';
5-
import path from 'path';
4+
import micromatch from "micromatch";
65
import { measure, marshalBool, excludeReposByName, includeReposByName } from './utils.js';
76

87
// https://gerrit-review.googlesource.com/Documentation/rest-api.html
@@ -16,19 +15,26 @@ interface GerritProjectInfo {
1615
web_links?: GerritWebLink[];
1716
}
1817

18+
interface GerritProject {
19+
name: string;
20+
id: string;
21+
state?: string;
22+
web_links?: GerritWebLink[];
23+
}
24+
1925
interface GerritWebLink {
2026
name: string;
2127
url: string;
2228
}
2329

2430
const logger = createLogger('Gerrit');
2531

26-
export const getGerritReposFromConfig = async (config: GerritConfig, ctx: AppContext): Promise<GitRepository[]> => {
32+
export const getGerritReposFromConfig = async (config: GerritConfig): Promise<GerritProject[]> => {
2733

2834
const url = config.url.endsWith('/') ? config.url : `${config.url}/`;
2935
const hostname = new URL(config.url).hostname;
3036

31-
const { durationMs, data: projects } = await measure(async () => {
37+
let { durationMs, data: projects } = await measure(async () => {
3238
try {
3339
return fetchAllProjects(url)
3440
} catch (err) {
@@ -42,67 +48,29 @@ export const getGerritReposFromConfig = async (config: GerritConfig, ctx: AppCon
4248
}
4349

4450
// exclude "All-Projects" and "All-Users" projects
45-
delete projects['All-Projects'];
46-
delete projects['All-Users'];
47-
delete projects['All-Avatars']
48-
delete projects['All-Archived-Projects']
49-
50-
logger.debug(`Fetched ${Object.keys(projects).length} projects in ${durationMs}ms.`);
51-
52-
let repos: GitRepository[] = Object.keys(projects).map((projectName) => {
53-
const project = projects[projectName];
54-
let webUrl = "https://www.gerritcodereview.com/";
55-
// Gerrit projects can have multiple web links; use the first one
56-
if (project.web_links) {
57-
const webLink = project.web_links[0];
58-
if (webLink) {
59-
webUrl = webLink.url;
60-
}
61-
}
62-
const repoId = `${hostname}/${projectName}`;
63-
const repoPath = path.resolve(path.join(ctx.reposPath, `${repoId}.git`));
64-
65-
const cloneUrl = `${url}${encodeURIComponent(projectName)}`;
66-
67-
return {
68-
vcs: 'git',
69-
codeHost: 'gerrit',
70-
name: projectName,
71-
id: repoId,
72-
cloneUrl: cloneUrl,
73-
path: repoPath,
74-
isStale: false, // Gerrit projects are typically not stale
75-
isFork: false, // Gerrit doesn't have forks in the same way as GitHub
76-
isArchived: false,
77-
gitConfigMetadata: {
78-
// Gerrit uses Gitiles for web UI. This can sometimes be "browse" type in zoekt
79-
'zoekt.web-url-type': 'gitiles',
80-
'zoekt.web-url': webUrl,
81-
'zoekt.name': repoId,
82-
'zoekt.archived': marshalBool(false),
83-
'zoekt.fork': marshalBool(false),
84-
'zoekt.public': marshalBool(true), // Assuming projects are public; adjust as needed
85-
},
86-
branches: [],
87-
tags: []
88-
} satisfies GitRepository;
89-
});
90-
51+
const excludedProjects = ['All-Projects', 'All-Users', 'All-Avatars', 'All-Archived-Projects'];
52+
projects = projects.filter(project => !excludedProjects.includes(project.name));
53+
9154
// include repos by glob if specified in config
9255
if (config.projects) {
93-
repos = includeReposByName(repos, config.projects);
56+
projects = projects.filter((project) => {
57+
return micromatch.isMatch(project.name, config.projects!);
58+
});
9459
}
95-
60+
9661
if (config.exclude && config.exclude.projects) {
97-
repos = excludeReposByName(repos, config.exclude.projects);
62+
projects = projects.filter((project) => {
63+
return !micromatch.isMatch(project.name, config.exclude!.projects!);
64+
});
9865
}
9966

100-
return repos;
67+
logger.debug(`Fetched ${Object.keys(projects).length} projects in ${durationMs}ms.`);
68+
return projects;
10169
};
10270

103-
const fetchAllProjects = async (url: string): Promise<GerritProjects> => {
71+
const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
10472
const projectsEndpoint = `${url}projects/`;
105-
let allProjects: GerritProjects = {};
73+
let allProjects: GerritProject[] = [];
10674
let start = 0; // Start offset for pagination
10775
let hasMoreProjects = true;
10876

@@ -119,8 +87,15 @@ const fetchAllProjects = async (url: string): Promise<GerritProjects> => {
11987
const jsonText = text.replace(")]}'\n", ''); // Remove XSSI protection prefix
12088
const data: GerritProjects = JSON.parse(jsonText);
12189

122-
// Merge the current batch of projects with allProjects
123-
Object.assign(allProjects, data);
90+
// Add fetched projects to allProjects
91+
for (const [projectName, projectInfo] of Object.entries(data)) {
92+
allProjects.push({
93+
name: projectName,
94+
id: projectInfo.id,
95+
state: projectInfo.state,
96+
web_links: projectInfo.web_links
97+
})
98+
}
12499

125100
// Check if there are more projects to fetch
126101
hasMoreProjects = Object.values(data).some(

packages/backend/src/repoCompileUtils.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { GithubConnectionConfig } from '@sourcebot/schemas/v3/github.type';
22
import { getGitHubReposFromConfig } from "./github.js";
33
import { getGitLabReposFromConfig } from "./gitlab.js";
44
import { getGiteaReposFromConfig } from "./gitea.js";
5+
import { getGerritReposFromConfig } from "./gerrit.js";
56
import { Prisma, PrismaClient } from '@sourcebot/db';
67
import { WithRequired } from "./types.js"
78
import { marshalBool } from "./utils.js";
8-
import { GiteaConnectionConfig, GitlabConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
9+
import { GerritConnectionConfig, GiteaConnectionConfig, GitlabConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
910

1011
export type RepoData = WithRequired<Prisma.RepoCreateInput, 'connections'>;
1112

@@ -148,6 +149,60 @@ export const compileGiteaConfig = async (
148149
},
149150
};
150151

152+
return record;
153+
})
154+
}
155+
156+
export const compileGerritConfig = async (
157+
config: GerritConnectionConfig,
158+
connectionId: number,
159+
orgId: number) => {
160+
161+
const gerritRepos = await getGerritReposFromConfig(config);
162+
const hostUrl = config.url ?? 'https://gerritcodereview.com';
163+
const hostname = config.url ? new URL(config.url).hostname : 'gerritcodereview.com';
164+
165+
return gerritRepos.map((project) => {
166+
const repoId = `${hostname}/${project.name}`;
167+
const cloneUrl = new URL(`${config.url}/${encodeURIComponent(project.name)}`);
168+
169+
let webUrl = "https://www.gerritcodereview.com/";
170+
// Gerrit projects can have multiple web links; use the first one
171+
if (project.web_links) {
172+
const webLink = project.web_links[0];
173+
if (webLink) {
174+
webUrl = webLink.url;
175+
}
176+
}
177+
178+
const record: RepoData = {
179+
external_id: project.id.toString(),
180+
external_codeHostType: 'gerrit',
181+
external_codeHostUrl: hostUrl,
182+
cloneUrl: cloneUrl.toString(),
183+
name: project.name,
184+
isFork: false,
185+
isArchived: false,
186+
org: {
187+
connect: {
188+
id: orgId,
189+
},
190+
},
191+
connections: {
192+
create: {
193+
connectionId: connectionId,
194+
}
195+
},
196+
metadata: {
197+
'zoekt.web-url-type': 'gitiles',
198+
'zoekt.web-url': webUrl,
199+
'zoekt.name': repoId,
200+
'zoekt.archived': marshalBool(false),
201+
'zoekt.fork': marshalBool(false),
202+
'zoekt.public': marshalBool(true),
203+
},
204+
};
205+
151206
return record;
152207
})
153208
}

packages/web/src/app/connections/quickActions.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,15 @@ export const gerritQuickActions: QuickAction<GerritConnectionConfig>[] = [
135135
{
136136
fn: (previous: GerritConnectionConfig) => ({
137137
...previous,
138-
url: previous.url ?? "",
138+
exclude: {
139+
...previous.exclude,
140+
projects: [
141+
...(previous.exclude?.projects ?? []),
142+
""
143+
]
144+
}
139145
}),
140-
name: "Set a custom url",
146+
name: "Exclude a project",
141147
}
142148
]
143149

0 commit comments

Comments
 (0)