diff --git a/modules/gitea/module.json b/modules/gitea/module.json index 7d95544e4..9af9a0c41 100644 --- a/modules/gitea/module.json +++ b/modules/gitea/module.json @@ -170,6 +170,7 @@ "scope": "mesh", "serves": [ "pull", + "branch", "open-pulls", "tags", "trees", @@ -251,7 +252,7 @@ "type": "string", "trusted": true, "runtime": true, - "default": "{\"publish\":[\"mesh-build-go\",\"mesh-build-node\"],\"publish-part\":[\"mesh-build-go\",\"mesh-build-node\"],\"remove\":[\"mesh-delivery-builder\"],\"versions\":[\"*\"],\"pull\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-gate\"],\"open-pulls\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-indexer\",\"mesh-delivery-gate\"],\"tags\":[\"mesh-delivery-forge-bridge\"],\"trees\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-checker\",\"mesh-delivery-indexer\"],\"file\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-checker\",\"mesh-delivery-indexer\"],\"contains\":[\"mesh-delivery-indexer\",\"mesh-delivery-gate\"],\"statuses\":[\"mesh-delivery-gate\"],\"status\":[\"mesh-delivery-forge-bridge\"],\"comment\":[\"mesh-delivery-forge-bridge\"],\"view\":[\"mesh-delivery-forge-bridge\"],\"note\":[\"mesh-delivery-forge-bridge\"],\"merge-pull\":[\"mesh-delivery-forge-bridge\"]}", + "default": "{\"publish\":[\"mesh-build-go\",\"mesh-build-node\"],\"publish-part\":[\"mesh-build-go\",\"mesh-build-node\"],\"remove\":[\"mesh-delivery-builder\"],\"versions\":[\"*\"],\"pull\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-gate\"],\"branch\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-indexer\"],\"open-pulls\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-indexer\",\"mesh-delivery-gate\"],\"tags\":[\"mesh-delivery-forge-bridge\"],\"trees\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-checker\",\"mesh-delivery-indexer\"],\"file\":[\"mesh-delivery-forge-bridge\",\"mesh-delivery-checker\",\"mesh-delivery-indexer\"],\"contains\":[\"mesh-delivery-indexer\",\"mesh-delivery-gate\"],\"statuses\":[\"mesh-delivery-gate\"],\"status\":[\"mesh-delivery-forge-bridge\"],\"comment\":[\"mesh-delivery-forge-bridge\"],\"view\":[\"mesh-delivery-forge-bridge\"],\"note\":[\"mesh-delivery-forge-bridge\"],\"merge-pull\":[\"mesh-delivery-forge-bridge\"]}", "description": "per verb of the seats the module holds, the callers that may call it (JSON object of verb to callers; * every proved caller, operator the operator's session); each holder reads the lines of its own verbs. The package seats' lines are design 51's (empty is that list for them); the seat git's are design 47's: its reads the parts that declare them in invokes, its acts (status, comment, view, note, merge-pull) the forge bridge alone, and a verb of git with no line is called by no one" } }, diff --git a/modules/gitea/seat.ts b/modules/gitea/seat.ts index 4078216b8..4fac593f2 100644 --- a/modules/gitea/seat.ts +++ b/modules/gitea/seat.ts @@ -20,7 +20,7 @@ import { callerOf, refusal, parseVerbCallers, type VerbCallers } from "@novox/me import { config } from "@novox/mesh-sdk/runtime"; import { seat as GIT, verbs as GIT_VERBS, - type ContainsAnswer, type CommentAnswer, type ErrorKind, type FileAnswer, type MergePullAnswer, type Merging, type NoteAnswer, + type BranchAnswer, type ContainsAnswer, type CommentAnswer, type ErrorKind, type FileAnswer, type MergePullAnswer, type Merging, type NoteAnswer, type OpenPull, type OpenPullsAnswer, type Pull, type PullAnswer, type Status, type StatusAnswer, type StatusesAnswer, type TagsAnswer, type TreesAnswer, type ViewAnswer, } from "@novox/mesh-delivery-contracts-git"; @@ -170,6 +170,14 @@ export function seatTools(gitea: GiteaClient, deps: SeatDeps): ToolDefinition[] const pull = pullOf(full, p); return pull.state === "open" ? { pull, merging: await mergingOf(gitea, owner, repo, p) } : { pull }; }), + verb(GIT_VERBS.branch, "The seat git's branch: a branch's head, and whether a protection rule refuses a direct push and admits only merges.", + async (args): Promise => { + const { owner, repo, full } = repositoryOf(args.repository); + const branch = String(args.branch ?? ""); + // The head first: a branch the forge does not hold is its 404, the kind not-found, never an unprotected branch. + const head = await gitea.branchHead(owner, repo, branch); + return { repository: full, branch, head, protected: pushRefused(await gitea.appliedProtection(owner, repo, branch)) }; + }), verb(GIT_VERBS.openPulls, "The seat git's open-pulls: every open pull request of every repository, with the statuses its base requires and those its head has.", async (): Promise => { const pulls: OpenPull[] = (await gitea.openPulls()).map((o) => ({ @@ -273,6 +281,11 @@ export function seatTools(gitea: GiteaClient, deps: SeatDeps): ToolDefinition[] ]; } +/** Whether a protection rule refuses a direct push and admits only merges: the rule exists and takes no push. */ +export function pushRefused(rule: { enable_push?: boolean } | null): boolean { + return !!rule && rule.enable_push === false; +} + function holds(comments: GiteaComment[], id: string): boolean { return comments.some((c) => String(c.body ?? "").includes(actMarker(id))); } @@ -295,7 +308,7 @@ export async function mergePull(gitea: GiteaClient, args: Record [a.pull.state, a.pull["merge-commit"], a.merging], ["merged", OTHER, undefined]], ["pull", "unknown", { repository: "novox/app", number: 404 }, {}, (a) => [a.pull, a["error-kind"]], [undefined, "not-found"]], ["pull", "forge down", { repository: "novox/app", number: 9 }, { getPullRequest: down }, (a) => [a.pull, a["error-kind"]], [undefined, "failed"]], + ["branch", "a branch a rule guards", { repository: "novox/app", branch: "main" }, { branchHead: async () => SHA, appliedProtection: async () => ({ rule_name: "main", enable_push: false }) }, (a) => [a.head, a.protected], [SHA, true]], + ["branch", "a branch no rule guards", { repository: "novox/app", branch: "scratch" }, { branchHead: async () => SHA, appliedProtection: async () => null }, (a) => [a.head, a.protected], [SHA, false]], + ["branch", "a rule that takes a push", { repository: "novox/app", branch: "main" }, { branchHead: async () => SHA, appliedProtection: async () => ({ rule_name: "main", enable_push: true }) }, (a) => [a.protected], [false]], + ["branch", "no such branch", { repository: "novox/app", branch: "nope" }, { branchHead: gone }, (a) => [a.head, a["error-kind"]], [undefined, "not-found"]], ["open-pulls", "one", {}, {}, (a) => [a.pulls.length, a.pulls[0].required], [1, ["mesh/check"]]], ["open-pulls", "forge down", {}, { openPulls: down }, (a) => [a.pulls, a["error-kind"]], [undefined, "failed"]], ["tags", "a page", { repository: "novox/app", prefix: "go/" }, {}, (a) => [a.tags, a.page, a.bound, a.more], [["go/v0.1.1", "go/v0.1.0"], 1, 50, undefined]],