Skip to content

fix(forge): honor project forge_override for self-hosted forges - #14620

Merged
Byron merged 8 commits into
gitbutlerapp:masterfrom
ameyypawar:fix/14319-forge-override
Oct 15, 2026
Merged

Byron merged 8 commits into
gitbutlerapp:masterfrom
ameyypawar:fix/14319-forge-override

Conversation

@ameyypawar

@ameyypawar ameyypawar commented Jul 5, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #14319. Opening as a draft for review — the direction was confirmed by the maintainer investigation in the issue and by the reporter, so I'd welcome a self-hosted test and any steer on the shape before marking it ready.

Bug

0.20.1 regressed self-hosted GitLab: a project with forge_override = gitlab shows "No forge could be determined" and the GitLab settings section disappears.

Cause

547b873 moved forge resolution into Rust, but but_forge::derive_forge_repo_info(url) never consumed the project's forge_override. For a self-hosted host that isn't a recognized keyword (gitlab.com/gitlab.*) and before an account is configured, it returns None — so forge_info() returns None and ForgeForm.svelte hides the GitLab config (gated on forgeInfo?.name === "gitlab").

Fix

Resolve the forge type in one place, resolve_forge_name, in priority order:

  1. the URL host (github.com, gitlab.*, …),
  2. a configured account whose custom host matches,
  3. the project's saved forge_override — last resort, mirroring pre-0.20.1's if forgeType === "default" && forgeOverride.

A recognized host or a matching account always wins, so a stale override can't mislabel a repo whose forge is already known. Owner/repo still come from the URL's GenericProvider regardless of how the type is resolved, so the override path produces the same ForgeRepoInfo the account path would.

The override (legacy_project.forge_override) is threaded through forge_info, compare_branch_url, and the forge API paths in but-api and the CLI auth path. The pure-URL identity comparison (remote_urls_match) passes None.

Tests

  • resolve_forge_name precedence — the override resolves a self-hosted host; a recognized host and a matching account each win over the override; none → None
  • end-to-end derive_forge_repo_info on a self-hosted URL (owner/repo parsed correctly)
  • build_base_url builds the self-hosted web URL from the override-derived info
  • from_override_str is case/whitespace-insensitive

cargo check --workspace and clippy are clean.

Notes / open questions

  • Host detection is substring-based, so a self-hosted host embedding another forge's domain (e.g. git.azure.company.com) is still detected as that forge and the override won't correct it — a pre-existing limitation, left out of scope here.
  • Account-vs-override ordering: a configured account (which carries credentials) is treated as authoritative over the saved preference. This doesn't affect the reported case (no account configured).
  • I threaded an Option<ForgeName> parameter through derive_forge_repo_info and its callers; happy to switch to a centralized ctx-level helper if that's preferred.

The reporter offered to test a build on their self-hosted instance — that would be the strongest confirmation.


Note

High Risk
Changes forge detection, credential selection, and cache invalidation across desktop and core forge/review paths; mis-resolution or auth matching could break integrations or cause wrong-host API use.

Overview
Restores self-hosted forge detection when a project has a saved forge_override but the remote host is not recognizable on its own (#14319). Rust forge resolution now takes an optional override as a last resort after URL host detection and matching integration accounts, and that override is passed through forge info, compare URLs, reviews/CI, and related API paths.

The forge layer also tightens repository and credential matching: richer ForgeRepoInfo (host/port), explicit same-server/same-repository checks, and resolve_account so live API calls only use accounts that match the repo’s forge and host (with safer remote reuse across protocols).

On desktop, project settings send partial ProjectUpdate payloads instead of full project objects, and RTK Query invalidates forge caches only when the override actually changes—so editing name/description no longer refetches PRs/checks. Forge settings UI stays available when an override is saved even if auto-detection would hide the picker.

Reviewed by Cursor Bugbot for commit 0073388. Bugbot is set up for automated code reviews on this repo. Configure here.

@PetrAltman

Copy link
Copy Markdown

I'm the reporter of #14319. Built this branch on Windows (x86_64-pc-windows-msvc) against my self-hosted GitLab and can confirm the main fix works: the "Configure GitLab integration" section is back in Project Settings and the "No forge could be determined" error is gone. Thanks for this!

One remaining issue that looks related to URL construction, in case it's in scope here:

The "View MR" button on the virtual branch (in the side column) opens GitLab at a truncated host — just gitlab instead of the full gitlab.internal.example.com. The short host actually resolves on my internal network (DNS search domain), but the page then fails with an SSL certificate error, because the cert is issued for the full FQDN gitlab.internal.example.com, not the short name. So the truncation is the root cause; the cert error is just the symptom.

The "Open MR in browser" button in the merge column opens the correct full URL (gitlab.internal.example.com/...) with no cert error.

So the two buttons seem to build the MR URL via different paths: the merge-column one resolves the self-hosted host correctly, but the virtual-branch "View MR" one keeps only the first host label and drops the rest of the domain. Happy to grab logs or test a follow-up change if useful.

(Host redacted — my real instance is an internal *.int.* domain.)

@PetrAltman

Copy link
Copy Markdown

Update / root-cause find, reporter of #14319 here.

I tracked down what actually triggered this on my setup: my git origin remote was pointing at a short host (gitlab) instead of the full FQDN (gitlab.internal.example.com). After fixing origin to use the FQDN, the GitLab integration works correctly even on 0.21 (which doesn't include this PR).

That seems to explain the original error: in 0.20.1 forge resolution became host-based, and my short origin host matched neither the built-in gitlab.* patterns nor my configured account host (gitlab.internal.example.com), so it resolved to no forge — while the saved forge override wasn't consulted as a fallback. Which is exactly what this PR adds.

So the PR still looks valuable as a robustness fallback (honor the override when the origin host can't be matched — mirrors, SSH host aliases, etc.), even though my specific breakage came down to a misconfigured origin.

One related note on the two MR buttons: "View MR" (virtual branch, side column) appears to build its URL from the raw origin host, while "Open MR in browser" (merge column) uses the resolved forge instance URL. That's why they differed for me — with origin fixed to the FQDN, both work now. Might still be worth having "View MR" use the resolved forge URL for consistency, but it's no longer broken on my end.

@ameyypawar

Copy link
Copy Markdown
Contributor Author

Thanks for building it and confirming — glad the GitLab section and forge detection are back on your self-hosted instance.

And thanks for chasing the root cause. It lines up with the fix: when the origin host doesn't match a built-in pattern or a configured account host — as a short or aliased host won't — 0.20.1 resolved to no forge and never consulted the saved override. This PR restores that fallback, so a saved forge preference still resolves in those cases (mirrors, SSH host aliases, short internal hostnames).

The "View MR" host truncation looks like a separate area from this PR (which changes forge resolution, not MR-URL construction) — probably best as its own issue. I'd want to trace both buttons' URL paths before proposing a fix, so it targets the right spot.

@ameyypawar
ameyypawar marked this pull request as ready for review July 9, 2026 02:31
@Byron

Byron commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Thanks @ameyypawar for the fix, and @PetrAltman for taking a look.

Something I'd feel uncomfortable with is that support for the forge override now is explicitly needed in but-api, so it can easily be forgotten as well. It feels like it belongs closer to but-forge. With that said, @estib-vega would know best if this is the right direction.

@alec-chernicki

Copy link
Copy Markdown

Bump on this PR if possible. I believe I'm running into this when attempting to run against a hosted GitHub Enterprise account.

@Byron

Byron commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

@alec-chernicki Could you try this PR locally and see if it solves your problem? And even if not, maybe it's not far from actually working. With that incentive, it should be easier to find a reviewer.
Thanks for your consideration - your help here would be vital as real-world testing isn't an option for the team right now.

@alec-chernicki

Copy link
Copy Markdown

@Byron, I ran this branch against the same GitHub Enterprise account, and I now see all the PR features in the app. This fix looks like it works.

@Byron
Byron force-pushed the fix/14319-forge-override branch from 1753641 to 4262607 Compare October 14, 2026 02:54
@Byron Byron self-assigned this Oct 14, 2026
ameyypawar and others added 2 commits October 14, 2026 16:27
GitButler 0.20.1 collapsed the frontend forge plumbing into Rust
(547b873), but the Rust resolver `derive_forge_repo_info` never consumed
the project's `forge_override`. A self-hosted forge whose host isn't a
recognized keyword (e.g. GitLab on a private host), with no configured
account, therefore resolves to no forge - the backend reports "No forge
could be determined" and the settings UI hides the GitLab configuration,
which is gated on `forgeInfo?.name === "gitlab"`.

Resolve the forge type in one place, `resolve_forge_name`: the URL host
first, then a configured account's custom host, and finally the project's
saved override as a last resort - mirroring pre-0.20.1's
`if forgeType === "default" && forgeOverride`. A recognized host or a
matching account always wins, so a stale override can't mislabel a repo
whose forge is already known. Owner/repo still come from the URL, so the
override path produces the same repository info the account path would.

Thread the override from `legacy_project.forge_override` through
`forge_info`, `compare_branch_url`, and the forge API paths in but-api and
the CLI. Pure-URL identity comparison (`remote_urls_match`) passes `None`.
Looked at each hunk carefully, and investigated the change with the agent.
Most of the code is from tests, which I didn't really look at/rubber-stamped.

Assisted-by: GPT 6.1 Sol
@Byron
Byron force-pushed the fix/14319-forge-override branch from 573acb0 to 802f3b6 Compare October 15, 2026 07:53
@Byron

Byron commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Review

I started with a project whose remote URL was set to https://forge-test.invalid/group/repo.git to make the automatic forge detection fail.

From there I could set the override in the Tauri app (but not in Lite):

Screenshot 2026-10-14 at 16 38 17

Without that, reviews could already not be retrieved, with it, that error would disappear (but I didn't check if the disappearance isn't some sort of caching effect).

Screenshot 2026-10-14 at 16 52 37

The key change in is but_forge::forge_info() which now takes the override as well. From there, whenever the remote url is used, the forge override is present as well.

The security fix

It appears that there was a latent issue with the user account matching, that was now exposed with forge overrides. It could lead to enterprise account tokens being sent to public hosts, as the URL for the remote was rebuilt based on the overridden forge, while the account may have been matched based on the actual host.

Notes for the (additional) Reviewer

  • I moved all tests for public APIs into integration tests as a flyby refactor. This makes the production source files less imposing.
  • The PR got bigger due to the added tests and docs here and there, as well as due to follow-up fixes to the Svelte UI regarding its handling of the forge override. The Lite GUI doesn't have that yet.
  • Keep manually selected forges editable in project settings is a logic-bugfix detected by the review which I ran into myself, and confirmed fixed in the Svelte GUI afterwards.

Addressed Codex Review Comments

  • [P1] Require a host-matching account for override-backed API calls — /Users/byron/dev/github.com/gitbutlerapp/gitbutler.fix-14319-forge-override/crates/but-api/src/legacy/forge.rs:312-315
    For an unrecognized self-hosted GitLab remote with an override and no preferred account, this now permits API calls even when only a GitLab.com or other-instance account is saved. ForgeRepoInfo carries no host, and GitLabClient::from_storage defaults to the first saved account. Consequently, list_reviews queries the wrong server and publish_review_only sends private titles, descriptions, and branch names there, potentially creating an MR in a same-named repository. Require a host-matching account for API actions while retaining override-only display and template support.
  • [P2] Invalidate forge queries when the saved override changes — but-api/src/legacy/forge.rs:135-135
    When choosing a forge for an initially unrecognized repository, forge_info is already cached as null. updateProject invalidates only ReduxTag.Project, whereas ForgeInfoService.get provides ForgeProvider; the project layout keeps that query subscribed and gates integration on it. Saving the override therefore never refetches this newly project-dependent result, leaving integration disabled until a reload or target change. Invalidate affected forge queries when setting or clearing the override, covering the desktop contract required by AGENTS.md:29–31.
  • [P2] Keep manually selected forges editable in project settings — but-forge/src/forge_info.rs:74-74
    After reopening an override-only repository, this returns the manually chosen provider as ForgeInfo.name. ForgeForm.svelte treats that value as automatic detection and renders the selector only for "default", so any recognized override hides the only UI for changing or clearing it. A mistaken GitHub choice for a GitLab host therefore cannot be corrected from settings. Keep the selector available for saved overrides or distinguish detection from override resolution, per AGENTS.md:29–31.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread crates/but-forge/src/lib.rs
Comment thread crates/but-forge/src/lib.rs

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread apps/desktop/src/lib/project/projectEndpoints.ts
Mostly rubber-stamped the additional tests, but looked at the refactoring in detail.

<!-- agent -->
Retain the repository host and port in ForgeRepoInfo and resolve stored
accounts against that server before live API calls. This prevents overrides
and first-account fallback from sending private review data to another forge
instance; a mismatched preferred account no longer falls back.

Preserve override-only display and templates, cache-only reads, and
unsupported-forge behavior. Check that review base and push repositories share a
server, including known API ports and public SSH aliases.

Assisted-by: GPT 6.1 Sol
@Byron
Byron force-pushed the fix/14319-forge-override branch from 99229e4 to 7d15d92 Compare October 15, 2026 09:13

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7d15d92. Configure here.

Comment thread crates/but-api/src/legacy/forge.rs
@Byron
Byron force-pushed the fix/14319-forge-override branch from 7d15d92 to 176c725 Compare October 15, 2026 09:56
Byron added 5 commits October 15, 2026 13:35
<!-- agent -->
Reuse self-hosted review remotes across SSH and HTTPS through the existing
repository compatibility check. Remove PartialEq so callers must choose
repository compatibility explicitly instead of relying on strict API-port
equality.

Assisted-by: GPT 6.1 Sol
This makes source files more readable and less imposing.
It's also now clear that unit-tests are only for non-public APIs.

Assisted-by: GPT 6.1 Sol
<!-- agent -->
The forge override now affects cached forge identity and URLs. Saving or
clearing it must invalidate those queries, along with review, repository,
and CI data, so integration recovers without reopening the project.

Assisted-by: GPT 6.1 Sol
Looked at all production code changes and they made sense,
rubber-stamped tests.

<!-- agent -->
Send partial project updates so unrelated settings no longer carry saved forge
overrides into cache invalidation. Ignore null overrides while preserving
refreshes for explicit set/clear operations.

The description-input regression reproduces the extra fetches; targeted desktop
tests, Svelte checks, ESLint, and Prettier validate the fix.

Assisted-by: GPT 6.1 Sol
<!-- agent -->
An override-resolved forge looked automatically detected after reopening
project settings, hiding the selector needed to correct or clear it.
Keep the selector available for saved overrides and describe that state
without claiming the provider was automatically detected.

Assisted-by: GPT 6.1 Sol
@Byron
Byron force-pushed the fix/14319-forge-override branch from 176c725 to 0073388 Compare October 15, 2026 11:35
@Byron
Byron merged commit 79bfa4f into gitbutlerapp:master Oct 15, 2026
48 of 49 checks passed
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.

GitLab integration broken in 0.20.1 (self-hosted instance

4 participants