Announce a pull request opened after its push, and say one nobody checks (hq issue 347)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds gitea → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds gitea → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
The announcer read only the repositories whose update time moved since its last pass, less a minute. Opening, reopening or retargeting a pull request does not move a repository's update time, so a pull request opened more than a minute after its push was never announced, never checked, and nothing said so (mesh-sdk #14, 2026-10-09). - Each pass also looks at every repository holding a pull request updated since the floor, by the forge's own search; every ten minutes, or when the search fails, at every repository. - Every five minutes the forge is asked for every open pull request; one on a base requiring mesh/merge-gate whose head has had none for ten minutes is said as the forge failing that pull request's merge check (a provider standing, ADR 0224), which the controller keeps as a condition, and is recovered when a check arrives or it closes. - The client refuses a pull request or issue number that is not one before asking the forge (a misnamed tool argument sent pulls/NaN).
This commit is contained in:
@@ -194,6 +194,7 @@ export class GiteaClient {
|
||||
}
|
||||
|
||||
async getIssue(owner: string, repo: string, index: number): Promise<GiteaIssue> {
|
||||
indexOf(index);
|
||||
return GiteaClient.mapIssue(await this.request<any>(`/repos/${owner}/${repo}/issues/${index}`));
|
||||
}
|
||||
|
||||
@@ -209,6 +210,7 @@ export class GiteaClient {
|
||||
|
||||
/** Patch an issue's state — the one edit the close tool needs. */
|
||||
async setIssueState(owner: string, repo: string, index: number, state: "open" | "closed"): Promise<GiteaIssue> {
|
||||
indexOf(index);
|
||||
return GiteaClient.mapIssue(
|
||||
await this.request<any>(`/repos/${owner}/${repo}/issues/${index}`, {
|
||||
method: "PATCH",
|
||||
@@ -218,6 +220,7 @@ export class GiteaClient {
|
||||
}
|
||||
|
||||
async addComment(owner: string, repo: string, index: number, body: string): Promise<{ id: number; html_url: string }> {
|
||||
indexOf(index);
|
||||
const c = await this.request<any>(`/repos/${owner}/${repo}/issues/${index}/comments`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ body }),
|
||||
@@ -257,7 +260,28 @@ export class GiteaClient {
|
||||
return (prs ?? []).map(GiteaClient.mapPull);
|
||||
}
|
||||
|
||||
/** The open pull requests of every repository the mesh's account sees, updated since a moment when one is given,
|
||||
* page by page (novox/hq issue 347): each with its repository and its own update time. */
|
||||
async searchOpenPulls(since?: string): Promise<{ owner: string; name: string; number: number; updated_at?: string }[]> {
|
||||
const out: { owner: string; name: string; number: number; updated_at?: string }[] = [];
|
||||
for (let page = 1; page <= 20; page++) {
|
||||
const qs = new URLSearchParams({ type: "pulls", state: "open", limit: "50", page: String(page) });
|
||||
if (since) qs.set("since", since);
|
||||
const found = await this.request<any[]>(`/repos/issues/search?${qs}`);
|
||||
for (const i of found ?? []) {
|
||||
const r = i?.repository ?? {};
|
||||
const [fo, fn] = String(r.full_name ?? "").split("/");
|
||||
const owner = String(r.owner ?? fo ?? "");
|
||||
const name = String(r.name ?? fn ?? "");
|
||||
if (owner && name && i?.number) out.push({ owner, name, number: Number(i.number), updated_at: i.updated_at });
|
||||
}
|
||||
if (!found || found.length < 50) break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async getPullRequest(owner: string, repo: string, index: number): Promise<GiteaPull> {
|
||||
indexOf(index);
|
||||
return GiteaClient.mapPull(await this.request<any>(`/repos/${owner}/${repo}/pulls/${index}`));
|
||||
}
|
||||
|
||||
@@ -275,6 +299,7 @@ export class GiteaClient {
|
||||
* deleted is gone from its source, and the mesh forgets it rather than asking its build (novox/hq ADR
|
||||
* 0236). The forge says `deleted`; `removed` is read the same. */
|
||||
async listPullFiles(owner: string, repo: string, index: number, most = 3000): Promise<{ paths: string[]; removed: string[]; truncated: boolean }> {
|
||||
indexOf(index);
|
||||
const paths: string[] = [];
|
||||
const removed: string[] = [];
|
||||
let pageSize = 0;
|
||||
@@ -408,6 +433,7 @@ export class GiteaClient {
|
||||
/** Close or reopen a pull request without merging it. A pull request is an issue to the forge's
|
||||
* state machine, and the pulls endpoint takes the same `state`. */
|
||||
async setPullState(owner: string, repo: string, index: number, state: "open" | "closed"): Promise<GiteaPull> {
|
||||
indexOf(index);
|
||||
return GiteaClient.mapPull(
|
||||
await this.request<any>(`/repos/${owner}/${repo}/pulls/${index}`, { method: "PATCH", body: JSON.stringify({ state }) }),
|
||||
);
|
||||
@@ -415,6 +441,7 @@ export class GiteaClient {
|
||||
|
||||
/** Change a pull request's title or body; a field left undefined is left alone. */
|
||||
async updatePullRequest(owner: string, repo: string, index: number, data: { title?: string; body?: string }): Promise<GiteaPull> {
|
||||
indexOf(index);
|
||||
return GiteaClient.mapPull(
|
||||
await this.request<any>(`/repos/${owner}/${repo}/pulls/${index}`, { method: "PATCH", body: JSON.stringify(data) }),
|
||||
);
|
||||
@@ -422,11 +449,13 @@ export class GiteaClient {
|
||||
|
||||
/** The unified diff of a pull request, as text. */
|
||||
async pullDiff(owner: string, repo: string, index: number): Promise<string> {
|
||||
indexOf(index);
|
||||
return this.requestText(`/repos/${owner}/${repo}/pulls/${index}.diff`);
|
||||
}
|
||||
|
||||
/** Every comment on an issue or pull request, oldest first. */
|
||||
async listComments(owner: string, repo: string, index: number): Promise<GiteaComment[]> {
|
||||
indexOf(index);
|
||||
const raw = await this.request<any[]>(`/repos/${owner}/${repo}/issues/${index}/comments`);
|
||||
return (raw ?? []).map((c) => ({
|
||||
id: Number(c?.id ?? 0),
|
||||
@@ -464,6 +493,7 @@ export class GiteaClient {
|
||||
}
|
||||
|
||||
async mergePullRequest(owner: string, repo: string, index: number, method = "merge", deleteBranch = false): Promise<void> {
|
||||
indexOf(index);
|
||||
await this.request(`/repos/${owner}/${repo}/pulls/${index}/merge`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ Do: method, delete_branch_after_merge: deleteBranch }),
|
||||
@@ -729,6 +759,16 @@ export class GiteaAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/** A pull request's or issue's number, refused unless it is one: a tool asked with a misnamed argument hands over
|
||||
* `NaN`, and the forge was asked about `pulls/NaN` (novox/hq issue 347). */
|
||||
export function indexOf(index: number): number {
|
||||
if (!Number.isInteger(index) || index <= 0) {
|
||||
throw new Error(`a pull request or issue number is a whole number above 0, and ${String(index)} was given — ` +
|
||||
"is the argument named \`number\`? Nothing was asked of the forge");
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/** The repositories that moved at or after a moment: every one when there is no moment yet, and one whose
|
||||
* update time is not known, so a forge that does not say is asked as before (novox/hq issue 250). */
|
||||
export function movedSince(repos: GiteaRepo[], floor: string): GiteaRepo[] {
|
||||
|
||||
Reference in New Issue
Block a user