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

it.effect.each(["checkout", "bare-backed worktree"] as const)(
"uses the only remote's HEAD for default-branch detection without origin in a %s",
(layout) =>
Effect.gen(function* () {
const driver = yield* GitVcsDriver.GitVcsDriver;
const path = yield* Path.Path;
const source = yield* makeTmpDir();
const remote = yield* makeTmpDir("git-vcs-driver-github-");
yield* initRepoWithCommit(source);
yield* git(remote, ["init", "--bare"]);
yield* git(source, ["branch", "-M", "trunk"]);
yield* git(source, ["remote", "add", "github", remote]);
yield* git(source, ["push", "-u", "github", "trunk"]);
let cwd = source;
if (layout === "bare-backed worktree") {
const container = yield* makeTmpDir("git-vcs-driver-bare-");
const gitDirArgs = ["--git-dir", path.join(container, "repo.git")];
cwd = path.join(container, "trunk");
yield* git(container, ["init", "--bare", "repo.git"]);
yield* git(container, [...gitDirArgs, "config", "user.email", "test@test.com"]);
yield* git(container, [...gitDirArgs, "config", "user.name", "Test"]);
yield* git(container, [...gitDirArgs, "remote", "add", "github", remote]);
yield* git(container, [...gitDirArgs, "fetch", "github"]);
yield* git(container, [
...gitDirArgs,
"worktree",
"add",
"--track",
"-b",
"trunk",
cwd,
"github/trunk",
]);
}
yield* git(cwd, [
"symbolic-ref",
"refs/remotes/github/HEAD",
"refs/remotes/github/trunk",
]);

assert.isTrue((yield* driver.statusDetailsLocal(cwd)).isDefaultBranch);
const remoteStatus = yield* driver.statusDetailsRemote(cwd);
assert.equal(remoteStatus.defaultBranch, "trunk");
assert.isTrue(remoteStatus.isDefaultBranch);

yield* git(cwd, ["checkout", "-b", "feature"]);
yield* writeTextFile(cwd, "feature.txt", "feature\n");
yield* git(cwd, ["add", "feature.txt"]);
yield* git(cwd, ["commit", "-m", "feature commit"]);
yield* git(cwd, ["push", "-u", "github", "feature"]);
const featureStatus = yield* driver.statusDetails(cwd);
assert.isFalse(featureStatus.isDefaultBranch);
assert.equal(featureStatus.aheadOfDefaultCount, 1);

yield* git(cwd, ["remote", "add", "origin", remote]);
assert.isNull((yield* driver.statusDetailsRemote(cwd)).defaultBranch);
}),
);

it.effect("makes background upstream status fetches non-interactive", () =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
Expand Down
38 changes: 22 additions & 16 deletions apps/server/src/vcs/GitVcsDriverCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1555,17 +1555,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
const path = yield* Path.Path;
const fetchCwd =
path.basename(gitCommonDir) === ".git" ? path.dirname(gitCommonDir) : gitCommonDir;
return yield* executeGit(
"GitVcsDriver.statusDetails.defaultBranch",
fetchCwd,
["--git-dir", gitCommonDir, "symbolic-ref", "refs/remotes/origin/HEAD"],
{ allowNonZeroExit: true },
).pipe(
Effect.map((result) => {
if (result.exitCode !== 0) return null;
return parseDefaultBranchFromRemoteHeadRef(result.stdout, "origin");
}),
);
return yield* resolvePrimaryDefaultBranchName(fetchCwd, ["--git-dir", gitCommonDir]);
Comment thread
only21mil marked this conversation as resolved.
Resolved
}),
{
capacity: 2_048,
Expand Down Expand Up @@ -1692,11 +1682,12 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
const resolveDefaultBranchName = (
cwd: string,
remoteName: string,
gitDirArgs: ReadonlyArray<string> = [],
): Effect.Effect<string | null, GitCommandError> =>
executeGit(
"GitVcsDriver.resolveDefaultBranchName",
cwd,
["symbolic-ref", `refs/remotes/${remoteName}/HEAD`],
[...gitDirArgs, "symbolic-ref", `refs/remotes/${remoteName}/HEAD`],
{ allowNonZeroExit: true },
).pipe(
Effect.map((result) => {
Expand Down Expand Up @@ -1730,8 +1721,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
const originRemoteExists = (cwd: string): Effect.Effect<boolean, GitCommandError> =>
remoteExists({ cwd, remoteName: "origin" });

const listRemoteNames = (cwd: string): Effect.Effect<ReadonlyArray<string>, GitCommandError> =>
runGitStdout("GitVcsDriver.listRemoteNames", cwd, ["remote"]).pipe(
const listRemoteNames = (
cwd: string,
gitDirArgs: ReadonlyArray<string> = [],
): Effect.Effect<ReadonlyArray<string>, GitCommandError> =>
runGitStdout("GitVcsDriver.listRemoteNames", cwd, [...gitDirArgs, "remote"]).pipe(
Effect.map(parseRemoteNamesInGitOrder),
);

Expand All @@ -1754,6 +1748,18 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
return firstRemote ?? null;
});

const resolvePrimaryDefaultBranchName = Effect.fn("GitVcsDriver.resolvePrimaryDefaultBranchName")(
function* (cwd: string, gitDirArgs: ReadonlyArray<string> = []) {
const originDefault = yield* resolveDefaultBranchName(cwd, "origin", gitDirArgs);
if (originDefault !== null) return originDefault;
const remoteNames = yield* listRemoteNames(cwd, gitDirArgs);
const [firstRemote] = remoteNames;
return firstRemote === undefined || remoteNames.includes("origin")
? null
: yield* resolveDefaultBranchName(cwd, firstRemote, gitDirArgs);
},
);

const resolvePrimaryRemoteName = (cwd: string) =>
findPrimaryRemoteName(cwd).pipe(
Effect.filterOrFail(
Expand Down Expand Up @@ -2089,7 +2095,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
);
}

const defaultBranch = yield* resolveDefaultBranchName(cwd, "origin");
const defaultBranch = yield* resolvePrimaryDefaultBranchName(cwd);
const isDefaultBranch =
branch !== null &&
(branch === defaultBranch ||
Expand Down Expand Up @@ -2235,7 +2241,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
),
statusCacheKey
? Cache.get(defaultBranchCache, statusCacheKey).pipe(Effect.orElseSucceed(() => null))
: resolveDefaultBranchName(cwd, "origin").pipe(Effect.orElseSucceed(() => null)),
: resolvePrimaryDefaultBranchName(cwd).pipe(Effect.orElseSucceed(() => null)),
statusCacheKey
? Cache.get(primaryRemoteExistsCache, statusCacheKey).pipe(
Effect.orElseSucceed(() => false),
Expand Down
Loading