From defc69a0f5be24f25799e24ebf103254513301ef Mon Sep 17 00:00:00 2001 From: AviPeltz Date: Mon, 4 May 2026 16:21:10 -0700 Subject: [PATCH] fix(host-service): tolerate `[blob:none]` suffix in `git remote -v` `git remote -v` appends a partial-clone marker (`[blob:none]`, `[treeless]`, etc.) after `(fetch)` whenever `remote..promisor` is set. The parser anchored on `(fetch)$`, so the line was silently dropped and `resolveLocalRepo` fell back to the alphabetically-first remote. For users who cloned with `--filter=blob:none`, the v2 add-folder flow detected the wrong remote and either rejected the folder or matched against the wrong cloud project. --- .../trpc/router/project/utils/git-remote.ts | 6 +++++- .../router/project/utils/resolve-repo.test.ts | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/host-service/src/trpc/router/project/utils/git-remote.ts b/packages/host-service/src/trpc/router/project/utils/git-remote.ts index cc27dafdcf..55515c37fc 100644 --- a/packages/host-service/src/trpc/router/project/utils/git-remote.ts +++ b/packages/host-service/src/trpc/router/project/utils/git-remote.ts @@ -18,7 +18,11 @@ export async function getAllRemoteUrls( if (!output) return remotes; for (const line of output.trim().split(/\r?\n/)) { - const match = line.trim().match(/^(\S+)\s+(\S+)\s+\(fetch\)$/); + // Don't anchor on `(fetch)` at end-of-line: git appends partial-clone + // markers like ` [blob:none]` when `remote..promisor=true`, and + // dropping those lines silently loses the remote (we then fall back to + // the alphabetically-first remote, which is almost always wrong). + const match = line.trim().match(/^(\S+)\s+(\S+)\s+\(fetch\)(?:\s|$)/); if (match?.[1] && match[2]) { remotes.set(match[1], match[2]); } diff --git a/packages/host-service/src/trpc/router/project/utils/resolve-repo.test.ts b/packages/host-service/src/trpc/router/project/utils/resolve-repo.test.ts index 5bfdf207ad..e18feec44a 100644 --- a/packages/host-service/src/trpc/router/project/utils/resolve-repo.test.ts +++ b/packages/host-service/src/trpc/router/project/utils/resolve-repo.test.ts @@ -107,6 +107,26 @@ describe("resolveLocalRepo", () => { expect(resolved.parsed?.name).toBe("App"); }); + test("returns origin when it is configured as a partial clone (`[blob:none]` suffix)", async () => { + // Partial clones (e.g. `git clone --filter=blob:none`) make + // `git remote -v` append a `[blob:none]` marker after the URL. + // Earlier the parser anchored on `(fetch)$`, so the origin line was + // silently skipped and we fell back to the alphabetically-first + // remote. Regression: `aaa` must NOT win over a partial-clone origin. + const repo = join(workRoot, "partial-clone-origin"); + const git = await initRepoAt(repo); + await git.addRemote("aaa", "https://github.com/Other/Repo.git"); + await git.addRemote("origin", "git@github.com:Acme/App.git"); + await git.raw(["config", "remote.origin.promisor", "true"]); + await git.raw(["config", "remote.origin.partialclonefilter", "blob:none"]); + + const resolved = await resolveLocalRepo(repo); + + expect(resolved.remoteName).toBe("origin"); + expect(resolved.parsed?.owner).toBe("Acme"); + expect(resolved.parsed?.name).toBe("App"); + }); + test("falls back to first GitHub remote when origin is missing", async () => { const repo = join(workRoot, "no-origin"); const git = await initRepoAt(repo);