gitea: mesh-tools writes a user's delivered repository and main's rule stays the operator's: only its merge whitelist gets mesh-tools, read back; what else blocks the mesh's merge is said (issue 1945, the operator's answer 2026-10-26 18:30)
This commit is contained in:
@@ -628,9 +628,8 @@ test("the merging account is given write on each repository the indexer's module
|
||||
await writes.take({ key: "car-hunter-2", op: "put", value: { v: 1, repository: "jschoubben/car-hunter" } });
|
||||
assert.equal(forge.calls.length, from, "a repository already written asks nothing more");
|
||||
const repo = (full: string) => forgeRepos.find((x) => x.full === full)!;
|
||||
assert.equal(repo("jschoubben/car-hunter").collaborators.get(TOOLS_ACCOUNT), "admin", "a user's repository is administered, not only written");
|
||||
assert.deepEqual(userRules.get("jschoubben/car-hunter"), { rule_name: "main", enable_push: false, enable_merge_whitelist: true,
|
||||
merge_whitelist_usernames: [TOOLS_ACCOUNT], enable_status_check: false, status_check_contexts: [], block_admin_merge_override: true });
|
||||
assert.equal(repo("jschoubben/car-hunter").collaborators.get(TOOLS_ACCOUNT), "write", "a user's repository is written, not administered");
|
||||
assert.equal(userRules.has("jschoubben/car-hunter"), false, "a repository with no rule for main is given none");
|
||||
assert.equal(repo("novox/hq").collaborators.size, 0, "the organisation's repository is written through its team, no collaborator");
|
||||
assert.equal((await client.getRepo("jschoubben", "car-hunter")).writes, true);
|
||||
assert.deepEqual(said, [{ type: WRITABLE_EVENT, body: { owner: "jschoubben", repo: "car-hunter", account: TOOLS_ACCOUNT } }]);
|
||||
@@ -658,23 +657,28 @@ test("the merging account is given write on each repository the indexer's module
|
||||
assert.ok(!forge.calls.slice(puts).some((c) => c.startsWith("PUT ")), "no write is asked on a repository no module builds from");
|
||||
assert.equal(repo("jschoubben/dotfiles").collaborators.get(TOOLS_ACCOUNT), "read");
|
||||
repoFaults.refuse.clear();
|
||||
// A stronger rule the operator set (a whitelist of the tools' account alone, two approvals) is kept, never weakened.
|
||||
userRules.set("jschoubben/snake", { rule_name: "main", enable_push: true, enable_merge_whitelist: true,
|
||||
merge_whitelist_usernames: [TOOLS_ACCOUNT], required_approvals: 2, block_admin_merge_override: false });
|
||||
// The operator's rule is left as set (the operator, 2026-10-26: "Write, rule untouched"): only mesh-tools is added to its
|
||||
// merge whitelist; a status the mesh does not set and required approvals are said as blocking the mesh's merge, unchanged.
|
||||
const operators = { rule_name: "main", enable_push: true, enable_merge_whitelist: true, merge_whitelist_usernames: ["jschoubben"],
|
||||
enable_status_check: true, status_check_contexts: ["mesh/merge-gate", "ci/external"], required_approvals: 2, block_admin_merge_override: false };
|
||||
userRules.set("jschoubben/snake", structuredClone(operators));
|
||||
const after = await grantTool.run({ repositories: ["jschoubben/snake"] }) as any;
|
||||
assert.deepEqual(after.granted, ["jschoubben/snake"]);
|
||||
assert.deepEqual(after.main_rule.map((x: any) => [x.repository, x.created, x.rule.enable_push, x.rule.required_approvals, x.rule.block_admin_merge_override]),
|
||||
[["jschoubben/snake", false, false, 2, true]]);
|
||||
assert.equal(repo("jschoubben/snake").collaborators.get(TOOLS_ACCOUNT), "admin");
|
||||
assert.deepEqual(((await unwritableTool.run({})) as any).unwritable, []);
|
||||
// The forge drops a whitelist login that cannot write and still answers 200: main's rule is read back and that fails loud.
|
||||
const admin = GiteaAdmin.fromEnv(env);
|
||||
await assert.rejects(admin.ensureMainRule("jschoubben", "dotfiles"),
|
||||
/main's rule of jschoubben\/dotfiles was written, but the forge holds otherwise: the merge whitelist \[\] leaves out mesh-tools/);
|
||||
userRules.get("jschoubben/snake").enable_status_check = true;
|
||||
userRules.get("jschoubben/snake").enable_push = true;
|
||||
const held = await admin.ensureMainRule("jschoubben", "snake");
|
||||
assert.deepEqual([held.rule.enable_push, held.rule.enable_status_check, held.rule.merge_whitelist_usernames], [false, false, [TOOLS_ACCOUNT]]);
|
||||
assert.equal(repo("jschoubben/snake").collaborators.get(TOOLS_ACCOUNT), "write");
|
||||
assert.deepEqual(userRules.get("jschoubben/snake"), { ...operators, merge_whitelist_usernames: ["jschoubben", TOOLS_ACCOUNT] }, "only the whitelist changed");
|
||||
assert.deepEqual(after.merge_whitelist, [{ repository: "jschoubben/snake", changed: true, listed: ["jschoubben", TOOLS_ACCOUNT] }]);
|
||||
const blockedWhy = "writable, but main's rule blocks the mesh's merge: it requires the status ci/external, which the mesh does not set; " +
|
||||
"it requires 2 approving reviews on the forge, which the gate does not give";
|
||||
assert.deepEqual(after.merge_blocked, [{ repository: "jschoubben/snake", why: blockedWhy }]);
|
||||
const again = await unwritableTool.run({}) as any;
|
||||
assert.deepEqual([again.unwritable, again.merge_blocked], [[], [{ repository: "jschoubben/snake", why: blockedWhy }]]);
|
||||
const writesBefore = forge.calls.filter((c) => /^PATCH .*branch_protections/.test(c)).length;
|
||||
assert.deepEqual(((await grantTool.run({ repositories: ["jschoubben/snake"] })) as any).merge_whitelist[0].changed, false);
|
||||
assert.equal(forge.calls.filter((c) => /^PATCH .*branch_protections/.test(c)).length, writesBefore, "a listed account is not written again");
|
||||
// The forge drops a whitelist login that cannot write and still answers 200: the whitelist is read back and that fails loud.
|
||||
userRules.set("jschoubben/dotfiles", { rule_name: "main", enable_merge_whitelist: true, merge_whitelist_usernames: ["jschoubben"] });
|
||||
await assert.rejects(GiteaAdmin.fromEnv(env).ensureMergerListed("jschoubben", "dotfiles", TOOLS_ACCOUNT),
|
||||
/mesh-tools was added to the merge whitelist of main's rule main of jschoubben\/dotfiles, but the forge holds \[jschoubben\]: it drops a login that cannot write/);
|
||||
userRules.delete("jschoubben/dotfiles");
|
||||
// A module gone from the indexer leaves its repository out of the delivered list.
|
||||
await writes.take({ key: "snake", op: "delete" });
|
||||
|
||||
Reference in New Issue
Block a user