Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions apps/server/src/project/RepositoryIdentityResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,64 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {
}).pipe(Effect.provide(Layer.merge(TestClock.layer(), resolverLayer)));
});

// A partial clone appends its filter to the fetch line, e.g. "(fetch) [blob:none]".
it.effect.each([
{ suffix: "", canonicalKey: "github.com/acme/repo" },
{ suffix: " [blob:none]", canonicalKey: "github.com/acme/repo" },
{ suffix: " [blob:limit=1m]", canonicalKey: "github.com/acme/repo" },
{ suffix: " [tree:0]", canonicalKey: "github.com/acme/repo" },
{ suffix: " blob:none", canonicalKey: null },
{ suffix: " [blob:none", canonicalKey: null },
])(
"reads the remote from a fetch line ending in '(fetch)$suffix'",
({ suffix, canonicalKey }) => {
const processRunner = Layer.succeed(ProcessRunner.ProcessRunner, {
run: (input) =>
Effect.succeed({
stdout: input.args.includes("rev-parse")
? "/repo\n"
: `origin\thttps://github.com/acme/repo.git (fetch)${suffix}\norigin\thttps://github.com/acme/repo.git (push)\n`,
stderr: "",
code: ChildProcessSpawner.ExitCode(0),
timedOut: false,
stdoutTruncated: false,
stderrTruncated: false,
stdoutInvalidUtf8: false,
stderrInvalidUtf8: false,
}),
});
const resolverLayer = Layer.effect(
RepositoryIdentityResolver.RepositoryIdentityResolver,
RepositoryIdentityResolver.make(),
).pipe(Layer.provide(processRunner));

return Effect.gen(function* () {
const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver;
const identity = yield* resolver.resolve("/repo");
expect(identity?.canonicalKey ?? null).toBe(canonicalKey);
}).pipe(Effect.provide(resolverLayer));
},
);

it.effect("resolves a partial clone the same as a full clone of the repository", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const cwd = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-repository-identity-partial-clone-test-",
});

yield* git(cwd, ["init"]);
yield* git(cwd, ["remote", "add", "origin", "https://github.com/T3Tools/t3code.git"]);
// What `git clone --filter=blob:none` records for the remote.
yield* git(cwd, ["config", "remote.origin.partialclonefilter", "blob:none"]);

const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver;
const identity = yield* resolver.resolve(cwd);

expect(identity?.canonicalKey).toBe("github.com/t3tools/t3code");
}).pipe(Effect.provide(RepositoryIdentityResolver.layer)),
);

it.effect("normalizes equivalent GitHub remotes into a stable repository identity", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
Expand Down
3 changes: 2 additions & 1 deletion apps/server/src/project/RepositoryIdentityResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ function parseRemoteFetchUrls(stdout: string): Map<string, string> {
for (const line of stdout.split("\n")) {
const trimmed = line.trim();
if (trimmed.length === 0) continue;
const match = /^(\S+)\s+(\S+)\s+\((fetch|push)\)$/.exec(trimmed);
// A partial clone's fetch line ends with its filter, e.g. "(fetch) [blob:none]".
const match = /^(\S+)\s+(\S+)\s+\((fetch|push)\)(?:\s+\[.*\])?$/.exec(trimmed);
if (!match) continue;
const [, remoteName = "", remoteUrl = "", direction = ""] = match;
if (direction !== "fetch" || remoteName.length === 0 || remoteUrl.length === 0) {
Expand Down
22 changes: 22 additions & 0 deletions apps/server/src/sourceControl/GitHubCli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,28 @@ describe("selectGitHubBaseRepository", () => {
assert.strictEqual(select(input), null);
}
});

it("reads an origin whose fetch line carries a partial-clone filter", () => {
const withOrigin = (fetchSuffix: string) =>
[
`origin\tgit@github.com:acme/web.git (fetch)${fetchSuffix}`,
"origin\tgit@github.com:acme/web.git (push)",
remotes(["fork", "git@github.com:me/web.git"]),
].join("\n");
// gh ranks origin above fork, so origin wins whenever its fetch line is read.
for (const suffix of ["", " [blob:none]", " [blob:limit=1m]", " [tree:0]"]) {
assert.deepStrictEqual(select({ remotes: withOrigin(suffix) }), {
owner: "acme",
name: "web",
});
}
for (const malformed of [" blob:none", " [blob:none"]) {
assert.deepStrictEqual(select({ remotes: withOrigin(malformed) }), {
owner: "me",
name: "web",
});
}
});
});

describe("GitHubCli.listPullRequestsByHead", () => {
Expand Down
3 changes: 2 additions & 1 deletion apps/server/src/sourceControl/GitHubCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,8 @@ export function selectGitHubBaseRepository(input: {
const host = input.host.toLowerCase();
const repositories = new Map<string, { readonly owner: string; readonly name: string }>();
for (const line of input.remotes.split("\n")) {
const match = /^(\S+)\s+(\S+)\s+\(fetch\)$/u.exec(line.trim());
// A partial clone's fetch line ends with its filter, e.g. "(fetch) [blob:none]".
const match = /^(\S+)\s+(\S+)\s+\(fetch\)(?:\s+\[.*\])?$/u.exec(line.trim());
if (!match) continue;
const [remoteHost, owner, name, ...rest] = normalizeGitRemoteUrl(match[2]!).split("/");
if (remoteHost !== host || !owner || !name || rest.length > 0) return null;
Expand Down
47 changes: 47 additions & 0 deletions apps/server/src/vcs/GitVcsDriver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as Fiber from "effect/Fiber";
import * as TestClock from "effect/testing/TestClock";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Path from "effect/Path";
import * as PlatformError from "effect/PlatformError";
import { ChildProcessSpawner } from "effect/unstable/process";
Expand Down Expand Up @@ -1190,3 +1191,49 @@ it.effect("GitVcsDriver flushes checkpoint objects and refs to disk before publi
),
);
});

// A partial clone appends its filter to the fetch line, e.g. "(fetch) [blob:none]".
it.effect.each([
{ suffix: "", listed: true },
{ suffix: " [blob:none]", listed: true },
{ suffix: " [blob:limit=1m]", listed: true },
{ suffix: " [tree:0]", listed: true },
// Git prints the configured filter verbatim, spaces and brackets included.
{ suffix: " [sparse:oid=main:dir/a b]c]", listed: true },
{ suffix: " blob:none", listed: false },
{ suffix: " [blob:none", listed: false },
])("GitVcsDriver lists a remote whose fetch line ends in '(fetch)$suffix'", ({ suffix, listed }) =>
Effect.gen(function* () {
const driver = yield* GitVcsDriver.makeVcsDriverShape();
const { remotes } = yield* driver.listRemotes("/repo");

assert.deepStrictEqual(
remotes.map(({ name, url, pushUrl }) => ({ name, url, pushUrl: Option.getOrNull(pushUrl) })),
listed
? [
{
name: "origin",
url: "https://github.com/acme/repo.git",
pushUrl: "git@github.com:acme/repo.git",
},
]
: [],
);
}).pipe(
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
Layer.mock(VcsProcess.VcsProcess)({
run: () =>
Effect.succeed({
exitCode: ChildProcessSpawner.ExitCode(0),
stdout: `origin\thttps://github.com/acme/repo.git (fetch)${suffix}\norigin\tgit@github.com:acme/repo.git (push)\n`,
stderr: "",
stdoutTruncated: false,
stderrTruncated: false,
}),
}),
),
),
),
);
3 changes: 2 additions & 1 deletion apps/server/src/vcs/GitVcsDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,8 @@ function parseGitRemoteVerboseOutput(
continue;
}

const match = /^(\S+)\s+(\S+)\s+\((fetch|push)\)$/.exec(trimmed);
// A partial clone's fetch line ends with its filter, e.g. "(fetch) [blob:none]".
const match = /^(\S+)\s+(\S+)\s+\((fetch|push)\)(?:\s+\[.*\])?$/.exec(trimmed);
if (!match) {
continue;
}
Expand Down
40 changes: 40 additions & 0 deletions apps/server/src/vcs/GitVcsDriverCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2888,6 +2888,46 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
assert.equal(yield* git(cwd, ["remote"]), "octocat\norigin");
}),
);

// A partial clone appends its filter to the fetch line, e.g. "(fetch) [blob:none]".
it.effect.each([
{ suffix: "", remoteName: "origin" },
{ suffix: " [blob:none]", remoteName: "origin" },
{ suffix: " [blob:limit=1m]", remoteName: "origin" },
{ suffix: " [tree:0]", remoteName: "origin" },
{ suffix: " blob:none", remoteName: "pingdotgg" },
{ suffix: " [blob:none", remoteName: "pingdotgg" },
])(
"ensureRemote reads an origin fetch line ending in '(fetch)$suffix'",
({ suffix, remoteName }) =>
Effect.gen(function* () {
const added: Array<ReadonlyArray<string>> = [];
const spawner = ChildProcessSpawner.make((command) =>
Effect.sync(() => {
const args = ChildProcess.isStandardCommand(command) ? command.args : [];
if (args[0] === "remote" && args[1] === "add") added.push(args);
return makeSuccessfulHandle(
args[0] === "remote" && args[1] === "-v"
? `origin\thttps://github.com/pingdotgg/t3code.git (fetch)${suffix}\norigin\thttps://github.com/pingdotgg/t3code.git (push)\n`
: "",
);
}),
);
const driver = yield* makeGitVcsDriverCore().pipe(
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
Effect.provide(ServerConfigLayer),
);

const resolved = yield* driver.ensureRemote({
cwd: "/repo",
preferredName: "pingdotgg",
url: "git@github.com:pingdotgg/t3code.git",
});

assert.equal(resolved, remoteName);
assert.equal(added.length, remoteName === "origin" ? 0 : 1);
}),
);
});

describe("commit context", () => {
Expand Down
3 changes: 2 additions & 1 deletion apps/server/src/vcs/GitVcsDriverCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@ function parseRemoteFetchUrls(stdout: string): Map<string, string> {
for (const line of stdout.split("\n")) {
const trimmed = line.trim();
if (trimmed.length === 0) continue;
const match = /^(\S+)\s+(\S+)\s+\((fetch|push)\)$/.exec(trimmed);
// A partial clone's fetch line ends with its filter, e.g. "(fetch) [blob:none]".
const match = /^(\S+)\s+(\S+)\s+\((fetch|push)\)(?:\s+\[.*\])?$/.exec(trimmed);
if (!match) continue;
const [, remoteName = "", remoteUrl = "", direction = ""] = match;
if (direction !== "fetch" || remoteName.length === 0 || remoteUrl.length === 0) {
Expand Down
Loading