Skip to content
Merged
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
77 changes: 48 additions & 29 deletions apps/server/src/vcs/GitVcsDriver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,35 +102,54 @@ runVcsDriverContractSuite<GitVcsDriver.GitVcsDriver, GitContractError>({
},
});

it.effect("lists the fetch and push URLs of a partial clone remote", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const cwd = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-partial-clone-remotes-",
});
const driver = yield* GitVcsDriver.makeVcsDriverShape();
yield* runGit(cwd, ["init"]);
yield* runGit(cwd, ["remote", "add", "origin", "https://github.com/pingdotgg/t3code.git"]);
yield* runGit(cwd, [
"remote",
"set-url",
"--push",
"origin",
"git@github.com:octocat/t3code.git",
]);
yield* runGit(cwd, ["config", "remote.origin.promisor", "true"]);
yield* runGit(cwd, ["config", "remote.origin.partialclonefilter", "blob:none"]);

const result = yield* driver.listRemotes(cwd);
assert.deepEqual(result.remotes, [
{
name: "origin",
url: "https://github.com/pingdotgg/t3code.git",
pushUrl: Option.some("git@github.com:octocat/t3code.git"),
isPrimary: true,
},
]);
}).pipe(Effect.scoped, Effect.provide(layerGitContract)),
it.effect.each([
{
fetchUrl: "https://github.com/pingdotgg/t3code.git",
pushUrl: "git@github.com:octocat/t3code.git",
lineEnding: "\n",
},
{ fetchUrl: "../team repos/shared.git", pushUrl: "../team repos/push.git", lineEnding: "\n" },
{
fetchUrl: " ../team repos/shared (fetch) [blob:none].git ",
pushUrl: "../team repos/push (push).git\t",
lineEnding: "\r\n",
},
])(
"lists the fetch and push URLs of a partial clone remote (%j)",
({ fetchUrl, pushUrl, lineEnding }) =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const cwd = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-partial-clone-remotes-",
});
const process = yield* VcsProcess.VcsProcess;
const driver = yield* GitVcsDriver.makeVcsDriverShape().pipe(
Effect.provideService(VcsProcess.VcsProcess, {
run: (input) =>
process.run(input).pipe(
Effect.map((result) => ({
...result,
stdout: result.stdout.replace(/\r?\n/g, lineEnding),
})),
),
}),
);
yield* runGit(cwd, ["init"]);
yield* runGit(cwd, ["remote", "add", "origin", fetchUrl]);
yield* runGit(cwd, ["remote", "set-url", "--push", "origin", pushUrl]);
yield* runGit(cwd, ["config", "remote.origin.promisor", "true"]);
yield* runGit(cwd, ["config", "remote.origin.partialclonefilter", "blob:none"]);

const result = yield* driver.listRemotes(cwd);
assert.deepEqual(result.remotes, [
{
name: "origin",
url: fetchUrl,
pushUrl: Option.some(pushUrl),
isPrimary: true,
},
]);
}).pipe(Effect.scoped, Effect.provide(layerGitContract)),
);

const makeCheckpointFixture = Effect.fn("makeCheckpointFixture")(function* (
Expand Down
10 changes: 3 additions & 7 deletions apps/server/src/vcs/GitVcsDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,14 +518,10 @@ function parseGitRemoteVerboseOutput(
output: string,
): Map<string, { url?: string; pushUrl?: string }> {
const remotes = new Map<string, { url?: string; pushUrl?: string }>();
for (const line of output.split("\n")) {
const trimmed = line.trim();
if (trimmed.length === 0) {
continue;
}

for (const line of output.split(/\r?\n/)) {
// Git uses a tab before the URL and one space before the direction; both can occur in URLs.
// Partial clones append the promisor filter, e.g. `(fetch) [blob:none]`.
const match = /^(\S+)\s+(\S+)\s+\((fetch|push)\)(?:\s+\[[^\]]+\])?$/.exec(trimmed);
const match = /^(\S+)\t(.+) \((fetch|push)\)(?:\s+\[[^\]]+\])?$/.exec(line);
if (!match) {
continue;
}
Expand Down
55 changes: 55 additions & 0 deletions apps/server/src/vcs/GitVcsDriverCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5593,6 +5593,61 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
}),
);

it.effect.each(["\n", "\r\n"])(
"ensureRemote preserves spaces in fetch URLs and reserves their names (%j)",
(lineEnding) =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
const fetchUrl = " ../team repos/shared (fetch) [blob:none].git ";
const pushUrl = "../team repos/push (push).git";
yield* git(cwd, ["init"]);
yield* git(cwd, ["remote", "add", "origin", fetchUrl]);
yield* git(cwd, ["remote", "set-url", "--push", "origin", pushUrl]);
yield* git(cwd, ["config", "remote.origin.promisor", "true"]);
yield* git(cwd, ["config", "remote.origin.partialclonefilter", "blob:none"]);
const liveDriver = yield* GitVcsDriver.GitVcsDriver;
const listing = yield* liveDriver.execute({
cwd,
args: ["remote", "-v"],
operation: "test",
});
const delegate = yield* ChildProcessSpawner.ChildProcessSpawner;
const spawner = ChildProcessSpawner.make((command) =>
ChildProcess.isStandardCommand(command) &&
command.args[0] === "remote" &&
command.args[1] === "-v"
? Effect.succeed(makeSuccessfulHandle(listing.stdout.replace(/\r?\n/g, lineEnding)))
: delegate.spawn(command),
);
const driver = yield* makeGitVcsDriverCore().pipe(
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
Effect.provide(layerServerConfig),
);

assert.equal(
yield* driver.ensureRemote({ cwd, preferredName: "origin", url: fetchUrl }),
"origin",
);
assert.equal(
yield* driver.ensureRemote({ cwd, preferredName: "origin", url: pushUrl }),
"origin-1",
);
const fetch = yield* liveDriver.execute({
cwd,
args: ["config", "--get", "remote.origin.url"],
operation: "test",
});
const push = yield* liveDriver.execute({
cwd,
args: ["config", "--get", "remote.origin.pushurl"],
operation: "test",
});
assert.equal(fetch.stdout, `${fetchUrl}\n`);
assert.equal(push.stdout, `${pushUrl}\n`);
assert.equal(yield* git(cwd, ["config", "--get", "remote.origin-1.url"]), pushUrl);
}),
);

it.effect("ensureRemote returns the remote a parallel call added for the same URL", () =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
Expand Down
7 changes: 3 additions & 4 deletions apps/server/src/vcs/GitVcsDriverCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,10 @@ function sanitizeRemoteName(value: string): string {

function parseRemoteFetchUrls(stdout: string): Map<string, string> {
const remotes = new Map<string, string>();
for (const line of stdout.split("\n")) {
const trimmed = line.trim();
if (trimmed.length === 0) continue;
for (const line of stdout.split(/\r?\n/)) {
// Git uses a tab before the URL and one space before the direction; both can occur in URLs.
// Partial clones append the promisor filter, e.g. `(fetch) [blob:none]`.
const match = /^(\S+)\s+(\S+)\s+\((fetch|push)\)(?:\s+\[[^\]]+\])?$/.exec(trimmed);
const match = /^(\S+)\t(.+) \((fetch|push)\)(?:\s+\[[^\]]+\])?$/.exec(line);
if (!match) continue;
const [, remoteName = "", remoteUrl = "", direction = ""] = match;
if (direction !== "fetch" || remoteName.length === 0 || remoteUrl.length === 0) {
Expand Down
Loading