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
64 changes: 64 additions & 0 deletions apps/server/src/vcs/GitVcsDriverCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2879,6 +2879,70 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
}),
);

it.effect("does not guess a Changes base for a branch renamed during the status read", () =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
const fork = yield* makeTmpDir();
const upstream = yield* makeTmpDir();
yield* initRepoWithCommit(cwd);
yield* git(cwd, ["branch", "-M", "main"]);
yield* git(fork, ["init", "--bare"]);
yield* git(upstream, ["init", "--bare"]);
yield* git(cwd, ["remote", "add", "origin", fork]);
yield* git(cwd, ["remote", "add", "upstream", upstream]);
yield* git(cwd, ["push", "origin", "main"]);
yield* writeTextFile(cwd, "upstream.txt", "work missing from the fork\n");
yield* git(cwd, ["add", "upstream.txt"]);
yield* git(cwd, ["commit", "-m", "upstream work"]);
yield* git(cwd, ["push", "upstream", "main"]);
yield* git(cwd, ["fetch", "origin"]);
yield* git(cwd, ["fetch", "upstream"]);
yield* git(cwd, ["checkout", "-b", "t3/temporary"]);
yield* git(cwd, ["config", "branch.t3/temporary.t3-merge-base", "main"]);
yield* git(cwd, [
"config",
"branch.t3/temporary.t3-merge-base-ref",
"refs/remotes/upstream/main",
]);
yield* writeTextFile(cwd, "feature.txt", "thread work\n");

const outer = yield* GitVcsDriver.GitVcsDriver;
const delegate = yield* ChildProcessSpawner.ChildProcessSpawner;
const renameClaimed = yield* Ref.make(false);
const renamed = yield* Deferred.make<void>();
// Rename after `git status` has read HEAD, as the thread launcher does after the first turn.
// Base config reads start only after status completes; hold every one until the rename lands.
const spawner = ChildProcessSpawner.make((command) =>
Effect.gen(function* () {
if (ChildProcess.isStandardCommand(command) && command.args[0] === "config") {
if (!(yield* Ref.getAndSet(renameClaimed, true))) {
yield* git(cwd, ["branch", "-m", "t3/temporary", "feature/named"]).pipe(
Effect.provideService(GitVcsDriver.GitVcsDriver, outer),
Effect.orDie,
);
yield* Deferred.succeed(renamed, undefined);
}
yield* Deferred.await(renamed);
}
return yield* delegate.spawn(command);
}),
);
const driver = yield* makeGitVcsDriverCore().pipe(
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
Effect.provide(layerServerConfig),
);

const status = yield* driver.statusDetailsLocal(cwd, { includeBranchChanges: true });

assert.strictEqual(status.branch, "t3/temporary");
assert.deepStrictEqual(status.branchChanges, {
baseRef: null,
insertions: 1,
deletions: 0,
});
}),
);

it.effect("keeps local branch bases consistent when tags share their names", () =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
Expand Down
7 changes: 6 additions & 1 deletion apps/server/src/vcs/GitVcsDriverCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3144,12 +3144,17 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
readonly untrackedRoots: ReadonlyArray<string>;
},
) {
const { baseRef, mergeBase } = yield* resolveReviewMergeBase(
const resolved = yield* resolveReviewMergeBase(
cwd,
branch,
undefined,
options?.knownOriginDefaultBranch ?? null,
);
// A rename after `git status` read HEAD moves the base config away, so the base was guessed.
const { baseRef, mergeBase } =
branch === null || (yield* branchExists(cwd, branch))
? resolved
: { baseRef: null, mergeBase: "HEAD" };
const roots = options?.untrackedRoots ?? [];
// Bound pathspec matching work and stay below Windows' command-line limit.
const pathspecs =
Expand Down
Loading