Repository navigation
feat: Forgejo and Gitea pull request hosts - #683
Merged
Merged
Conversation
Settings move from github.hosts to kind-per-host source_control.hosts (legacy input still loads); configure and credential status take neutral per-host settings and status; forge::connect routes each key and host to its kind's implementation. services::forgejo serves Forgejo and Gitea over direct REST with a per-host token (saved, GITEA_TOKEN for GITEA_INSTANCE_URL's server, tea's stored login), branching on /api/v1/version where the two differ. Host terms add FORGEJO and GITEA, and the model-visible text names only the kinds settings configure. Command validation becomes kind-aware (invalid_host).
… on pull request surfaces One row per host with its kind's mark, switch, credential source, one notice, a write-only token editor and Remove host for added hosts; an Add host dialog with a kind picker and the kind's host rule. Open on <host> names the server for Forgejo and Gitea, and Files says when viewed marks are kept by Tcode.
Host-named strings on every surface Forgejo and Gitea reach, with the conflicts and checks pages only where the host has them and an Unsupported rejection for an absent capability. Forgejo and Gitea offer no auto-merge or merge message; line comments offer no reaction or edit; a mergeable:false is a conflict only when read twice at one head. URL hosts must be authorities, the media credential is the pull request's server's, and the files cursor pages by the first page's size. One kind lookup and host rule in core; tools keep the host names they were registered with. Settings join missing tools with or, say Not connected once, and keep a command's URL whole on a phone.
A rate limit's resume time reads ahead (in 5m), and soon once passed. A media redirect keeps the token only on the server's origin. Forgejo's mergeable:false is a conflict once it has held 30 seconds at one head, and the verdicts are bounded. no_credential_title names its host; the credits setting says Forgejo and Gitea use the server's message; the command chip spaces its words as text. The wire note names added, not origin.
Tryanks
marked this pull request as ready for review
October 9, 2026 12:32
Tryanks
enabled auto-merge (squash)
October 9, 2026 12:32
This was referenced Oct 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Step 2 of #643 (A7 of #535): Forgejo and Gitea behind the host boundary, the kind-per-host settings, and Settings → Source Control for any kind.
Evidence
Before: Settings → Source Control listed GitHub hosts only, with a free host field and no validation; a Forgejo or Gitea URL could not be linked.
After:
Synthetic hosts (
git.acme.test:3000/forge,gitea.acme.test) on a fresh profile; the PR page is the scratch repository below, read withGITEA_TOKENfor its server. The conversation shows the thread without Reply/Resolve (Gitea 1.25 has neither); Files shows the Tcode-kept viewed mark (toggling it wroteviewed-marks.jsonand showed "1 of 1 viewed").Live reads through Tcode's host (
forge::connectdriven by a throwaway probe, not committed; counts areforgejo host=… path=…request log lines):refs/pull/N/head, checks Failing), conversation 8 (1 comment + 2 approvals, 2 labels, 2 reviewers, complete), files 2 (2 files with hunks, whole diff), file text 1 (go.mod, 255 lines), watch detail 2 (17 statuses, 1 failing, head sha), activity 3 (3 remarks), action state 4 (behind 2, methods merge/squash/rebase); 22 lines in total.sorton the statuses list (the combined status is read now).Live writes through Tcode's actions on
https://gitea.tryanks.com(Gitea 1.25.5), scratch repoTryanks/tcode-a7-forgejo-check, credential fromtea login helper get(status:source=Cli{tea},added: false): PR #1 opened from a branch withtea, then through Tcode: comment → Applied; review (COMMENT verdict with a line comment on CHECK.md:3; the author may only comment) → Applied; set label (id 71) → Applied; react ♥ → Applied; close → Applied (summary Closed); reopen → Applied (summary Open); update branch → UpToDate; merge squash at the head → Applied (summary Merged, using 1.25'sDofield). Ready-for-review → refused before any write request because the capability is absent (the live run answered Rejected(Invalid); since the review that refusal is Rejected(Unsupported)). The scratch repository was deleted afterwards (tea repos delete).Not delivered because the API cannot do it (no substitute was built):
EditPullRequestOptionhas nodraftfield on codeberg.org, gitea.com or gitea.tryanks.com (/swagger.v1.json); draft is a WIP title prefix. Capabilitydraftis false, so no control shows; the "mark draft/ready" live step is unverified for that reason.merge_messageis false, so the merge sheet hides the option and the server writes its own message.What
teaprovides: a token per server through its git credential helper (tea login helper get, never printing tokens otherwise) and the list of login URLs (tea logins list --output json, no tokens). It cannot choose among several logins for one server (its helper picks), so no account picker is shown for tea.Tests
the_model_reads_the_hosts_settings_configure(GitHub-only linking text is the literal origin/main text; with Forgejo/Gitea configured it namesteaonce and "GitHub or Forgejo or Gitea"),each_kind_names_its_hosts_by_its_own_rule,github_hosts_from_before_kinds_load_as_github_source_control_hosts(literal legacygithub.hostsinput),a_host_is_refused_by_its_kinds_rule(invalid_hostwith the kind's text),a_server_version_says_which_api_it_speaks,urls_and_remotes_find_the_server_by_port_and_mount,statuses_and_mergeability_read_as_the_watch_needs(Source control: other hosts, Forgejo/Gitea, GitLab, Bitbucket, Azure DevOps (A7) #643:warningfails, a non-draft that cannot merge conflicts, a draft stays unknown),line_comments_on_one_line_share_a_thread_named_by_the_first.a_url_naming_another_server_after_userinfo_is_refused(https://gitea.x.com@127.0.0.1:8080/…andhttps://codeberg.org:443@evil.com/…name no server; fails without the authority check),a_conflict_is_a_false_that_holds_at_one_head(amergeable:falseis a conflict only once it has held 30 s at one head, so two back-to-back reads never confirm one; the verdicts are dropped after 24 h and capped at 512),each_host_goes_to_its_kinds_implementation(configured kind beats the name; codeberg.org/gitea.com by name; unknown → GitHub),a_token_goes_only_to_the_server_it_is_for(GITEA_TOKENonly forGITEA_INSTANCE_URL's server; a faketeais asked per host and its token never reaches another server),a_redirect_off_the_servers_origin_drops_the_token(a media redirect keeps the token only on the server's origin: scheme, host and port; another port of the same host gets none),a_time_ahead_reads_in_the_future(a rate limit's resume time reads "in 5m", rounded up; it once read "resumes 5m ago"),a_threads_tools_keep_the_hosts_they_were_registered_with(this test found thathosts_incompared&'staticconsts by address, which fails across crates; it now filtersHostKind::ALL).tests/github.rsandtests/pull_request_reads.rs(set_token(HostKind::Github, …),HostSettings{kind}; the env source now carries its variable,Env{name:"GITHUB_TOKEN"}, which is what the fixture sets); runtimetests.rs(SourceControlHostpatch,source_controlsnapshot),pull_requests_tests.rs(settings setup,linking_instructions(&hosts_in([]))), anddispatch_next_pending(None)wherefalsewas passed. No assertion was loosened.Checks at 2fdf22c (after the second review; origin/main merged at 8ccddf8):
cargo fmt --all --check: cleancargo clippy --workspace --all-targets --locked -- -D warnings: cleancargo nextest run --workspace --locked --no-fail-fast:1111 tests run: 1111 passed, 13 skippedcargo machete: no unused dependenciestcode-runtime pipe::p4b_tests::attachments_mux_uses_host_session_directory_and_returns_identical_bytesat 16.6 s: it waits for the first host event with the 5-second wall-clock deadline inpipe::tests::next_event. It passed alone (1 s), in the runtime crate's run (197/197) and in the final full run; nothing here touches that path. The driver is tracked as Runtime test driver: attachments_mux test busy-polls against a 5 s wall-clock deadline #687 and not changed here.Follow-ups (not in this PR)
meta.reviewers_truncatedandstack.*still say GitHub (not reachable on Forgejo/Gitea pages).own_line_commentreads reviews unpaged.Merge Danger
Door: two-way
Blast Radius: source-control
Wire changes, noted above PROTOCOL_VERSION:
Settings.github→Settings.source_controlwith kinded hosts and per-host status,SettingsPatch::SourceControlHost/RemoveSourceControlHost,SetHostToken/RefreshHostCredentials,PullRequestCapabilities.host_viewed_marksandmerge_message,PullRequestRejection::Unsupported,HostStatus.added(no origin enum). settings.json written by this build no longer carriesgithub; an older build reading it loses the GitHub host switches (they default on), and this build reads the older file. Saved GitHub tokens stay under@github; Forgejo/Gitea tokens go under@forgejo. Unknown hosts still route to GitHub. The model-visible text is unchanged for GitHub-only settings and grows only when a Forgejo/Gitea host is configured. GitHub-only side effects, accepted:is_pull_request_url(a menu hint) now also accepts/pulls/Non any host, andmerges_or_closesalso matchestea pr merge|close. The Forgejo mark (assets/icons/forgejo.svg) is Forgejo's logo (CC BY-SA 4.0) drawn monochrome.Part of #643
Closes #674