Abstract git forges and add GitLab support - #816
Merged
Conversation
Decode forge state strings once at the boundary (case-insensitive, with an unknown fallback) instead of comparing raw "MERGED"/"OPEN" literals across views and reducers. Merge readiness now distinguishes a non-blocking "checking" assessment from a real block, so a fresh pull request with unknown mergeability no longer renders as Blocked.
Remote URLs now parse into a host, port, and full namespace path through a single grammar (https, ssh with port, scp-style, git protocol), with the GitHub owner/repo identity derived on top. Remote URL resolution goes through ls-remote --get-url so url.<base>.insteadOf aliases resolve before parsing.
Mechanical rename with no behavior change: the models every view and reducer consumes (pull request, status checks, check state, merge queue entry, workflow run) drop the GitHub prefix, and the GitHub-specific client files move under Clients/Forge/GitHub.
Pull request operations now flow through a ForgeClient witness whose endpoints are domain operations: a summary tier fetching proposals for all queried branches, a detail tier for the selected worktree (structurally unable to carry state or merge timestamps), and forge-neutral actions. The GitHub adapter dispatches to the existing gh-backed client at call time, so batching, retries, and fork matching stay adapter-private and dependency overrides keep applying. Adds the pure per-repository forge resolver (override, authenticated-host membership, known-host fast path, never a default fallback), forge capabilities and vocabulary values seeded with GitHub defaults, and an authenticated-hosts endpoint on the gh client.
Settings gain a Git Forges pane with always-visible GitHub and GitLab rows (CLI presence and per-host auth state), a keyed per-forge enablement map that keeps the legacy GitHub flag decoded and written for downgrade safety, and a per-repository forge override (automatic, GitHub, GitLab, or none) resolved through the repository settings chain. The github deeplink and CLI tokens stay as permanent aliases for the new forges section. The GitLab adapter serves the summary tier with one state-inclusive, recency-ordered merge request list per repository (plus bounded per-branch lookups when a full page may have truncated), matched locally on source branch; merge, close, and mark-ready map to glab with an explicit auto-merge flag and an unsupported error for per-merge rebase. Every invocation names its host explicitly. Forge resolution goes through a registry (override, known-host fast path, authenticated-host membership from each CLI's own config), diff-stat fields are optional and hide when a forge reports none, and CI log and rerun operations surface typed unsupported errors until the detail tier lands.
The selected worktree's proposal is enriched by a per-selection detail fetch whose payload type declares no state or merge timestamp, so it can never influence the merged-worktree transition or the branch watermark. The GitLab adapter serves it from one mr view call, mapping detailed_merge_status through an adapter-owned table: mergeable, conflicts, and requested changes land in the shared vocabulary, hard blocks carry GitLab's prose verbatim, and in-flight or unknown statuses degrade to the non-blocking checking state. The head pipeline maps to a single rollup check, pipelines back the existing latest-run and re-run flows, and rerunning is capability-gated. Palette titles and gates now come from the resolved forge's vocabulary and capabilities (byte-identical for GitHub), the repository's resolved forge is cached in state, and the inspector renders forge-correct nouns, sigils, and empty states.
Availability now unions all enabled forges, so disabling one cannot stall the others; disabling a forge tears down its rows, cancels its in-flight sweeps, and rejects late or unresolved results. Resolution caches remote hosts and authenticated hosts with short TTLs and never caches errors. The GitLab client filters fork merge requests per project, parses both glab config indent styles, encodes query values strictly, and falls back to per-branch queries on full pages. A legacy opt-out keeps newly introduced forges disabled for users who had turned the GitHub integration off.
Ports the rebased #810 flow off the deleted GitHub-only remote resolver: the re-fetch resolves its forge per repository, per-forge teardown cancels the in-flight open-fetches scoped to its repositories, and the two fetch toasts use forge-neutral copy.
Each forge is one row in the agents style: service mark, title, status detail first (signed-in user and hosts bold with divider-split segments, or the error in red with install links inline), the purpose line, and a capability grid derived from ForgeCapabilities, with the enable switch trailing and a Beta pill on GitLab. The merge pickers move to a headless leading section, the shared Beta pill adopts the tinted style, and the pane gains the GitLab tanuki asset and a new sidebar icon.
One GraphQL call per sweep fetches each matched merge request's head pipeline, so unselected rows show CI state like GitHub rows; a failed call degrades to no badge, never a failed sweep. Rollup-only summaries keep detail enrichment while still taking the fresher pipeline. Also probes glab's macOS config location (Library/Application Support) so keyring logins surface in authenticated hosts and the settings status.
Merge in the palette and inspector was gated on confirmed mergeability, which hid it while a forge still reports checking. The gate now hides merge only on a real block. The inspector's action rows also pick up capability gating and forge vocabulary, and its header drops the plain number that duplicated the badge.
Adapters stamp how their forge writes proposal numbers, so sidebar and inspector badges render !12 for merge requests without a registry lookup. GitLab merges read the current head SHA first and pass --sha: namespaces enforcing SHA checks reject sha-less merges outright, and the guard also fails a merge whose branch moved since it was reviewed.
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.
Closes #365
Closes #366
Summary
Abstracts the git forge behind a
ForgeClientwitness so pull request functionality is no longer GitHub-only, and ships GitLab as the first additional forge.glab: sidebar MR badges with head-pipeline CI state, merged-worktree auto-archive, merge (with squash and an explicit head SHA guard for namespaces enforcing SHA checks), close, mark ready, pipeline retry, and a detail tier mappingdetailed_merge_statusinto the shared merge readiness model.!sigils, Pipelines) in the palette, inspector, and badges; capabilities gate unsupported actions (rebase merges and CI log copy on GitLab).insteadOfremote rewrites resolve, and merge actions stay offered until a real block.Settings stay backward and forward compatible: the GitHub toggle keeps its legacy key, per-repo overrides are absent when inherited, and users who had the integration off do not get GitLab enabled silently.
Type of change
ready)How was this tested?
Unit tests across resolution, settings persistence and migration, GitLab decoding and fork matching, the detail tier, and the reducer flows (teardown, stale-sweep rejection, enrichment preservation). Verified live against github.com and gitlab.com repositories: badges, pipeline state, merge on a SHA-enforcing namespace, auto-archive, and the settings pane.
make checkpasses (format + lint)make testpassesChecklist
Closes #above.ready.