Skip to content

Commit c449409

Browse files
committed
add gitea support
1 parent d0fb98c commit c449409

File tree

8 files changed

+213
-74
lines changed

8 files changed

+213
-74
lines changed

packages/backend/src/connectionManager.ts

Lines changed: 4 additions & 1 deletion
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 } from "./repoCompileUtils.js";
8+
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig } from "./repoCompileUtils.js";
99

1010
interface IConnectionManager {
1111
scheduleConnectionSync: (connection: Connection) => Promise<void>;
@@ -86,6 +86,9 @@ export class ConnectionManager implements IConnectionManager {
8686
case 'gitlab': {
8787
return await compileGitlabConfig(config, job.data.connectionId, orgId, this.db);
8888
}
89+
case 'gitea': {
90+
return await compileGiteaConfig(config, job.data.connectionId, orgId, this.db);
91+
}
8992
default: {
9093
return [];
9194
}

packages/backend/src/gitea.ts

Lines changed: 76 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { Api, giteaApi, HttpResponse, Repository as GiteaRepository } from 'gitea-js';
2-
import { GiteaConfig } from "@sourcebot/schemas/v2/index.type"
3-
import { excludeArchivedRepos, excludeForkedRepos, excludeReposByName, getTokenFromConfig, marshalBool, measure } from './utils.js';
4-
import { AppContext, GitRepository } from './types.js';
2+
import { GiteaConnectionConfig } from '@sourcebot/schemas/v3/gitea.type';
3+
import { getTokenFromConfig, measure } from './utils.js';
54
import fetch from 'cross-fetch';
65
import { createLogger } from './logger.js';
7-
import path from 'path';
86
import micromatch from 'micromatch';
7+
import { PrismaClient } from '@sourcebot/db';
98

109
const logger = createLogger('Gitea');
1110

12-
export const getGiteaReposFromConfig = async (config: GiteaConfig, orgId: number, ctx: AppContext) => {
11+
export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
1312
// TODO: pass in DB here to fetch secret properly
14-
const token = config.token ? await getTokenFromConfig(config.token, orgId) : undefined;
13+
const token = config.token ? await getTokenFromConfig(config.token, orgId, db) : undefined;
1514

1615
const api = giteaApi(config.url ?? 'https://gitea.com', {
1716
token,
@@ -34,94 +33,107 @@ export const getGiteaReposFromConfig = async (config: GiteaConfig, orgId: number
3433
const _repos = await getReposOwnedByUsers(config.users, api);
3534
allRepos = allRepos.concat(_repos);
3635
}
37-
38-
let repos: GitRepository[] = allRepos
39-
.map((repo) => {
40-
const hostname = config.url ? new URL(config.url).hostname : 'gitea.com';
41-
const repoId = `${hostname}/${repo.full_name!}`;
42-
const repoPath = path.resolve(path.join(ctx.reposPath, `${repoId}.git`));
43-
44-
const cloneUrl = new URL(repo.clone_url!);
45-
if (token) {
46-
cloneUrl.username = token;
47-
}
48-
49-
return {
50-
vcs: 'git',
51-
codeHost: 'gitea',
52-
name: repo.full_name!,
53-
id: repoId,
54-
cloneUrl: cloneUrl.toString(),
55-
path: repoPath,
56-
isStale: false,
57-
isFork: repo.fork!,
58-
isArchived: !!repo.archived,
59-
gitConfigMetadata: {
60-
'zoekt.web-url-type': 'gitea',
61-
'zoekt.web-url': repo.html_url!,
62-
'zoekt.name': repoId,
63-
'zoekt.archived': marshalBool(repo.archived),
64-
'zoekt.fork': marshalBool(repo.fork!),
65-
'zoekt.public': marshalBool(repo.internal === false && repo.private === false),
66-
},
67-
branches: [],
68-
tags: []
69-
} satisfies GitRepository;
70-
});
7136

72-
if (config.exclude) {
73-
if (!!config.exclude.forks) {
74-
repos = excludeForkedRepos(repos, logger);
75-
}
76-
77-
if (!!config.exclude.archived) {
78-
repos = excludeArchivedRepos(repos, logger);
79-
}
80-
81-
if (config.exclude.repos) {
82-
repos = excludeReposByName(repos, config.exclude.repos, logger);
37+
allRepos = allRepos.filter(repo => repo.full_name !== undefined);
38+
allRepos = allRepos.filter(repo => {
39+
if (repo.full_name === undefined) {
40+
logger.warn(`Repository with undefined full_name found: orgId=${orgId}, repoId=${repo.id}`);
41+
return false;
8342
}
84-
}
85-
86-
logger.debug(`Found ${repos.length} total repositories.`);
43+
return true;
44+
});
8745

46+
8847
if (config.revisions) {
8948
if (config.revisions.branches) {
9049
const branchGlobs = config.revisions.branches;
91-
repos = await Promise.all(
92-
repos.map(async (repo) => {
93-
const [owner, name] = repo.name.split('/');
50+
allRepos = await Promise.all(
51+
allRepos.map(async (repo) => {
52+
const [owner, name] = repo.full_name!.split('/');
9453
let branches = (await getBranchesForRepo(owner, name, api)).map(branch => branch.name!);
9554
branches = micromatch.match(branches, branchGlobs);
96-
55+
9756
return {
9857
...repo,
9958
branches,
10059
};
10160
})
10261
)
10362
}
104-
63+
10564
if (config.revisions.tags) {
10665
const tagGlobs = config.revisions.tags;
107-
repos = await Promise.all(
108-
repos.map(async (repo) => {
109-
const [owner, name] = repo.name.split('/');
66+
allRepos = await Promise.all(
67+
allRepos.map(async (allRepos) => {
68+
const [owner, name] = allRepos.name!.split('/');
11069
let tags = (await getTagsForRepo(owner, name, api)).map(tag => tag.name!);
11170
tags = micromatch.match(tags, tagGlobs);
112-
71+
11372
return {
114-
...repo,
73+
...allRepos,
11574
tags,
11675
};
11776
})
11877
)
11978
}
12079
}
80+
81+
let repos = allRepos
82+
.filter((repo) => {
83+
const isExcluded = shouldExcludeRepo({
84+
repo,
85+
exclude: config.exclude,
86+
});
87+
88+
return !isExcluded;
89+
});
12190

91+
logger.debug(`Found ${repos.length} total repositories.`);
12292
return repos;
12393
}
12494

95+
const shouldExcludeRepo = ({
96+
repo,
97+
exclude
98+
} : {
99+
repo: GiteaRepository,
100+
exclude?: {
101+
forks?: boolean,
102+
archived?: boolean,
103+
repos?: string[],
104+
}
105+
}) => {
106+
let reason = '';
107+
const repoName = repo.full_name!;
108+
109+
const shouldExclude = (() => {
110+
if (!!exclude?.forks && repo.fork) {
111+
reason = `\`exclude.forks\` is true`;
112+
return true;
113+
}
114+
115+
if (!!exclude?.archived && !!repo.archived) {
116+
reason = `\`exclude.archived\` is true`;
117+
return true;
118+
}
119+
120+
if (exclude?.repos) {
121+
if (micromatch.isMatch(repoName, exclude.repos)) {
122+
reason = `\`exclude.repos\` contains ${repoName}`;
123+
return true;
124+
}
125+
}
126+
127+
return false;
128+
})();
129+
130+
if (shouldExclude) {
131+
logger.debug(`Excluding repo ${repoName}. Reason: ${reason}`);
132+
}
133+
134+
return shouldExclude;
135+
}
136+
125137
const getTagsForRepo = async <T>(owner: string, repo: string, api: Api<T>) => {
126138
try {
127139
logger.debug(`Fetching tags for repo ${owner}/${repo}...`);

packages/backend/src/repoCompileUtils.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { GithubConnectionConfig } from '@sourcebot/schemas/v3/github.type';
22
import { getGitHubReposFromConfig } from "./github.js";
33
import { getGitLabReposFromConfig } from "./gitlab.js";
4+
import { getGiteaReposFromConfig } from "./gitea.js";
45
import { Prisma, PrismaClient } from '@sourcebot/db';
56
import { WithRequired } from "./types.js"
67
import { marshalBool } from "./utils.js";
7-
import { GitlabConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
8+
import { GiteaConnectionConfig, GitlabConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
89

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

@@ -102,6 +103,51 @@ export const compileGitlabConfig = async (
102103
},
103104
};
104105

106+
return record;
107+
})
108+
}
109+
110+
export const compileGiteaConfig = async (
111+
config: GiteaConnectionConfig,
112+
connectionId: number,
113+
orgId: number,
114+
db: PrismaClient) => {
115+
116+
const giteaRepos = await getGiteaReposFromConfig(config, orgId, db);
117+
const hostUrl = config.url ?? 'https://gitea.com';
118+
119+
return giteaRepos.map((repo) => {
120+
const repoUrl = `${hostUrl}/${repo.full_name}`;
121+
const cloneUrl = new URL(repo.clone_url!);
122+
123+
const record: RepoData = {
124+
external_id: repo.id!.toString(),
125+
external_codeHostType: 'gitea',
126+
external_codeHostUrl: hostUrl,
127+
cloneUrl: cloneUrl.toString(),
128+
name: repo.full_name!,
129+
isFork: repo.fork!,
130+
isArchived: !!repo.archived,
131+
org: {
132+
connect: {
133+
id: orgId,
134+
},
135+
},
136+
connections: {
137+
create: {
138+
connectionId: connectionId,
139+
}
140+
},
141+
metadata: {
142+
'zoekt.web-url-type': 'gitea',
143+
'zoekt.web-url': repo.html_url!,
144+
'zoekt.name': repo.full_name!,
145+
'zoekt.archived': marshalBool(repo.archived),
146+
'zoekt.fork': marshalBool(repo.fork!),
147+
'zoekt.public': marshalBool(repo.internal === false && repo.private === false),
148+
},
149+
};
150+
105151
return record;
106152
})
107153
}

packages/web/src/actions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ErrorCode } from "@/lib/errorCodes";
99
import { isServiceError } from "@/lib/utils";
1010
import { githubSchema } from "@sourcebot/schemas/v3/github.schema";
1111
import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema";
12+
import { giteaSchema } from "@sourcebot/schemas/v3/gitea.schema";
1213
import { ConnectionConfig } from "@sourcebot/schemas/v3/connection.type";
1314
import { encrypt } from "@sourcebot/crypto"
1415
import { getConnection } from "./data/connection";
@@ -279,6 +280,8 @@ const parseConnectionConfig = (connectionType: string, config: string) => {
279280
return githubSchema;
280281
case "gitlab":
281282
return gitlabSchema;
283+
case 'gitea':
284+
return giteaSchema;
282285
}
283286
})();
284287

packages/web/src/app/connections/[id]/components/configSetting.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import { z } from "zod";
1111
import { ConfigEditor, QuickAction } from "../../components/configEditor";
1212
import { createZodConnectionConfigValidator } from "../../utils";
1313
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type";
14-
import { githubQuickActions, gitlabQuickActions } from "../../quickActions";
14+
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type";
15+
import { githubQuickActions, gitlabQuickActions, giteaQuickActions } from "../../quickActions";
1516
import { Schema } from "ajv";
16-
import { GitLabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
17+
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
1718
import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema";
1819
import { updateConnectionConfigAndScheduleSync } from "@/actions";
1920
import { useToast } from "@/components/hooks/use-toast";
@@ -39,13 +40,21 @@ export const ConfigSetting = (props: ConfigSettingProps) => {
3940
}
4041

4142
if (type === 'gitlab') {
42-
return <ConfigSettingInternal<GitLabConnectionConfig>
43+
return <ConfigSettingInternal<GitlabConnectionConfig>
4344
{...props}
4445
quickActions={gitlabQuickActions}
4546
schema={gitlabSchema}
4647
/>;
4748
}
4849

50+
if (type === 'gitea') {
51+
return <ConfigSettingInternal<GiteaConnectionConfig>
52+
{...props}
53+
quickActions={giteaQuickActions}
54+
schema={githubSchema}
55+
/>;
56+
}
57+
4958
return null;
5059
}
5160

packages/web/src/app/connections/new/[type]/components/connectionCreationForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { z } from "zod";
1818
import { ConfigEditor, QuickActionFn } from "../../../components/configEditor";
1919

2020
interface ConnectionCreationForm<T> {
21-
type: 'github' | 'gitlab';
21+
type: 'github' | 'gitlab' | 'gitea';
2222
defaultValues: {
2323
name: string;
2424
config: string;

packages/web/src/app/connections/new/[type]/page.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
'use client';
22

3-
import { githubQuickActions, gitlabQuickActions } from "../../quickActions";
3+
import { giteaQuickActions, githubQuickActions, gitlabQuickActions } from "../../quickActions";
44
import ConnectionCreationForm from "./components/connectionCreationForm";
5-
import { GitLabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
5+
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
6+
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type";
67
import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema";
78
import { githubSchema } from "@sourcebot/schemas/v3/github.schema";
9+
import { giteaSchema } from "@sourcebot/schemas/v3/gitea.schema";
810
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type";
911
import { useRouter } from "next/navigation";
1012

@@ -22,16 +24,20 @@ export default function NewConnectionPage({
2224
return <GitLabCreationForm />;
2325
}
2426

27+
if (type === 'gitea') {
28+
return <GiteaCreationForm />;
29+
}
30+
2531
router.push('/connections');
2632
}
2733

2834
const GitLabCreationForm = () => {
29-
const defaultConfig: GitLabConnectionConfig = {
35+
const defaultConfig: GitlabConnectionConfig = {
3036
type: 'gitlab',
3137
}
3238

3339
return (
34-
<ConnectionCreationForm<GitLabConnectionConfig>
40+
<ConnectionCreationForm<GitlabConnectionConfig>
3541
type="gitlab"
3642
title="Create a GitLab connection"
3743
defaultValues={{
@@ -61,4 +67,24 @@ const GitHubCreationForm = () => {
6167
quickActions={githubQuickActions}
6268
/>
6369
)
70+
}
71+
72+
const GiteaCreationForm = () => {
73+
const defaultConfig: GiteaConnectionConfig = {
74+
type: 'gitea',
75+
}
76+
77+
return (
78+
<ConnectionCreationForm<GiteaConnectionConfig>
79+
type="gitea"
80+
title="Create a Gitea connection"
81+
defaultValues={{
82+
config: JSON.stringify(defaultConfig, null, 2),
83+
name: 'my-gitea-connection',
84+
}}
85+
schema={giteaSchema}
86+
quickActions={giteaQuickActions}
87+
/>
88+
)
89+
6490
}

0 commit comments

Comments
 (0)