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

it.effect("Changes compares the default branch with its tracking upstream, not origin", () =>
Effect.gen(function* () {
// `origin` is a fork whose main is behind; the checkout tracks the
// canonical repository through a second remote (#15314).
const canonical = yield* makeTmpDir();
yield* initRepoWithCommit(canonical);
yield* git(canonical, ["branch", "-M", "main"]);
const fork = yield* makeTmpDir();
yield* git(fork, ["clone", "--quiet", canonical, "."]);
yield* writeTextFile(canonical, "canonical.txt", "one\ntwo\nthree\n");
yield* git(canonical, ["add", "canonical.txt"]);
yield* git(canonical, ["commit", "-m", "canonical moves ahead"]);

const cwd = yield* makeTmpDir();
const driver = yield* GitVcsDriver.GitVcsDriver;
yield* git(cwd, ["init", "-b", "main"]);
yield* git(cwd, ["remote", "add", "origin", fork]);
yield* git(cwd, ["remote", "add", "upstream", canonical]);
yield* git(cwd, ["fetch", "--quiet", "--all"]);
yield* git(cwd, ["reset", "--hard", "--quiet", "upstream/main"]);
yield* git(cwd, ["branch", "--set-upstream-to=upstream/main", "main"]);

const status = yield* driver.statusDetailsLocal(cwd, { includeBranchChanges: true });
assert.deepStrictEqual(status.branchChanges, {
baseRef: "upstream/main",
insertions: 0,
deletions: 0,
});
const preview = yield* driver.getReviewDiffPreview({ cwd });
const changes = preview.sources.find((source) => source.kind === "branch-range")!;
assert.strictEqual(changes.baseRef, "upstream/main");
assert.deepStrictEqual(changes.files, []);

// Without an upstream the primary remote's copy stays the base.
yield* git(cwd, ["branch", "--unset-upstream", "main"]);
const untracked = yield* driver.statusDetailsLocal(cwd, { includeBranchChanges: true });
assert.deepStrictEqual(untracked.branchChanges, {
baseRef: "origin/main",
insertions: 3,
deletions: 0,
});
}),
);

it.effect("reports non-repository directories without failing", () =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
Expand Down
46 changes: 33 additions & 13 deletions apps/server/src/vcs/GitVcsDriverCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1682,6 +1682,23 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
return remoteBase.exitCode === 0 ? { baseRef, comparisonRef } : null;
});

// Tracking may point to a different remote than the primary one. Resolve its
// full ref so a similarly named local branch or tag cannot shadow it.
const resolveTrackedRemoteBaseRef = Effect.fn("resolveTrackedRemoteBaseRef")(function* (
cwd: string,
refName: string,
) {
const upstream = yield* executeGit(
"GitVcsDriver.resolveTrackedRemoteBaseRef",
cwd,
["rev-parse", "--symbolic-full-name", `${refName}@{upstream}`],
{ allowNonZeroExit: true },
);
const comparisonRef = upstream.stdout.trim();
if (upstream.exitCode !== 0 || !comparisonRef.startsWith("refs/remotes/")) return null;
return { baseRef: comparisonRef.slice("refs/remotes/".length), comparisonRef };
});

const resolveBaseBranchForNoUpstream = Effect.fn("resolveBaseBranchForNoUpstream")(function* (
cwd: string,
refName: string,
Expand Down Expand Up @@ -1725,19 +1742,22 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
continue;
}
if (normalizedCandidate === refName) {
if (
options?.allowRemoteOfCurrent &&
primaryRemoteName &&
(yield* remoteBranchExists({
cwd,
remoteName: primaryRemoteName,
refName: normalizedCandidate,
}))
) {
return {
baseRef: `${primaryRemoteName}/${normalizedCandidate}`,
comparisonRef: `refs/remotes/${primaryRemoteName}/${normalizedCandidate}`,
};
if (options?.allowRemoteOfCurrent) {
const trackedRemoteBase = yield* resolveTrackedRemoteBaseRef(cwd, refName);
if (trackedRemoteBase !== null) return trackedRemoteBase;
if (
primaryRemoteName &&
(yield* remoteBranchExists({
cwd,
remoteName: primaryRemoteName,
refName: normalizedCandidate,
}))
) {
return {
baseRef: `${primaryRemoteName}/${normalizedCandidate}`,
comparisonRef: `refs/remotes/${primaryRemoteName}/${normalizedCandidate}`,
};
}
}
continue;
}
Expand Down
Loading