Skip to content

Source control: other hosts, Forgejo/Gitea, GitLab, Bitbucket, Azure DevOps (A7) #643

Description

@Tryanks

Summary

Other hosts: Forgejo / Gitea, GitLab, Bitbucket, Azure DevOps, self-hosted included. Phase A7 of #535. Starts only after #638, #532, #639, #640, #641 and #642 are complete: the host trait is extracted from the finished GitHub code as a refactor, one delivery per host.

Decisions (2026-10-09, from the upstream host study)

  • Transport: every host uses direct REST (or GraphQL where the host offers it) with a per-host token resolved like GitHub's (saved token → env token scoped to the configured host → the host CLI's stored token: fj/tea for Forgejo and Gitea, glab for GitLab, az for Azure DevOps; Bitbucket API token with email or Bearer). No read or write goes through a CLI, so none depends on a checkout (upstream's glab and az --detect do). Bitbucket Data Center is out of scope, as upstream.

  • Fix rather than copy upstream's gaps, each recorded as a deviation with its own test: every host reports the head SHA (GitLab sha, Forgejo head.sha, Bitbucket source.commit.hash, Azure lastMergeSourceCommit) so the watch sees a push; the snapshot carries a checks state and an edited-at where the host has them; a mergeability the host never resolves does not keep the watch in the 2-minute "in flight" cadence; Forgejo warning maps to failing, not pending; Azure rejectedByPolicy is not a conflict; the Forgejo branch lookup is bounded; Bitbucket gets a one-read summary.

  • The host trait adds: a neutral error type (including tool or credential missing), a capability set separate from viewer permissions (so an absent capability is never shown as a permission denial), opaque diff cursors (Azure iteration plus file index), opaque reviewer and label ids, viewed marks held by Tcode and staled by file revision for hosts without native marks, per-host URL and reference parsing (including hosts with ports and mount paths), and host-neutral model-visible text (linking instructions, tool descriptions, the is_pull_request_url filter, the merge/close command detection).

  • Reviews on hosts that replay a review as several requests (GitLab, Bitbucket) report Partial on a mid-way failure; GitLab merges pass auto_merge=false unless asked; GitLab update-branch is a rebase; Azure auto-merge is auto-complete; Forgejo auto-merge uses merge_when_checks_succeed; no host offers revert.

  • Checks: GitLab one pipeline check plus its jobs where cheap, Forgejo commit statuses, Bitbucket build statuses, Azure policy evaluations; no host marks required checks, so the watch gate is all checks.

  • UI (design tmp/design/track-a7-ui.md, 2026-10-09): Settings → Source Control becomes one row per host of any kind (kind glyph, authority, enable switch, one status line with the credential source, one notice by priority, a write-only token editor; Bitbucket chooses API token with email or Bearer), "Add host…" with a kind picker and host validation (ports and mount paths for Forgejo/Gitea, ports for self-hosted GitLab and GitHub Enterprise; Bitbucket fixed to bitbucket.org; an Azure host is an organisation, dev.azure.com/org), "Remove host" for user-added hosts; hosts are listed once detected (configured, status-reported, CLI login, env) plus github.com. The settings shape migrates from github.hosts to a kind-per-host source_control.hosts with a literal legacy-input test. Monochrome brand marks are added for GitLab, Bitbucket, Azure DevOps and Forgejo (Gitea shares the Forgejo mark); "Open on " for Forgejo/Gitea and unknown hosts, the product name for the others; "#" for numbers everywhere. An absent capability means no control at all (never a permission reason), with three muted-line exceptions (Azure cannot post comments; a Bitbucket close cannot be reopened; viewed marks kept by Tcode). GitLab labels are in scope; reviewers use opaque host ids.

Progress

  • Step 1, the host boundary extraction: delivered in refactor: extract the pull request host boundary (A7 step 1) #676 (trait Forge in crates/services/src/forge.rs, GitHub behind it; per-PR terms(key), host text aggregated over core's HOSTS; capabilities limited to what the UI reads). The GitHub-named settings types on configure / credential_status and the invalid_github_host validation move to the kind-per-host form in step 2.
  • Step 2, Forgejo / Gitea with the settings migration and the Source Control UI: delivered in feat: Forgejo and Gitea pull request hosts #683 (kind-per-host source_control.hosts with a legacy github.hosts migration; Hosts dispatcher; HostKind rules in core; Forgejo host with live reads on codeberg.org and gitea.com and live writes on a self-hosted Gitea 1.25; capabilities merge_message and host_viewed_marks; PullRequestRejection::Unsupported; Forgejo/Gitea auto-merge off until the API can read a scheduled merge; draft toggle absent (no API)). Follow-ups recorded in the PR body: unpaged review read in own_line_comment, viewed marks for renames and pruning, scope hints and create-token link, origins text, Settings Rejected/RateLimited notices, kind glyphs on badges and rows, Add host from the link dialog.
  • Step 3, GitLab (gitlab.com and self-hosted): delivered in feat: GitLab merge requests as pull requests #690 (REST v4 plus GraphQL where anonymous REST answers 401; token order saved → GITLAB_TOKEN for the server GITLAB_HOST/GITLAB_URI/GL_HOST names → glab per host; gitlab.com listed only once something names it; capabilities update_merge (rebase-only update) and optional snapshot line counts; reviewers via mergeRequestSetReviewers APPEND/REMOVE; relative /uploads/ links rewritten outside code; RebaseStarted toast for an asynchronous rebase; shared forge/{http,verdicts,viewed}). Every write is unverified: no GitLab token or glab on the maintainer's machine. Follow-ups recorded in the PR body: a Partial review keeps posted comments as drafts; fold GitHub's private in_order into forge::http; unapprove; pipeline jobs as checks; approvals as remarks; whether the web upload route accepts a Bearer PAT for private projects.
  • Step 4, Bitbucket Cloud: delivered in feat: Bitbucket Cloud pull requests #696 (REST 2.0 on bitbucket.org only; two credential forms, API token with email (Basic) or access token (Bearer), stored under @bitbucket and @bitbucket-email; BITBUCKET_TOKEN/BITBUCKET_EMAIL; shared forge/{checkout,anchors,revisions}; resolve via comments/{id}/resolve; mergeability from the undocumented /conflicts endpoint degrading to Unknown; Rebase → rebase_fast_forward; merge_message false; a decline cannot be reopened (capability off, close confirmation reads the action state first); anonymous budget 60/h so summary and discovery are kept 10 min without a credential; 429 pause clamped to the window and per credential). Every write and the fork merge-base form are unverified: no Bitbucket token on the maintainer's machine. Follow-ups: Pull requests: the Files tab says "Files 0" before the first files read on hosts without a file count #698 (Files 0 before the first read), Source Control: host-owned token URL, Unverified notice, and a labels capability #699 (host-owned token URL, Unverified notice, labels capability).
  • Step 5, Azure DevOps: delivered in feat: Azure DevOps pull requests #702 (Services only; a host is an organization dev.azure.com/{org}, {org}.visualstudio.com normalised; saved PAT as Basic, AZURE_DEVOPS_EXT_PAT only for AZURE_DEVOPS_ORG, az access token as Bearer when az is on PATH; REST 7.1 with 7.1-preview.1 for statuses, labels, policy evaluations and wit/tags; threads with iteration context and changeTrackingId; hunks built locally from item text (imara-diff, printer bug carried as Carried: imara-diff 0.2.0 UnifiedDiff writes a wrong first-hunk header near the top of a file #703); completion keeps the author's completionOptions; auto-complete via autoCompleteSetBy; votes 10/-5). Anonymous reads verified on a public PowerShell project; every write and every credentialed read are unverified: no PAT or az on the maintainer's machine.

All five steps are merged. Open follow-ups from the host work: #698, #699, #703; the unverified writes on GitLab, Bitbucket and Azure need a live pass once a token for each exists.

Order and reuse

First the trait extraction as a pure refactor (its own PR), then Forgejo / Gitea against both codeberg.org and gitea.com (one upstream adapter labelled "Forgejo / Gitea"; shared ancestry is not proof of equivalence), then GitLab on gitlab.com, Bitbucket Cloud on bitbucket.org, Azure DevOps on dev.azure.com; one PR per step, each with live evidence on the hosted instance. Hosts without a watch fingerprint keep the snapshot-gated watch with the 10-minute backstop, as upstream. Each host keeps its own credential resolution (saved token, env, CLI login) and URL-to-identity validation.

Seams kept clean in A1–A6 so this is a refactor

PullRequestKey is logical identity with an opaque repository locator; GitHub node ids, GraphQL enums, cursors, merge-state strings, REST paths and credential fingerprints stay in services/github; checks, reviews and mergeability normalized with unknown / unsupported / incomplete distinctions; domain-shaped action payloads and results; detail carries the permitted actions and methods per PR; summary, detail, watch projection, paged conversation, files, immutable content and media are distinct reads behind one cache owner; link, watch and settlement ownership in runtime and core; native stack membership optional host data; settings may stay GitHub-shaped until extraction.

Reference

Upstream host study: tmp/t3code-hosts-study.md in the maintainer's checkout (file:line citations per host).

T3 Code: apps/server/src/pullRequest/{Forgejo,GitLab,Bitbucket,AzureDevOps}PullRequestProvider.ts, apps/server/src/sourceControl/*SourceControlProvider.ts, PullRequestProvider.ts.

Activity

  1. Tryanks commented on Oct 11, 2026

    @Tryanks
    OwnerAuthor

    Delivered: #676 (boundary), #683 (Forgejo/Gitea), #690 (GitLab), #696 (Bitbucket Cloud), #702 (Azure DevOps). Follow-ups: #698, #699, #703, and live verification of the writes on GitLab, Bitbucket and Azure once tokens exist.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions