From 51c48524889d9a3d108093b901b8e6e903ef0bcc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 20:31:54 +0000 Subject: [PATCH] =?UTF-8?q?Push-Historie:=20Obergrenze=20beim=20Bl=C3=A4tt?= =?UTF-8?q?ern,=20Test=20f=C3=BCr=20HeadCommit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- server/services/pushHistory.js | 14 ++++++++++++- test/pushHistory.test.js | 38 ++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/server/services/pushHistory.js b/server/services/pushHistory.js index cba3d59..e9f9f28 100644 --- a/server/services/pushHistory.js +++ b/server/services/pushHistory.js @@ -2,6 +2,10 @@ import { config } from '../config.js' import { giteaFetch, splitRepo } from './giteaClient.js' const DAY_MS = 24 * 60 * 60 * 1000 +// Sicherheitsnetz fuer listRecentPushes: sollte Gitea "page" ignorieren und immer dieselbe +// volle, noch im Fenster liegende Seite liefern, blaettert die Schleife nicht endlos weiter +// (40 x 50 = 2000 Eintraege). +const MAX_FEED_PAGES = 40 /** * Baut aus Gitea-1.25-Aktivitaeten-Feed-Eintraegen (`GET .../activities/feeds`) Push-Objekte @@ -61,7 +65,9 @@ export function pushesFromFeed(feed, { since } = {}) { * Liest die Push-Historie eines Repos der letzten `days` Tage aus dem Gitea-Aktivitaeten-Feed * (`GET /repos/{owner}/{repo}/activities/feeds`, seitenweise mit 50 pro Seite, neueste zuerst). * Blaettert nur so weit wie noetig: stoppt bei einer leeren Seite, einer Seite mit weniger als - * 50 Eintraegen, oder sobald der aelteste Eintrag einer Seite vor `since` liegt. + * 50 Eintraegen, oder sobald der aelteste Eintrag einer Seite vor `since` liegt. Bricht + * zusaetzlich nach `MAX_FEED_PAGES` Seiten ab (mit einer Konsolen-Warnung), falls keine dieser + * Bedingungen je eintritt. * * @param {string} repoFullName "owner/repo" * @param {{days?: number, now?: Date}} [options] @@ -80,6 +86,12 @@ export async function listRecentPushes(repoFullName, { days = config.gitea.backf collected.push(...items) if (items.length < limit) break if (new Date(items[items.length - 1].created) < since) break + if (page >= MAX_FEED_PAGES) { + console.warn( + `[pushHistory] ${repoFullName}: mehr als ${MAX_FEED_PAGES} Seiten Aktivitäten – ältere Pushes im Zeitraum werden nicht nachgetragen` + ) + break + } page += 1 } diff --git a/test/pushHistory.test.js b/test/pushHistory.test.js index cda78cf..28b436d 100644 --- a/test/pushHistory.test.js +++ b/test/pushHistory.test.js @@ -68,10 +68,26 @@ function manyPushes(base) { return entries } +/** + * Mehr als 40*50 Eintraege, alle nur Minuten auseinander und damit weit innerhalb jedes + * 30-Tage-Fensters - fuer den Test der Blaetter-Obergrenze (die Seite wird nie leer/kurz, + * und der "aeltester Eintrag vor since"-Stopp greift nie). + */ +function packedPushes(base, count) { + const entries = [] + for (let i = 0; i < count; i++) { + entries.push(pushEntry({ sha: `p${i}`, at: new Date(base.getTime() - i * 60000).toISOString() })) + } + return entries +} + before(async () => { fake = await startFakeGitea({ - repos: [{ full_name: 'knso/viel' }], - feeds: { 'knso/viel': manyPushes(now) }, + repos: [{ full_name: 'knso/viel' }, { full_name: 'knso/endlos' }], + feeds: { + 'knso/viel': manyPushes(now), + 'knso/endlos': packedPushes(now, 2005), + }, }) Object.assign(process.env, { GITEA_BASE_URL: fake.baseUrl, @@ -99,6 +115,10 @@ test('HeadCommit bestimmt commitSha', () => { const [p] = pushesFromFeed([pushEntry({ commits: ['c2', 'c1'], head: 'c2' })], { since: new Date(0) }) assert.equal(p.commitSha, 'c2'); assert.equal(p.commits.length, 2) }) +test('HeadCommit hat Vorrang vor dem ersten Commit', () => { + const [p] = pushesFromFeed([pushEntry({ commits: ['c1', 'c2'], head: 'c2' })], { since: new Date(0) }) + assert.equal(p.commitSha, 'c2') +}) test('ohne HeadCommit gilt der erste Commit', () => { const [p] = pushesFromFeed([pushEntry({ commits: ['c2', 'c1'], head: null })], { since: new Date(0) }) assert.equal(p.commitSha, 'c2') @@ -118,3 +138,17 @@ test('listRecentPushes blättert nur so weit wie nötig', async () => { assert.equal(r.length, 90) assert.equal(fake.calls.filter((c) => c.path.includes('/activities/feeds')).length, 2) }) +test('Blättern hat eine Obergrenze', async () => { + // Feed liefert auf jeder Seite 50 volle, aktuelle Einträge - ohne Obergrenze würde + // listRecentPushes endlos weiterblättern. + const originalWarn = console.warn + console.warn = () => {} + let r + try { + r = await listRecentPushes('knso/endlos', { now }) + } finally { + console.warn = originalWarn + } + assert.equal(r.length, 2000) + assert.equal(fake.calls.filter((c) => c.path.includes('/knso/endlos/activities/feeds')).length, 40) +})