diff --git a/modules/gitea/index.ts b/modules/gitea/index.ts index 3dcafd2..0e99f53 100644 --- a/modules/gitea/index.ts +++ b/modules/gitea/index.ts @@ -61,9 +61,17 @@ import { join } from "node:path"; const mergedRecord = process.env.MESH_GITEA_STATE_DIR ? join(process.env.MESH_GITEA_STATE_DIR, "merged-announced.json") : null; const announced = new Set(); let primedMerges = false; +// since is the moment the watching began: a merge made before it is history, whatever page of the +// forge's listing it surfaces on. Without it, an old merge past the first page — pushed into view +// as newer pull requests were updated — was announced as if it had just happened, and the mesh +// rebuilt everything built from that repository, once per old merge (2026-09-28). +let since = ""; if (mergedRecord && existsSync(mergedRecord)) { try { - for (const sha of JSON.parse(readFileSync(mergedRecord, "utf8")) as string[]) announced.add(sha); + const kept = JSON.parse(readFileSync(mergedRecord, "utf8")) as string[] | { announced: string[]; since: string }; + const list = Array.isArray(kept) ? kept : kept.announced; + for (const sha of list) announced.add(sha); + since = Array.isArray(kept) ? new Date().toISOString() : kept.since; primedMerges = true; } catch { // An unreadable record is treated as no record: prime again rather than re-announce history. @@ -73,7 +81,7 @@ function keepAnnounced(): void { if (!mergedRecord) return; mkdirSync(join(mergedRecord, ".."), { recursive: true }); const tmp = mergedRecord + ".tmp"; - writeFileSync(tmp, JSON.stringify([...announced].slice(-2000))); + writeFileSync(tmp, JSON.stringify({ announced: [...announced].slice(-2000), since })); renameSync(tmp, mergedRecord); } async function pollMerged(client: GiteaClient): Promise { @@ -83,7 +91,10 @@ async function pollMerged(client: GiteaClient): Promise { const pulls = await client.listPullRequests(repo.owner, repo.name, { state: "closed", sort: "recentupdate", limit: "20" }); for (const pull of pulls) { if (!pull.merged || !pull.merge_commit_sha || announced.has(pull.merge_commit_sha)) continue; - if (primedMerges) { + // Announced only if merged since the watching began; recorded either way, so it is looked + // at once. + const fresh = !!pull.merged_at && !!since && pull.merged_at > since; + if (primedMerges && fresh) { await emit("pull.merged", { owner: repo.owner, repo: repo.name, @@ -104,6 +115,7 @@ async function pollMerged(client: GiteaClient): Promise { changed = true; } } + if (!primedMerges) since = new Date().toISOString(); if (!primedMerges || changed) keepAnnounced(); primedMerges = true; }