feat(pr): support Forgejo pull requests across parse, watch, and merge #3
Loading…
Reference in a new issue
No description provided.
Delete branch "fm/fm-forgejo-pr-support"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What was unsupported
bin/fm-pr-lib.shparsed only GitHub pull-request URLs (/pull/, fixedgithub.com) and GitLab merge-request URLs (/-/merge_requests/, self-hosted). A Forgejo pull request athttps://<host>/<owner>/<repo>/pulls/<n>(note/pulls/plural, self-hosted) failedfm_pr_url_parse, so:bin/fm-pr-check.sh <id> <url>exited2witherror: invalid PR check requestand no merge poll was ever armed.bin/fm-pr-merge.shrefused too: it addressed GitHub by owner/repository throughgh-axiand had an explicit provider check.On a forge whose only delivery target is Forgejo (GitHub is read-only here), that meant no pull request was ever automatically watched, and every merge and CI transition was caught by hand or by heartbeat.
What now works
URL parsing
fm_pr_url_parseaccepts a Forgejo pull request URL and yields the same provider-tagged identity the other forges do:provider=forgejo,host, theowner/repositorypath, andnumber. The host is self-hosted (exactly as GitLab already is) - nothing hardcodesgit.pyr0.dev.github.comandgitlab.comare refused as Forgejo hosts so a typo'd or spoofed URL can never be armed as a Forgejo watch that could never succeed. A Forgejo project is alwaysowner/repository(two segments, no nesting); a deeper path is refused.Merge poll
bin/fm-pr-poll.shgained aforgejobranch that reads the REST API (GET /api/v1/repos/<owner>/<repo>/pulls/<n>) withcurland parses themergedfield withjq. It revalidates host, path, number, and the reconstructed URL from the private sidecar exactly as the GitHub and GitLab branches do, so a doctored sidecar cannot redirect it. It emits exactly onemergedline only when the API reportsmerged == true; every other reading, a missing token, a missing tool, or any API error stays silent - a failed lookup can never be read as a merge. The byte-static security contract is unchanged: task and URL data live only in the privatestate/<id>.pr-pollsidecar and are never interpolated into the check script.Arming
bin/fm-pr-check.sharms a working merge poll for a Forgejo pull request, recordingpr=and the forge'spr_head=(the API'shead.sha) in task metadata like the other providers. It refuses to arm whencurlorjqis missing fromPATH- the one point where a missing tool can be reported instead of silently watching nothing.Merge
bin/fm-pr-merge.shcan now merge a Forgejo pull request through its REST API (POST .../pulls/<n>/merge), since Forgejo has no firstmate-wrapped CLI the wayghwraps GitHub. It defaults toDo=squash(matching the GitHub default), forwards--merge/--rebase/--method=<value>/--delete-branch, and refuses repo-override flags and any unsupportedgh-axi-specific flag rather than silently dropping it. A real API merge failure propagates; it never silently succeeds. A GitLab merge request URL is still refused rather than sent to the wrong forge.How the token is obtained
Forgejo has no CLI that manages its own credentials, so the REST API is read directly. The token (
FORGEJO_API_KEY) is read from the environment at call time, falling back to the captain's documented private file~/.zshenv_local(plainVAR=valuelines, POSIX-shell compatible) when the watcher or another non-interactive invocation inherited no export - which is the common case, since~/.zshenv_localis only sourced by interactive zsh and the variable is not exported.The token is never embedded in a generated script, a metadata file, or anything under
state/. The byte-static poll script inlines the same load contract (it cannot resolve the library path from an arbitrary state directory);fm_pr_forgejo_tokeninbin/fm-pr-lib.showns the canonical contract and is used by the check and merge paths. A missing token fails closed: the merge path refuses with an explicit error, and the poll stays silent (a private repo answers 404 to an unauthenticated request, which would otherwise read as "not merged").What was verified against the live API
Read-only checks against
https://git.pyr0.dev(no pull request was merged, closed, commented on, or otherwise mutated):pyr0/slotrift#22,pyr0/slotrift#24, andpyr0/pyr0-dev-infra#176.fm-pr-check.sharming againstpyr0/slotrift#22recordedpr=andpr_head=5089a7f103ada0445087061821d6a4e932bedd56, published a byte-for-byte static check, and the armed poll emittedmerged.--validatedpath returnedmergedfor the merged pull request and stayed silent for a non-existent number and for a sidecar whose host was swapped.Tests
Colocated with the existing PR suites:
tests/fm-pr-check-security.test.sh: Forgejo canonical/malformed URL parsing, the poll contract (onlymerged == truewakes; curl failure and doctored sidecars stay silent), arming guards for missingcurlandjq, the full merge path (recording,Do=squashdefault, method forwarding,--delete-branch, failure propagation, unsupported-arg refusal, repo-override refusal), and Forgejo merged-poll retirement.tests/fm-pr-merge.test.sh: Forgejo recordspr=/pr_head=before posting, failure propagation, explicit--merge/--method=not overridden,--delete-branchforwarding, unsupported-arg refusal, repo-override refusal, missing-token refusal, and the~/.zshenv_localtoken fallback.The full test suite on the rebased branch shows no regressions attributable to this change. The pr-forge family (both PR test files) and the watcher-wake-lock family pass cleanly in isolation. The failing tests in the full
--allrun are all unrelated to PR tooling: pre-existing on the base (herdr-presentation-e2e, calm-pi-extension, documentation-audiences, session-start), environmental - new tests brought by the upstream merge that require aharnesstool not present in this environment (fm-on, fm-remote-doctor) - or full-run resource flakiness that passes in isolation (fm-bootstrap, fm-pr-check-security, fm-watcher-lock). None exercise Forgejo parsing, the poll, or the merge path.Still unsupported
fm-pr-merge.sh; the refusal is unchanged, since GitLab merge parity is a separate change.merge,squash,rebase, andrebase-merge;gh's--rebasemaps to Forgejorebase. There is nogh-style--rebase-mergeflag, but--method=rebase-mergereaches it.pr_headis recorded for Forgejo when the API and token are available; both consumers already treat it as optional.67c7955a1ae4b5e41d6c