Skip to content
Open
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
10 changes: 0 additions & 10 deletions apps/server/src/git/GitWorkflowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,6 @@ export class GitWorkflowService extends Context.Service<
options?: GitVcsDriver.CreateWorktreeOptions,
) => Effect.Effect<VcsCreateWorktreeResult, GitCommandError>;
readonly listLocalBranchNames: (cwd: string) => Effect.Effect<string[], GitCommandError>;
readonly fetchRemote: (input: {
readonly cwd: string;
readonly remoteName: string;
readonly refName?: string;
}) => Effect.Effect<void, GitCommandError>;
readonly remoteExists: (input: {
readonly cwd: string;
readonly remoteName: string;
Expand Down Expand Up @@ -420,11 +415,6 @@ export const make = Effect.gen(function* () {
Effect.andThen(git.listLocalBranchNames(cwd)),
);
}),
fetchRemote: Effect.fn("GitWorkflowService.fetchRemote")(function (input) {
return ensureGitCommand("GitWorkflowService.fetchRemote", input.cwd).pipe(
Effect.andThen(git.fetchRemote(input)),
);
}),
remoteExists: Effect.fn("GitWorkflowService.remoteExists")(function (input) {
return ensureGitCommand("GitWorkflowService.remoteExists", input.cwd).pipe(
Effect.andThen(git.remoteExists(input)),
Expand Down
7 changes: 0 additions & 7 deletions apps/server/src/vcs/GitVcsDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,12 +250,6 @@ export interface GitFetchRemoteTrackingBranchInput {
remoteBranch: string;
}

export interface GitFetchRemoteInput {
cwd: string;
remoteName: string;
refName?: string;
}

export interface GitRemoteExistsInput {
cwd: string;
remoteName: string;
Expand Down Expand Up @@ -407,7 +401,6 @@ export class GitVcsDriver extends Context.Service<
cwd: string,
remoteName: string,
) => Effect.Effect<string | null, GitCommandError>;
readonly fetchRemote: (input: GitFetchRemoteInput) => Effect.Effect<void, GitCommandError>;
readonly remoteExists: (input: GitRemoteExistsInput) => Effect.Effect<boolean, GitCommandError>;
readonly remoteBranchExists: (
input: GitRemoteBranchExistsInput,
Expand Down
108 changes: 95 additions & 13 deletions apps/server/src/vcs/GitVcsDriverCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ describe("Git output collection", () => {
);
});

const originMain = { remoteName: "origin", branchName: "main" } as const;

const makeTmpDir = (
prefix = "git-vcs-driver-test-",
): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> =>
Expand Down Expand Up @@ -1497,7 +1499,14 @@ it.effect.each([
if (!ChildProcess.isStandardCommand(command))
return yield* Effect.die("expected Git command");
if (command.args[0] !== "fetch") return makeNonRepositoryHandle();
assert.deepEqual(command.args, ["fetch", "--quiet", "--", "origin"]);
assert.deepEqual(command.args, [
"fetch",
"--quiet",
"--no-tags",
"--",
"origin",
"+refs/heads/main:refs/remotes/origin/main",
]);
assert.equal(command.options.env?.LANGUAGE, "C");
assert.equal(command.options.env?.GIT_TERMINAL_PROMPT, "0");
yield* Ref.update(attempts, (count) => count + 1);
Expand All @@ -1520,7 +1529,9 @@ it.effect.each([
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
);
const cwd = yield* makeTmpDir();
const error = yield* driver.fetchRemote({ cwd, remoteName: "origin" }).pipe(Effect.flip);
const error = yield* driver
.fetchWorktreeBaseCommit({ cwd, refName: "main", remote: originMain })
.pipe(Effect.flip);
assert.include(error.detail, scenario.expected);
assert.equal(error.exitCode, 128);
assert.equal(error.stderrLength, stderr.length);
Expand Down Expand Up @@ -1564,7 +1575,7 @@ it.effect("retries a fetch that lost a ref update to another Git process", () =>
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
);
const cwd = yield* makeTmpDir();
yield* driver.fetchRemote({ cwd, remoteName: "origin", refName: "main" });
yield* driver.fetchWorktreeBaseCommit({ cwd, refName: "main", remote: originMain });
assert.equal(yield* Ref.get(attempts), 2);
}).pipe(
Effect.provide(
Expand Down Expand Up @@ -1605,6 +1616,67 @@ it.effect("does not fetch for status when the branch tracks a local branch", ()
).pipe(Effect.provide(layerTest)),
);

it.effect("fetches for status only the default branch and checked-out upstreams and bases", () =>
Effect.scoped(
Effect.gen(function* () {
const delegate = yield* ChildProcessSpawner.ChildProcessSpawner;
const fetches: Array<ReadonlyArray<string>> = [];
const spawner = ChildProcessSpawner.make((command) => {
if (ChildProcess.isStandardCommand(command) && command.args.includes("fetch")) {
fetches.push(command.args.slice(command.args.indexOf("fetch")).map(String));
}
return delegate.spawn(command);
});
const driver = yield* makeGitVcsDriverCore().pipe(
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
);
const cwd = yield* makeTmpDir();
const remote = yield* makeTmpDir("git-vcs-driver-remote-");
const worktree = NodePath.join(yield* makeTmpDir("git-vcs-driver-worktrees-"), "stale");
yield* initRepoWithCommit(cwd);
yield* git(cwd, ["branch", "-M", "main"]);
yield* git(remote, ["init", "--bare"]);
yield* git(cwd, ["remote", "add", "origin", remote]);
const branches = ["main", "feature", "parent", "idle-base", "gone", "idle", "unrelated"];
yield* git(cwd, ["push", "origin", ...branches.map((name) => `HEAD:refs/heads/${name}`)]);
yield* git(cwd, ["remote", "set-head", "origin", "main"]);
yield* git(cwd, ["checkout", "-b", "feature", "--track", "origin/feature"]);
yield* git(cwd, ["branch", "--track", "idle", "origin/idle"]);
yield* git(cwd, ["config", "branch.feature.t3-merge-base", "origin/parent"]);
yield* git(cwd, ["config", "branch.idle.t3-merge-base", "origin/idle-base"]);
yield* git(cwd, ["worktree", "add", "-b", "stale", "--track", worktree, "origin/gone"]);
yield* git(remote, ["--git-dir=.", "update-ref", "-d", "refs/heads/gone"]);
// Pushing by path leaves the local remote-tracking refs behind the remote.
const moved = yield* git(cwd, ["commit-tree", "HEAD^{tree}", "-p", "HEAD", "-m", "moved"]);
const movedBranches = ["main", "feature", "parent", "idle-base", "idle", "unrelated"];
yield* git(cwd, [
"push",
remote,
...movedBranches.map((name) => `${moved}:refs/heads/${name}`),
]);
const before = yield* git(cwd, ["rev-parse", "origin/unrelated"]);

const status = yield* driver.statusDetailsRemote(cwd);

assert.deepStrictEqual(fetches, [
[
"fetch",
"--quiet",
"--no-tags",
"--no-auto-gc",
"--",
"origin",
"refs/heads/feature",
"refs/heads/main",
"refs/heads/parent",
],
]);
assert.equal(status.behindCount, 1);
assert.equal(yield* git(cwd, ["rev-parse", "origin/unrelated"]), before);
}),
).pipe(Effect.provide(layerTest)),
);

it.effect("lets a worktree checkout that is still working run past five minutes", () =>
Effect.gen(function* () {
const checkoutStarted = yield* Deferred.make<void>();
Expand Down Expand Up @@ -4081,7 +4153,8 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
yield* writeTextFile(cwd, "second.txt", "second\n");
yield* git(cwd, ["add", "second.txt"]);
yield* git(cwd, ["commit", "-m", "second commit"]);
yield* git(cwd, ["push"]);
// Push by path so the status poll still has a moved branch to fetch.
yield* git(cwd, ["push", remote, `HEAD:refs/heads/${initialBranch}`]);
yield* git(cwd, ["repack", "-d"]);
yield* git(cwd, ["config", "gc.autoPackLimit", "1"]);
yield* git(cwd, ["config", "gc.autoDetach", "false"]);
Expand Down Expand Up @@ -5650,7 +5723,9 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
const driver = yield* GitVcsDriver.GitVcsDriver;
const missingRemote = `${cwd}/private-missing-remote`;
yield* git(cwd, ["remote", "add", "origin", missingRemote]);
const error = yield* driver.fetchRemote({ cwd, remoteName: "origin" }).pipe(Effect.flip);
const error = yield* driver
.fetchWorktreeBaseCommit({ cwd, refName: "main", remote: originMain })
.pipe(Effect.flip);
assert.include(error.detail, "could not access the remote repository");
assert.equal(error.exitCode, 128);
assert.isAbove(error.stderrLength ?? 0, 0);
Expand Down Expand Up @@ -5679,7 +5754,11 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {

assert.equal(yield* driver.resolvePrimaryRemoteName(cwd), remoteName);
yield* driver.fetchRemoteTrackingBranch({ cwd, remoteName, remoteBranch: initialBranch });
yield* driver.fetchRemote({ cwd, remoteName });
yield* driver.fetchWorktreeBaseCommit({
cwd,
refName: initialBranch,
remote: { remoteName, branchName: initialBranch },
});
const head = yield* driver.fetchRefCommit({ cwd, ref: "refs/pull/7/head" });

assert.isFalse(yield* fileSystem.exists((yield* Path.Path).join(cwd, "t3-marker")));
Expand Down Expand Up @@ -6148,11 +6227,7 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {

yield* git(peer, ["push", "origin", "HEAD:refs/heads/unrelated"]);
const driver = yield* GitVcsDriver.GitVcsDriver;
yield* driver.fetchRemote({
cwd,
remoteName: "origin",
refName: `origin/${initialBranch}`,
});
yield* driver.fetchWorktreeBaseCommit({ cwd, refName: initialBranch });
assert.isFalse(
yield* driver.remoteBranchExists({ cwd, remoteName: "origin", refName: "unrelated" }),
);
Expand Down Expand Up @@ -6222,8 +6297,15 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
assert.equal(status.aheadCount, 0);
assert.equal(status.aheadOfDefaultCount, 0);

yield* driver.fetchRemote({ cwd, remoteName: "origin", refName: "local-only" });
assert.isTrue(
// A missing branch must not fall back to fetching every branch.
assert.isNull(
yield* driver.fetchWorktreeBaseCommit({
cwd,
refName: "local-only",
remote: { remoteName: "origin", branchName: "local-only" },
}),
);
assert.isFalse(
yield* driver.remoteBranchExists({ cwd, remoteName: "origin", refName: "unrelated" }),
);
}),
Expand Down
Loading
Loading