Skip to content

feat(bin): add Gitea/Forgejo forge support for PR watch and merge - #3068

Open
chewbakartik wants to merge 1 commit into
kunchenguid:mainfrom
chewbakartik:fm/forgejo-forge-support
Open

chewbakartik wants to merge 1 commit into
kunchenguid:mainfrom
chewbakartik:fm/forgejo-forge-support

Conversation

@chewbakartik

@chewbakartik chewbakartik commented Aug 25, 2026 •

Copy link
Copy Markdown

Intent

Fix Greptile review findings 1 and 2 on the Gitea/Forgejo forge-support pull request #3068 in kunchenguid/firstmate (fork chewbakartik/firstmate, head branch fm/forgejo-forge-support), which blocked the Greptile Review check at 4/5 confidence. The change normalizes the tea login-vs-record endpoint comparison in both bin/fm-pr-merge.sh and the standalone byte-pinned bin/fm-pr-poll.sh: a login configured with its scheme's default port (https :443, http :80) now matches the validated portless PR URL that names the same endpoint, while any non-default port stays significant and never matches; the host is lowercased on both sides. The login-selection section of docs/forge-merge-watch.md is updated to describe endpoint identity, and regression tests cover a default-ported login matching in both the merge suite and the poll security suite. Greptile findings 3 through 7 (foreign --remote override rejection and the closed-list pagination walk) are already fixed in the base head 8f799af and were deliberately left untouched here. The pipeline must push only to the fork branch chewbakartik/firstmate:fm/forgejo-forge-support as a clean fast-forward and attach to the existing open PR #3068; it must not open a new pull request.

What Changed

  • bin/fm-pr-lib.sh parses https://<host>/<owner>/<repo>/pulls/<n> URLs into a new gitea provider identity (host validated with GitLab's arbitrary self-hosted-host rule, owner/repo with GitHub's fixed shape), and bin/fm-pr-check.sh refuses to arm a watch when tea is not on PATH.
  • bin/fm-pr-poll.sh (byte-static watcher) and bin/fm-pr-merge.sh read/merge Gitea/Forgejo pull requests through tea: the poll walks tea pulls list --state closed pages to find the recorded index, and the merge path resolves the one tea login whose endpoint matches the parsed host, calling tea pulls merge with --style squash by default. Both compare login endpoint to the validated host using endpoint identity — a login on its scheme's default port (https :443, http :80) matches the portless URL naming that endpoint, any non-default port stays distinct, and the host is lowercased on both sides.
  • bin/fm-pr-merge.sh extends its override rejection to --login/--remote (and, on Gitea/Forgejo only, the -r/-l short forms tea maps to --repo/--login) so the repository and login always come from the validated URL, not caller-supplied flags.
  • Renamed docs/gitlab-merge-watch.md to docs/forge-merge-watch.md, expanded to cover GitHub, GitLab, and Gitea/Forgejo (including the login endpoint-identity/default-port normalization), and updated README.md, docs/architecture.md, docs/scripts.md, and docs/documentation-audiences.json to match.
  • Added Gitea/Forgejo regression coverage to tests/fm-pr-check-security.test.sh and tests/fm-pr-merge.test.sh, including cases where a default-ported login matches a portless recorded URL.

Risk Assessment

✅ Low: The scoped fix (bin/fm-pr-merge.sh, bin/fm-pr-poll.sh, docs, tests) correctly implements endpoint-identity normalization — traced through default-port (443/80), non-default-port, and case-mismatch cases in both the extracted gitea_endpoint_identity helper and the standalone poll script's duplicated inline logic, both strip only an exact trailing ":443"/":80" suffix tied to the matching scheme so a non-default port is never falsely collapsed; the new tests exercise real behavior (poll/merge output) rather than source text, and the rest of the branch diff corresponds to already-decided or explicitly out-of-scope prior-round work (pagination budget removal, --remote rejection) per the recorded decisions and user intent.

Testing

The fix normalizes tea login-vs-record endpoint comparison (stripping default ports https:443/http:80, lowercasing host) in both bin/fm-pr-merge.sh and bin/fm-pr-poll.sh. Its own added regression tests (one in tests/fm-pr-merge.test.sh, one in tests/fm-pr-check-security.test.sh) exercise exactly the CLI-facing behavior end-to-end — a default-ported tea login now resolves and drives a real merge (tea pulls merge invoked, pr= recorded) and a real poll match (poll reports 'merged') for a portless PR URL. Ran the full merge test file (44/44 pass) and, since the full 200KB security suite exceeded a reasonable local runtime, ran a targeted invocation of just its two relevant cases (both pass). To rule out these being trivially-passing tests, I checked out the pre-fix versions of both scripts and reran the same two new tests: both failed with the exact symptom the intent describes (default-ported login rejected/ambiguous), proving the tests are real regression coverage, then restored the fixed scripts leaving the worktree clean. No test or environment issues found; no findings to report.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

🔧 **Rebase** - 3 issues found → auto-fixed ✅
  • ⚠️ bin/fm-pr-merge.sh - merge conflict rebasing onto origin/main
  • ⚠️ docs/architecture.md - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-pr-merge.test.sh - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-pr-merge.test.sh (all 44 cases pass, including new test_gitea_default_port_login_matches)
  • Focused re-run of tests/fm-pr-check-security.test.sh restricted to test_gitea_login_port_mismatch and test_gitea_default_port_login_matches (both pass)
  • Regression proof: swapped bin/fm-pr-merge.sh and bin/fm-pr-poll.sh back to the pre-fix parent commit (12bfaef) content and re-ran the two new tests — both failed as expected ('did not match a login configured with its https default port' / 'could not resolve exactly one tea login for forge.example'), then restored the fixed scripts and confirmed a clean worktree
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed (2) ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix: {"summary": "no code lint fixes needed; install actionlint to unblock lint check"}
1 warning still open:

  • ⚠️ linter found issues (exit code 1)

🔧 Fix: Install actionlint to $HOME/.local/bin to unblock lint check
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

@greptile-apps

greptile-apps Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Reviews (13): Last reviewed commit: "feat(pr): add Gitea/Forgejo as a fully-s..." | Re-trigger Greptile

Comment thread bin/fm-pr-merge.sh Outdated
Comment thread bin/fm-pr-poll.sh Outdated
Comment thread bin/fm-pr-poll.sh Outdated
Comment thread bin/fm-pr-poll.sh Outdated
Comment thread bin/fm-pr-merge.sh Outdated
@chewbakartik chewbakartik changed the title feat(pr): add Gitea/Forgejo as a fully-supported forge feat(bin): add Gitea/Forgejo pull request support to forge merge/watch scripts Aug 25, 2026
Comment thread bin/fm-pr-poll.sh
Comment thread bin/fm-pr-merge.sh Outdated
Comment thread bin/fm-pr-merge.sh Outdated
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

Full diff reviewed at 8f799af5ed10d5a137bd1ba9a57523b1e37834b5 against main 9a01dea3995c7f80ff4890d6f77142bba32d4ba3. Class: opt-in. Fork workflows for this HEAD are approved.

Not merge-ready:

  • the current login/URL model rejects valid explicitly-default-port endpoints such as https://host:443/... (the URL parser cannot carry a port, while login matching preserves it), so valid Gitea/Forgejo configurations cannot poll or merge;
  • no matching no-mistakes-pipeline-attestation:v1 exists for this HEAD;
  • repository CI is only now starting after workflow approval.

Overlap check: #3044 currently implements fleet-wide GitHub conflict detection, not Gitea merge/watch, but it also changes bin/fm-pr-lib.sh, related tests, and docs in a very large moving branch. This PR remains on the standing overlap hold so the two branches are not landed blindly.

The correctness and attestation blockers are waiting on the author. The overlap hold is a captain/maintainer integration decision and is not waiting on the author.

chewbakartik added a commit to chewbakartik/firstmate that referenced this pull request Aug 27, 2026
Greptile findings 1 and 2 on PR kunchenguid#3068 are two sides of one
normalization. The login-vs-record comparison kept every port, so a
login configured with its scheme's default port (https://...:443,
http://...:80) could never match the validated portless record that
names the same endpoint, so merging or watching a PR on such a login
silently refused.

Endpoint identity now strips the scheme's default port (https :443,
http :80) from both sides of the comparison and lowercases the host,
while any other port is kept, which is the guarantee finding 1
requires: a validated Gitea/Forgejo PR URL never carries a port and
names the default-port endpoint, a default-ported login is that same
endpoint, and a non-default-ported login names a different endpoint
and never matches.

Both copies of the contract change: bin/fm-pr-merge.sh (the merge
path, through gitea_endpoint_identity) and bin/fm-pr-poll.sh (the
standalone poll's inline matching, which cannot source shared code).
The login-selection section of docs/forge-merge-watch.md is updated
to describe endpoint identity, and regression tests cover a
default-ported login matching in both the merge suite and the poll
security suite.
@chewbakartik
chewbakartik force-pushed the fm/forgejo-forge-support branch from 8f799af to 52cada3 Compare August 27, 2026 11:02
@chewbakartik chewbakartik changed the title feat(bin): add Gitea/Forgejo pull request support to forge merge/watch scripts feat(bin): add Gitea/Forgejo forge support for PR watch and merge Aug 27, 2026
Comment thread bin/fm-pr-merge.sh
@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Try greploops.

@chewbakartik
chewbakartik force-pushed the fm/forgejo-forge-support branch from 23c2965 to d5d51f7 Compare August 27, 2026 16:51
Extends the PR watch-and-merge pipeline with a third provider, tagged
"gitea", at parity with GitHub: URL recognition for the
https://<host>/<owner>/<repo>/pulls/<n> shape, merge-poll status via tea,
and autonomous merge via tea pulls merge. Forgejo is a protocol-compatible
fork of Gitea and shares the tea CLI, so one implementation covers both;
tea is used over Forgejo's own fj CLI per the captain's own manual testing.
GitLab remains watch-only, unchanged, since no owner/repository pair can
address its arbitrary-depth namespace.

tea addresses a self-hosted instance through a configured login rather
than through the URL, so both the poll and the merge path resolve the
login whose own host matches the validated record's host, refusing
silently (poll) or loudly (merge) on any ambiguity or absence. tea has no
head-commit field in any output format, so a Gitea/Forgejo task records no
pr_head=, mirroring GitLab's existing precedent.

Renames docs/gitlab-merge-watch.md to docs/forge-merge-watch.md, keeping
the GitLab evidence intact and adding a Gitea/Forgejo section built from
read-only verification against a real self-hosted instance; no live
arm-and-merge cycle against a real pull request was exercised, so the
captain should smoke-test that path before relying on it unattended.
@chewbakartik
chewbakartik force-pushed the fm/forgejo-forge-support branch from d5d51f7 to 8c0df17 Compare August 27, 2026 17:15
@chewbakartik

Copy link
Copy Markdown
Author

RE: Closed-list cap hides old merges (65100fc):

"This thread motivated the pagination redesign, which landed since: the single bounded listing is gone. The walk now pages (bin/fm-pr-poll.sh L193-195, --page N --limit 1000) until the server returns a page with zero data rows (L202-203), exiting early at the target index (L199-201). The per-request bound is the forge's own page cap - a server may clamp below 1000 and fullness is never inferred from the requested limit (comment L179-189) - and a header-only page always terminates (test_gitea_poll_stops_on_header_only_last_page). Repository history growth no longer makes the watch ineffective: the watched index is examined whenever it is within the closed list, however many newer PRs have closed since, and no cap can strand the watcher armed past a merge.

@chewbakartik

Copy link
Copy Markdown
Author

RE: Server cap stops pagination (f373aa2):

"Correct against the revision that inferred page fullness from the requested --limit; that inference is gone. The current walk never treats a short page as end-of-list: termination is judged by data rows (bin/fm-pr-poll.sh L202-203: [ "$rows" -gt 0 ] || break, with tail -n +2 dropping the CSV header), and the design comment (L179-189) states the rule: a server that clamps its own page size below 1000 would look short on every page, so stopping on that would give up after page one. The requested --limit 1000 (L193-195) is only a request; the server's clamp sets the actual page size and the walk continues to the next page until the list is genuinely exhausted (a zero-row page). Pinned by test_gitea_poll_paginates_past_server_clamped_page_size.

@chewbakartik

Copy link
Copy Markdown
Author

RE: Header-only pages never terminate (63feb29):

"Fixed in this revision: tea's CSV always carries the header line, so raw output alone no longer signals exhaustion; a page with zero data rows terminates the walk (bin/fm-pr-poll.sh L202-203), pinned by the zero-data-row break (bin/fm-pr-poll.sh L202-203, after tail -n +2 drops the header) and by test_gitea_poll_stops_on_header_only_last_page.

Comment thread bin/fm-pr-poll.sh
done < <(printf '%s\n' "$raw" | tail -n +2)
[ "$rows" -gt 0 ] || break
page=$((page + 1))
done

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"This comment describes the 50,000-row cutoff from an earlier revision of the walk; that cutoff no longer exists in this head, so the failure it describes cannot occur here. The current walk pages the closed list until it is genuinely exhausted, so a merge can never fall outside the watch window: (a) each request is bounded by the forge's own page cap - bin/fm-pr-poll.sh L193-195 requests --page N --limit 1000 and Gitea/Forgejo may clamp that server-side; (b) the walk exits early the moment the target index appears (L199-201), so it reads only as many pages as the target's recency rank implies; (c) termination is judged by data rows, never by the requested limit (L202-203: tail -n +2 drops tea's CSV header and a zero-row page breaks the loop), so a header-only page - the server's own end-of-list marker - always terminates, and a server that clamps pages below 1000 can never look short (design comment L179-189); (d) any tea failure exits silently and leaves the watcher armed for the next cycle (L195), never emitting a false merged. The bound on this walk is therefore the forge's per-page cap and the list's finite length - not a client row budget - and the guarantees are pinned by tests: test_gitea_poll_paginates_past_row_budget (51,000 rows, target on page 52 - still found), test_gitea_poll_stops_on_header_only_last_page, test_gitea_poll_paginates_past_server_clamped_page_size, test_gitea_poll_paginates_closed_list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants