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 * im selben Zuschnitt wie der Push-Webhook (siehe `server/routes/webhook/gitea.js` und * `createGitPushWorksheet`). Nur `op_type === 'commit_repo'`-Eintraege ab `since` mit * mindestens einem Commit zaehlen. Der erste Push auf einen neuen Branch (Eintrag mit leerem * `content`) und andere Aktivitaeten (z. B. `create_repo`) werden uebersprungen; ein nicht * parsbarer `content` ebenso. Ergebnis aufsteigend nach `pushedAt` sortiert (aelteste * zuerst), unabhaengig von der Reihenfolge im Feed. * * @param {object[]} feed Feed-Eintraege wie von Gitea geliefert. * @param {{since: Date}} options * @returns {Array<{branch: string, commitSha: string, pushedAt: Date, * pusher: {login: string, username: string, full_name: string}, * commits: Array<{id: string, message: string, author: {name: string}}>}>} */ export function pushesFromFeed(feed, { since } = {}) { const pushes = [] for (const entry of feed || []) { if (entry?.op_type !== 'commit_repo') continue if (!(new Date(entry.created) >= since)) continue let parsed try { parsed = JSON.parse(entry.content) } catch { continue } const rawCommits = parsed?.Commits if (!Array.isArray(rawCommits) || rawCommits.length === 0) continue const actUser = entry.act_user || {} pushes.push({ branch: String(entry.ref_name || '').replace('refs/heads/', ''), commitSha: parsed.HeadCommit?.Sha1 || rawCommits[0].Sha1, pushedAt: new Date(entry.created), pusher: { login: actUser.login, username: actUser.login, full_name: actUser.full_name || '', }, commits: rawCommits.map((c) => ({ id: c.Sha1, message: c.Message, author: { name: c.AuthorName }, })), }) } return pushes.sort((a, b) => a.pushedAt - b.pushedAt) } /** * 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. 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] * @returns {Promise>} */ export async function listRecentPushes(repoFullName, { days = config.gitea.backfillDays, now = new Date() } = {}) { const [owner, repo] = splitRepo(repoFullName) const since = new Date(now - days * DAY_MS) const limit = 50 const collected = [] let page = 1 while (true) { const items = await giteaFetch(`/repos/${owner}/${repo}/activities/feeds?limit=${limit}&page=${page}`) if (!Array.isArray(items) || items.length === 0) break 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 } return pushesFromFeed(collected, { since }) }