From 59b4bd086af2f00a8f29753306b0727beaed821b Mon Sep 17 00:00:00 2001 From: Tony <97451137+TonybynMp4@users.noreply.github.com> Date: Wed, 11 Nov 2026 21:59:15 +0100 Subject: [PATCH] fix(gitlab): merge request calls use the bound remote's host With GitLab remotes on two hosts in one checkout, the provider received the bound remote's context but never passed its host to glab, so glab could look up, create or check out a merge request on the other host. Merge request and default branch calls now set GITLAB_HOST from that context. Co-Authored-By: Claude Opus 5.5 --- .../src/server/GitLabCli.ts | 11 ++++ .../GitLabSourceControlProvider.test.ts | 55 +++++++++++++++++++ .../src/server/GitLabSourceControlProvider.ts | 11 +++- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/packages/source-control-gitlab/src/server/GitLabCli.ts b/packages/source-control-gitlab/src/server/GitLabCli.ts index 7690304897af4..c51ae52ffd59e 100644 --- a/packages/source-control-gitlab/src/server/GitLabCli.ts +++ b/packages/source-control-gitlab/src/server/GitLabCli.ts @@ -277,6 +277,7 @@ export class GitLabCli extends Context.Service< readonly listMergeRequests: (input: { readonly cwd: string; + readonly host?: string | undefined; readonly headSelector: string; readonly source?: SourceControlProvider.SourceControlRefSelector; readonly state: "open" | "closed" | "merged" | "all"; @@ -285,6 +286,7 @@ export class GitLabCli extends Context.Service< readonly getMergeRequest: (input: { readonly cwd: string; + readonly host?: string | undefined; readonly reference: string; }) => Effect.Effect; @@ -301,6 +303,7 @@ export class GitLabCli extends Context.Service< readonly createMergeRequest: (input: { readonly cwd: string; + readonly host?: string | undefined; readonly baseBranch: string; readonly headSelector: string; readonly source?: SourceControlProvider.SourceControlRefSelector; @@ -311,10 +314,12 @@ export class GitLabCli extends Context.Service< readonly getDefaultBranch: (input: { readonly cwd: string; + readonly host?: string | undefined; }) => Effect.Effect; readonly checkoutMergeRequest: (input: { readonly cwd: string; + readonly host?: string | undefined; readonly reference: string; readonly force?: boolean; }) => Effect.Effect; @@ -460,6 +465,7 @@ export const make = Effect.gen(function* () { const executeMergeRequest = (input: { readonly cwd: string; + readonly host?: string | undefined; readonly reference: string; readonly args: ReadonlyArray; }) => @@ -480,6 +486,7 @@ export const make = Effect.gen(function* () { listMergeRequests: Effect.fn("GitLabCli.listMergeRequests")(function (input) { return execute({ cwd: input.cwd, + host: input.host, args: [ "mr", "list", @@ -518,6 +525,7 @@ export const make = Effect.gen(function* () { getMergeRequest: Effect.fn("GitLabCli.getMergeRequest")(function (input) { return executeMergeRequest({ cwd: input.cwd, + host: input.host, reference: input.reference, args: ["mr", "view", "--output", "json", "--", input.reference], }).pipe( @@ -635,6 +643,7 @@ export const make = Effect.gen(function* () { const sourceProject = sourceProjectIdentifier(input.source); return execute({ cwd: input.cwd, + host: input.host, args: [ "api", "--method", @@ -655,6 +664,7 @@ export const make = Effect.gen(function* () { getDefaultBranch: Effect.fn("GitLabCli.getDefaultBranch")(function (input) { return execute({ cwd: input.cwd, + host: input.host, args: ["api", "projects/:fullpath"], }).pipe( Effect.map((result) => result.stdout.trim()), @@ -677,6 +687,7 @@ export const make = Effect.gen(function* () { checkoutMergeRequest: Effect.fn("GitLabCli.checkoutMergeRequest")(function (input) { return executeMergeRequest({ cwd: input.cwd, + host: input.host, reference: input.reference, args: ["mr", "checkout", "--", input.reference], }).pipe(Effect.asVoid); diff --git a/packages/source-control-gitlab/src/server/GitLabSourceControlProvider.test.ts b/packages/source-control-gitlab/src/server/GitLabSourceControlProvider.test.ts index 6ef52973ffe70..b039ee87108c6 100644 --- a/packages/source-control-gitlab/src/server/GitLabSourceControlProvider.test.ts +++ b/packages/source-control-gitlab/src/server/GitLabSourceControlProvider.test.ts @@ -281,3 +281,58 @@ it.effect.each(["read", "decode"] as const)( else assert.propertyVal(error.cause, "_tag", "SchemaError"); }), ); + +it.effect("sends merge request calls to the bound remote's host", () => + Effect.gen(function* () { + const hosts: Array = []; + const provider = yield* GitLabSourceControlProvider.make.pipe( + Effect.provide( + GitLabCli.layer.pipe( + Layer.provideMerge( + TestSourceControlHost.layer({ + process: { + run: (input) => { + hosts.push(input.env?.GITLAB_HOST); + return Effect.succeed( + processOutput(input.args[0] === "mr" ? "[]" : '{"default_branch":"main"}'), + ); + }, + }, + }), + ), + ), + ), + ); + const context = { + provider: { + kind: SourceControlProviderKind.make("gitlab"), + name: "GitLab Self-Hosted", + baseUrl: "https://gitlab.example.test", + }, + remoteName: "work", + remoteUrl: "git@gitlab.example.test:group/project.git", + }; + + yield* provider.listChangeRequests({ + cwd: "/repo", + context, + headSelector: "feature", + state: "open", + }); + yield* provider.getDefaultBranch({ cwd: "/repo", context }); + yield* provider.createChangeRequest({ + cwd: "/repo", + context, + baseRefName: "main", + headSelector: "feature", + title: "Feature", + bodyFile: "/tmp/body.md", + }); + + assert.deepStrictEqual(hosts, [ + "gitlab.example.test", + "gitlab.example.test", + "gitlab.example.test", + ]); + }), +); diff --git a/packages/source-control-gitlab/src/server/GitLabSourceControlProvider.ts b/packages/source-control-gitlab/src/server/GitLabSourceControlProvider.ts index 9f599794057fc..782e5e8edb491 100644 --- a/packages/source-control-gitlab/src/server/GitLabSourceControlProvider.ts +++ b/packages/source-control-gitlab/src/server/GitLabSourceControlProvider.ts @@ -166,6 +166,9 @@ export const discovery = { "Install the GitLab command-line tool (`glab`) from https://gitlab.com/gitlab-org/cli or your package manager (for example `brew install glab`).", } satisfies SourceControlCliDiscoverySpec; +const contextHost = (context: SourceControlProvider.SourceControlProviderContext | undefined) => + context === undefined ? undefined : new URL(context.provider.baseUrl).host; + export const make = Effect.gen(function* () { const gitlab = yield* GitLabCli.GitLabCli; const { git } = yield* SourceControlHost.SourceControlHost; @@ -234,6 +237,7 @@ export const make = Effect.gen(function* () { return gitlab .listMergeRequests({ cwd: input.cwd, + host: contextHost(input.context), headSelector: input.headSelector, ...(source ? { source } : {}), state: input.state, @@ -259,7 +263,7 @@ export const make = Effect.gen(function* () { }, ), getChangeRequest: Effect.fn("GitLabSourceControlProvider.getChangeRequest")(function (input) { - return gitlab.getMergeRequest(input).pipe( + return gitlab.getMergeRequest({ ...input, host: contextHost(input.context) }).pipe( Effect.map(toChangeRequest), Effect.mapError( (error) => @@ -283,6 +287,7 @@ export const make = Effect.gen(function* () { return gitlab .createMergeRequest({ cwd: input.cwd, + host: contextHost(input.context), baseBranch: input.baseRefName, headSelector: input.headSelector, ...(source ? { source } : {}), @@ -347,7 +352,7 @@ export const make = Effect.gen(function* () { ); }), getDefaultBranch: Effect.fn("GitLabSourceControlProvider.getDefaultBranch")(function (input) { - return gitlab.getDefaultBranch(input).pipe( + return gitlab.getDefaultBranch({ ...input, host: contextHost(input.context) }).pipe( Effect.mapError( (error) => new SourceControlProviderError({ @@ -363,7 +368,7 @@ export const make = Effect.gen(function* () { }), checkoutChangeRequest: Effect.fn("GitLabSourceControlProvider.checkoutChangeRequest")( function (input) { - return gitlab.checkoutMergeRequest(input).pipe( + return gitlab.checkoutMergeRequest({ ...input, host: contextHost(input.context) }).pipe( Effect.mapError( (error) => new SourceControlProviderError({