diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md index 5dcbf4f77d6038..52fbdd607b6c92 100644 --- a/docs/usage/self-hosted-configuration.md +++ b/docs/usage/self-hosted-configuration.md @@ -213,6 +213,11 @@ For example: } ``` + +!!! note + On Gitea/Forgejo, you can't use `autodiscoverTopics` together with `autodiscoverNamespaces` because both platforms do not support this. + Topics are preferred and `autodiscoverNamespaces` will be ignored when you configure `autodiscoverTopics` on Gitea/Forgejo. + ## autodiscoverProjects You can use this option to filter the list of autodiscovered repositories by project names. diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index d5e9c2547cb7a3..d924c13b359e9f 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -897,7 +897,7 @@ const options: RenovateOptions[] = [ subType: 'string', default: null, globalOnly: true, - supportedPlatforms: ['gitlab'], + supportedPlatforms: ['gitea', 'gitlab'], }, { name: 'autodiscoverProjects', diff --git a/lib/modules/platform/gitea/gitea-helper.spec.ts b/lib/modules/platform/gitea/gitea-helper.spec.ts index 28bcf498247421..9ee88efbffe430 100644 --- a/lib/modules/platform/gitea/gitea-helper.spec.ts +++ b/lib/modules/platform/gitea/gitea-helper.spec.ts @@ -22,6 +22,7 @@ import { getRepoLabels, getVersion, mergePR, + orgListRepos, requestPrReviewers, searchIssues, searchRepos, @@ -250,6 +251,15 @@ describe('modules/platform/gitea/gitea-helper', () => { }); }); + describe('orgListRepos', () => { + it('should call /api/v1/orgs/[organization]/repos endpoint', async () => { + httpMock.scope(baseUrl).get('/orgs/some/repos').reply(200, mockRepo); + + const res = await orgListRepos('some'); + expect(res).toEqual(mockRepo); + }); + }); + describe('getRepo', () => { it('should call /api/v1/repos/[repo] endpoint', async () => { httpMock diff --git a/lib/modules/platform/gitea/gitea-helper.ts b/lib/modules/platform/gitea/gitea-helper.ts index 3b25b850efeffc..fffa9dc5d13106 100644 --- a/lib/modules/platform/gitea/gitea-helper.ts +++ b/lib/modules/platform/gitea/gitea-helper.ts @@ -75,6 +75,19 @@ export async function searchRepos( return res.body.data; } +export async function orgListRepos( + organization: string, + options?: GiteaHttpOptions, +): Promise { + const url = `${API_PATH}/orgs/${organization}/repos`; + const res = await giteaHttp.getJson(url, { + ...options, + paginate: true, + }); + + return res.body; +} + export async function getRepo( repoPath: string, options?: GiteaHttpOptions, diff --git a/lib/modules/platform/gitea/index.spec.ts b/lib/modules/platform/gitea/index.spec.ts index 2a8de9e8c40bad..30fc9c6fab8de9 100644 --- a/lib/modules/platform/gitea/index.spec.ts +++ b/lib/modules/platform/gitea/index.spec.ts @@ -72,6 +72,12 @@ describe('modules/platform/gitea/index', () => { const mockTopicRepos: Repo[] = [partial({ full_name: 'a/b' })]; + const mockNamespaceRepos: Repo[] = [ + partial({ full_name: 'org1/repo' }), + partial({ full_name: 'org2/repo' }), + partial({ full_name: 'org2/repo2', archived: true }), + ]; + const mockPRs: MockPr[] = [ partial({ number: 1, @@ -390,6 +396,20 @@ describe('modules/platform/gitea/index', () => { expect(repos).toEqual(['a/b']); }); + it('should query the organization endpoint for each namespace', async () => { + const scope = httpMock.scope('https://gitea.com/api/v1'); + + scope.get('/orgs/org1/repos').reply(200, mockNamespaceRepos); + scope.get('/orgs/org2/repos').reply(200, mockNamespaceRepos); + + await initFakePlatform(scope); + + const repos = await gitea.getRepos({ + namespaces: ['org1', 'org2'], + }); + expect(repos).toEqual(['org1/repo', 'org2/repo']); + }); + it('Sorts repos', async () => { process.env.RENOVATE_X_AUTODISCOVER_REPO_SORT = 'updated'; process.env.RENOVATE_X_AUTODISCOVER_REPO_ORDER = 'desc'; diff --git a/lib/modules/platform/gitea/index.ts b/lib/modules/platform/gitea/index.ts index dd03749ca263f5..4482eb1b9db37b 100644 --- a/lib/modules/platform/gitea/index.ts +++ b/lib/modules/platform/gitea/index.ts @@ -319,12 +319,28 @@ const platform: Platform = { async getRepos(config?: AutodiscoverConfig): Promise { logger.debug('Auto-discovering Gitea repositories'); try { - if (!config?.topics) { + if (config?.topics) { + logger.debug({ topics: config.topics }, 'Auto-discovering by topics'); + const repos = await map(config.topics, fetchRepositories); + return deduplicateArray(repos.flat()); + } else if (config?.namespaces) { + logger.debug( + { namespaces: config.namespaces }, + 'Auto-discovering by organization', + ); + const repos = await map( + config.namespaces, + async (organization: string) => { + const orgRepos = await helper.orgListRepos(organization); + return orgRepos + .filter((r) => !r.mirror && !r.archived) + .map((r) => r.full_name); + }, + ); + return deduplicateArray(repos.flat()); + } else { return await fetchRepositories(); } - - const repos = await map(config.topics, fetchRepositories); - return deduplicateArray(repos.flat()); } catch (err) { logger.error({ err }, 'Gitea getRepos() error'); throw err;