gitea: say that a repository made private later is granted only at the next start or by hand, and let one unreadable permission refuse its repository, not the whole pass
mesh/delivery merged: source.main moved to novox/mesh-catalog@a4f5df797b68
mesh/check being carried to b4a7bb9dea50 on a4f5df797b68

This commit is contained in:
2026-10-26 13:43:46 +01:00
parent 0e7e220d49
commit b4a7bb9dea
3 changed files with 50 additions and 10 deletions
+30 -6
View File
@@ -10,8 +10,11 @@
// - **at the start**, one pass over every repository of every user and organisation;
// - **on the forge's own event** (the system webhook, webhook.ts): `forge.repository.created` and `forge.fork` at
// once, and the first event of any repository this process has not yet seen readable, since the forge may not
// send a repository's creation for every owner, and a repository made private later sends no creation at all.
// Nothing polls for it.
// send a repository's creation for every owner. Nothing polls for it.
//
// **Not caught: a repository made private later.** The forge sends no event for a change of visibility, and a public
// repository is already seen readable by the start's pass, so its events ask nothing. Such a repository is granted at
// the module's next start, or now by `gitea_grant_read`; `gitea_unreadable_repos` shows it meanwhile.
//
// **The grant needs the site administrator's login.** Adding a collaborator asks the repository's administration,
// which on another user's repository only the site administrator holds; the module holds that login for setting the
@@ -76,8 +79,14 @@ export class ReadAll {
const repos = await this.forge.everyRepository();
const out: Unreadable[] = [];
for (const repo of repos) {
const u = await this.judge(repo);
if (u) out.push(u);
try {
const u = await this.judge(repo);
if (u) out.push(u);
} catch (err) {
// A permission the forge would not answer is said for that repository; the rest are still looked at.
out.push({ repository: repo.full_name, permission: "unknown", private: repo.private, archived: repo.archived,
why: `the forge did not answer ${this.account}'s permission on it — ${err instanceof Error ? err.message : String(err)}` });
}
}
return { checked: repos.length, unreadable: out };
}
@@ -97,7 +106,14 @@ export class ReadAll {
} else repos = await this.forge.everyRepository();
const result: Granted = { checked: repos.length, granted: [], refused: [] };
for (const repo of repos) {
const u = await this.judge(repo);
let u: Unreadable | null;
try {
u = await this.judge(repo);
} catch (err) {
// One repository whose permission cannot be read is a refusal of its own, never the end of the pass.
result.refused.push({ repository: repo.full_name, why: `its permission could not be read — ${err instanceof Error ? err.message : String(err)}` });
continue;
}
if (!u) continue;
try {
await this.forge.addReadCollaborator(repo.owner, repo.name, this.account);
@@ -106,7 +122,13 @@ export class ReadAll {
continue;
}
// Read back: the forge's own word that the account reads it now, never the grant's answer alone.
const after = await this.forge.repositoryPermission(repo.owner, repo.name, this.account);
let after: string;
try {
after = await this.forge.repositoryPermission(repo.owner, repo.name, this.account);
} catch (err) {
result.refused.push({ repository: repo.full_name, why: `granted, but its permission could not be read back — ${err instanceof Error ? err.message : String(err)}` });
continue;
}
if (READS.has(after)) {
this.readable.add(repo.full_name);
result.granted.push(repo.full_name);
@@ -136,6 +158,8 @@ export class ReadAll {
return;
}
const made = event.name === "forge.repository.created" || event.name === "forge.fork";
// Seen readable once, its events ask nothing: so a repository made private after that is not caught here (the
// forge says no change of visibility), only at the next start or by gitea_grant_read.
if (!made && this.readable.has(full)) return;
const r = await this.grant([full]);
if (r.granted.length) this.log(`${this.account} was given read on ${full}, on the forge's ${event.name}`);