From c7737befb7336ea855cc9a0705fcc6018dbfc8fb Mon Sep 17 00:00:00 2001 From: Michael Kriese <michael.kriese@visualon.de> Date: Fri, 21 Feb 2025 14:47:43 +0100 Subject: [PATCH] feat(gitea/forgejo): set poster server filter for pr cache (#34374) Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com> --- lib/modules/platform/gitea/index.spec.ts | 69 +++++++++++++----------- lib/modules/platform/gitea/index.ts | 34 ++++++++++-- lib/modules/platform/gitea/pr-cache.ts | 29 ++++++---- 3 files changed, 86 insertions(+), 46 deletions(-) diff --git a/lib/modules/platform/gitea/index.spec.ts b/lib/modules/platform/gitea/index.spec.ts index 49c236fe55..f9a52788f0 100644 --- a/lib/modules/platform/gitea/index.spec.ts +++ b/lib/modules/platform/gitea/index.spec.ts @@ -271,7 +271,7 @@ describe('modules/platform/gitea/index', () => { const repoResult = { ...mockRepo, ...repo }; const repository = repoResult.full_name; scope.get(`/repos/${repository}`).reply(200, repoResult); - await gitea.initRepo({ repository, ...config }); + await gitea.initRepo({ repository, ignorePrAuthor: true, ...config }); } describe('initPlatform()', () => { @@ -1160,7 +1160,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1196,7 +1196,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, [ thirdPartyPr, ...mockPRs.map((pr) => ({ @@ -1221,16 +1221,21 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ + state: 'all', + sort: 'recentupdate', + limit: 100, + poster: mockUser.username, + }) .reply(200, mockPRs.slice(0, 2), { // test correct pagination handling, domain should be ignored - link: '<https://domain.test/api/v1/repos/some/repo/pulls?state=all&sort=recentupdate&page=2>; rel="next",<https://domain.test/api/v1/repos/some/repo/pulls?state=all&sort=recentupdate&page=4512>; rel="last"', + link: '<https://domain.test/some/path/api/v1/repos/some/repo/pulls?state=all&sort=recentupdate&limit=50&page=2>; rel="next",<https://domain.test/some/path/api/v1/repos/some/repo/pulls?state=all&sort=recentupdate&page=4512>; rel="last"', }) .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate', page: 2 }) + .query({ state: 'all', sort: 'recentupdate', limit: 50, page: 2 }) .reply(200, mockPRs.slice(2)); await initFakePlatform(scope); - await initFakeRepo(scope); + await initFakeRepo(scope, undefined, { ignorePrAuthor: false }); const res1 = await gitea.getPrList(); const res2 = await gitea.getPrList(); @@ -1242,10 +1247,10 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs.slice(0, 2)) .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 20 }) .reply(200, mockPRs.slice(1)); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1274,7 +1279,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1289,7 +1294,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, []) .get('/repos/some/repo/pulls/1') .reply(200, pr); @@ -1305,7 +1310,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, []) .get('/repos/some/repo/pulls/42') .reply(200); // TODO: 404 should be handled @@ -1321,7 +1326,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, [null]); // TODO: 404 should be handled await initFakePlatform(scope); await initFakeRepo(scope); @@ -1335,7 +1340,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1352,7 +1357,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1372,7 +1377,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1392,7 +1397,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1413,7 +1418,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1435,7 +1440,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1457,7 +1462,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1475,7 +1480,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1613,7 +1618,7 @@ describe('modules/platform/gitea/index', () => { .post('/repos/some/repo/pulls') .reply(409) .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, [mockNewPR]); await initFakePlatform(scope); await initFakeRepo(scope); @@ -1637,7 +1642,7 @@ describe('modules/platform/gitea/index', () => { .post('/repos/some/repo/pulls') .reply(409) .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, [mockNewPR]) .patch('/repos/some/repo/pulls/42') .reply(200); @@ -1890,7 +1895,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs) .patch('/repos/some/repo/pulls/1', { title: 'New Title' }) .reply(200, pr); @@ -1906,7 +1911,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs) .patch('/repos/some/repo/pulls/1', { title: 'New Title', @@ -1929,7 +1934,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs) .patch('/repos/some/repo/pulls/1', { title: 'New Title', @@ -1952,7 +1957,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs) .patch('/repos/some/repo/pulls/3', { title: 'WIP: New Title', @@ -1975,7 +1980,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs) .patch('/repos/some/repo/pulls/1', { title: 'New Title', @@ -2013,7 +2018,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs) .get('/repos/some/repo/labels') .reply(200, mockRepoLabels) @@ -2052,7 +2057,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs) .get('/repos/some/repo/labels') .reply(200, mockRepoLabels) @@ -2807,7 +2812,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); @@ -2821,7 +2826,7 @@ describe('modules/platform/gitea/index', () => { const scope = httpMock .scope('https://gitea.com/api/v1') .get('/repos/some/repo/pulls') - .query({ state: 'all', sort: 'recentupdate' }) + .query({ state: 'all', sort: 'recentupdate', limit: 100 }) .reply(200, mockPRs); await initFakePlatform(scope); await initFakeRepo(scope); diff --git a/lib/modules/platform/gitea/index.ts b/lib/modules/platform/gitea/index.ts index 1c8326c541..c30f508b1a 100644 --- a/lib/modules/platform/gitea/index.ts +++ b/lib/modules/platform/gitea/index.ts @@ -63,6 +63,7 @@ import { } from './utils'; interface GiteaRepoConfig { + ignorePrAuthor: boolean; repository: string; mergeMethod: PRMergeMethod; @@ -263,6 +264,7 @@ const platform: Platform = { cloneSubmodules, cloneSubmodulesFilter, gitUrl, + ignorePrAuthor, }: RepoParams): Promise<RepoResult> { let repo: Repo; @@ -270,6 +272,7 @@ const platform: Platform = { config.repository = repository; config.cloneSubmodules = !!cloneSubmodules; config.cloneSubmodulesFilter = cloneSubmodulesFilter; + config.ignorePrAuthor = !!ignorePrAuthor; // Try to fetch information about repository try { @@ -475,7 +478,12 @@ const platform: Platform = { }, getPrList(): Promise<Pr[]> { - return GiteaPrCache.getPrs(giteaHttp, config.repository, botUserName); + return GiteaPrCache.getPrs( + giteaHttp, + config.repository, + config.ignorePrAuthor, + botUserName, + ); }, async getPr(number: number): Promise<Pr | null> { @@ -491,7 +499,13 @@ const platform: Platform = { // Add pull request to cache for further lookups / queries if (pr) { - await GiteaPrCache.setPr(giteaHttp, config.repository, botUserName, pr); + await GiteaPrCache.setPr( + giteaHttp, + config.repository, + config.ignorePrAuthor, + botUserName, + pr, + ); } } @@ -593,7 +607,13 @@ const platform: Platform = { throw new Error('Can not parse newly created Pull Request'); } - await GiteaPrCache.setPr(giteaHttp, config.repository, botUserName, pr); + await GiteaPrCache.setPr( + giteaHttp, + config.repository, + config.ignorePrAuthor, + botUserName, + pr, + ); return pr; } catch (err) { // When the user manually deletes a branch from Renovate, the PR remains but is no longer linked to any branch. In @@ -687,7 +707,13 @@ const platform: Platform = { ); const pr = toRenovatePR(gpr, botUserName); if (pr) { - await GiteaPrCache.setPr(giteaHttp, config.repository, botUserName, pr); + await GiteaPrCache.setPr( + giteaHttp, + config.repository, + config.ignorePrAuthor, + botUserName, + pr, + ); } }, diff --git a/lib/modules/platform/gitea/pr-cache.ts b/lib/modules/platform/gitea/pr-cache.ts index 7f6268dcdb..00e0e33acb 100644 --- a/lib/modules/platform/gitea/pr-cache.ts +++ b/lib/modules/platform/gitea/pr-cache.ts @@ -17,6 +17,7 @@ export class GiteaPrCache { private constructor( private repo: string, + private readonly ignorePrAuthor: boolean, private author: string | null, ) { const repoCache = getCache(); @@ -44,9 +45,10 @@ export class GiteaPrCache { private static async init( http: GiteaHttp, repo: string, + ignorePrAuthor: boolean, author: string | null, ): Promise<GiteaPrCache> { - const res = new GiteaPrCache(repo, author); + const res = new GiteaPrCache(repo, ignorePrAuthor, author); const isSynced = memCache.get<true | undefined>('gitea-pr-cache-synced'); if (!isSynced) { @@ -64,9 +66,10 @@ export class GiteaPrCache { static async getPrs( http: GiteaHttp, repo: string, + ignorePrAuthor: boolean, author: string, ): Promise<Pr[]> { - const prCache = await GiteaPrCache.init(http, repo, author); + const prCache = await GiteaPrCache.init(http, repo, ignorePrAuthor, author); return prCache.getPrs(); } @@ -78,10 +81,11 @@ export class GiteaPrCache { static async setPr( http: GiteaHttp, repo: string, + ignorePrAuthor: boolean, author: string, item: Pr, ): Promise<void> { - const prCache = await GiteaPrCache.init(http, repo, author); + const prCache = await GiteaPrCache.init(http, repo, ignorePrAuthor, author); prCache.setPr(item); } @@ -126,18 +130,23 @@ export class GiteaPrCache { } private async sync(http: GiteaHttp): Promise<GiteaPrCache> { - const query = getQueryString({ + let query: string | null = getQueryString({ state: 'all', sort: 'recentupdate', + // Fetch 100 PRs on the first run to ensure we have the most recent PRs. + // Gitea / Forgejo will cap appropriate (50 by default, see `MAX_RESPONSE_ITEMS`). + // https://docs.gitea.com/administration/config-cheat-sheet#api-api + // https://forgejo.org/docs/latest/admin/config-cheat-sheet/#api-api + limit: this.items.length ? 20 : 100, + // Supported since Gitea 1.23.0 and Forgejo v10.0.0. + // Will be ignoded by older instances. + ...(this.ignorePrAuthor ? {} : { poster: this.author }), }); - let url: string | undefined = - `${API_PATH}/repos/${this.repo}/pulls?${query}`; - - while (url) { + while (query) { // TODO: use zod, typescript can't infer the type of the response #22198 const res: HttpResponse<(PR | null)[]> = await http.getJsonUnchecked( - url, + `${API_PATH}/repos/${this.repo}/pulls?${query}`, { memCache: false, paginate: false, @@ -150,7 +159,7 @@ export class GiteaPrCache { } const uri = parseUrl(parseLinkHeader(res.headers.link)?.next?.url); - url = uri ? `${uri.pathname}${uri.search}` : undefined; + query = uri ? uri.search : null; } this.updateItems(); -- GitLab