diff --git a/apps/server/src/sourceControl/SourceControlRepositoryService.test.ts b/apps/server/src/sourceControl/SourceControlRepositoryService.test.ts index 861da9a10e05..b145fd37969a 100644 --- a/apps/server/src/sourceControl/SourceControlRepositoryService.test.ts +++ b/apps/server/src/sourceControl/SourceControlRepositoryService.test.ts @@ -176,7 +176,7 @@ it.effect("clones a looked-up repository into the requested destination", () => assert.deepStrictEqual(cloneCalls, [ { cwd: parent, - args: ["clone", CLONE_URLS.url, "t3code"], + args: ["clone", "--", CLONE_URLS.url, "t3code"], }, ]); }).pipe( @@ -195,6 +195,42 @@ it.effect("clones a looked-up repository into the requested destination", () => }).pipe(Effect.provide(NodeServices.layer)), ); +it.effect("passes dash-prefixed remote urls as positional arguments, not git options", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const parent = yield* fs.makeTempDirectoryScoped({ + prefix: "t3-source-control-clone-parent-", + }); + const injectionUrl = "--upload-pack=touch /tmp/pwned"; + const cloneCalls: Array<{ cwd: string; args: ReadonlyArray }> = []; + + yield* Effect.gen(function* () { + const service = yield* SourceControlRepositoryService.SourceControlRepositoryService; + const result = yield* service.cloneRepository({ + remoteUrl: injectionUrl, + destinationPath: `${parent}/repo`, + }); + + assert.deepStrictEqual(cloneCalls, [ + { cwd: parent, args: ["clone", "--", injectionUrl, "repo"] }, + ]); + assert.strictEqual(result.remoteUrl, injectionUrl); + }).pipe( + Effect.provide( + makeLayer({ + git: { + execute: (input) => + Effect.sync(() => { + cloneCalls.push({ cwd: input.cwd, args: input.args }); + return processOutput(); + }), + }, + }), + ), + ); + }).pipe(Effect.provide(NodeServices.layer)), +); + it.effect("preserves destination probe failures instead of treating them as missing paths", () => { const fileSystemCause = PlatformError.systemError({ _tag: "PermissionDenied", diff --git a/apps/server/src/sourceControl/SourceControlRepositoryService.ts b/apps/server/src/sourceControl/SourceControlRepositoryService.ts index 1b46369e25c4..fae6516a97c2 100644 --- a/apps/server/src/sourceControl/SourceControlRepositoryService.ts +++ b/apps/server/src/sourceControl/SourceControlRepositoryService.ts @@ -203,10 +203,12 @@ export const make = Effect.gen(function* () { }); } + // The end-of-options marker keeps remoteUrl and directoryName from being + // parsed as git options when they start with "-" (e.g. --upload-pack=...). yield* git.execute({ operation: "SourceControlRepositoryService.cloneRepository", cwd: preparedDestination.parentPath, - args: ["clone", remoteUrl, preparedDestination.directoryName], + args: ["clone", "--", remoteUrl, preparedDestination.directoryName], timeoutMs: 120_000, maxOutputBytes: 256 * 1024, });