authorgravatar for 154429365+Rishet11@users.noreply.github.comRishet11 <154429365+Rishet11@users.noreply.github.com> 2026-08-23 05:46:25+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-22 20:16:25-04:00
log30be31195883635aba96031a8d79c255fb28b438
tree7797945e2f8dfdb3008033567b1609ce0d7d7e93
parent4e169df1dd12e0ed960dcb310e9460897759a55e
signature Signed by PGP key B5690EEEBB952194

fix(server): fall back to the remote default branch instead of assuming main (#7078)


4 files changed, 68 insertions(+), 0 deletions(-)

apps/server/src/git/GitManager.test.ts+51
...@@ -2404,6 +2404,57 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2404,6 +2404,57 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2404 }),2404 }),
2405 );2405 );
24062406
2407 it.effect("create_pr targets the remote default branch when it is not main", () =>
2408 Effect.gen(function* () {
2409 const repoDir = yield* makeTempDir("t3code-git-manager-");
2410 yield* initRepo(repoDir);
2411 const remoteDir = yield* createBareRemote();
2412 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
2413 // A repository whose default branch is master, with no main anywhere.
2414 yield* runGit(repoDir, ["push", "origin", "HEAD:master"]);
2415 yield* runGit(repoDir, ["fetch", "origin"]);
2416 yield* runGit(repoDir, ["remote", "set-head", "origin", "master"]);
2417
2418 yield* runGit(repoDir, ["checkout", "-b", "feature/master-default"]);
2419 NodeFS.writeFileSync(NodePath.join(repoDir, "master-default.txt"), "master default\n");
2420 yield* runGit(repoDir, ["add", "master-default.txt"]);
2421 yield* runGit(repoDir, ["commit", "-m", "Master default"]);
2422
2423 const { manager, ghCalls } = yield* makeManager({
2424 ghScenario: {
2425 // Mirrors a provider that cannot report a default branch, as the Azure
2426 // DevOps CLI does when it cannot detect the repository.
2427 defaultBranch: "",
2428 prListSequence: [
2429 "[]",
2430 // @effect-diagnostics-next-line preferSchemaOverJson:off
2431 JSON.stringify([
2432 {
2433 number: 505,
2434 title: "Master default",
2435 url: "https://github.com/pingdotgg/codething-mvp/pull/505",
2436 baseRefName: "master",
2437 headRefName: "feature/master-default",
2438 },
2439 ]),
2440 ],
2441 },
2442 });
2443
2444 const result = yield* runStackedAction(manager, {
2445 cwd: repoDir,
2446 action: "create_pr",
2447 });
2448
2449 expect(result.pr.status).toBe("created");
2450 expect(
2451 ghCalls.some((call) =>
2452 call.includes("pr create --base master --head feature/master-default"),
2453 ),
2454 ).toBe(true);
2455 }),
2456 );
2457
2407 it.effect("returns existing PR metadata for commit/push/pr action", () =>2458 it.effect("returns existing PR metadata for commit/push/pr action", () =>
2408 Effect.gen(function* () {2459 Effect.gen(function* () {
2409 const repoDir = yield* makeTempDir("t3code-git-manager-");2460 const repoDir = yield* makeTempDir("t3code-git-manager-");
apps/server/src/git/GitManager.ts+12
...@@ -1489,6 +1489,18 @@ export const make = Effect.gen(function* () {...@@ -1489,6 +1489,18 @@ export const make = Effect.gen(function* () {
1489 return defaultFromProvider;1489 return defaultFromProvider;
1490 }1490 }
14911491
1492 // The provider lookup can fail for reasons unrelated to the branch, so fall
1493 // back to what the remote itself records before assuming a name. A repository
1494 // whose default branch is master would otherwise get a base branch that does
1495 // not exist.
1496 const defaultFromRemote = yield* gitCore.resolvePrimaryRemoteName(cwd).pipe(
1497 Effect.flatMap((remoteName) => gitCore.resolveDefaultBranchName(cwd, remoteName)),
1498 Effect.orElseSucceed(() => null),
1499 );
1500 if (defaultFromRemote) {
1501 return defaultFromRemote;
1502 }
1503
1492 return "main";1504 return "main";
1493 });1505 });
14941506
apps/server/src/vcs/GitVcsDriver.ts+4
...@@ -289,6 +289,10 @@ export class GitVcsDriver extends Context.Service<...@@ -289,6 +289,10 @@ export class GitVcsDriver extends Context.Service<
289 ) => Effect.Effect<GitRefreshCheckedOutBranchResult, GitCommandError>;289 ) => Effect.Effect<GitRefreshCheckedOutBranchResult, GitCommandError>;
290 readonly ensureRemote: (input: GitEnsureRemoteInput) => Effect.Effect<string, GitCommandError>;290 readonly ensureRemote: (input: GitEnsureRemoteInput) => Effect.Effect<string, GitCommandError>;
291 readonly resolvePrimaryRemoteName: (cwd: string) => Effect.Effect<string, GitCommandError>;291 readonly resolvePrimaryRemoteName: (cwd: string) => Effect.Effect<string, GitCommandError>;
292 readonly resolveDefaultBranchName: (
293 cwd: string,
294 remoteName: string,
295 ) => Effect.Effect<string | null, GitCommandError>;
292 readonly fetchRemote: (input: GitFetchRemoteInput) => Effect.Effect<void, GitCommandError>;296 readonly fetchRemote: (input: GitFetchRemoteInput) => Effect.Effect<void, GitCommandError>;
293 readonly remoteExists: (input: GitRemoteExistsInput) => Effect.Effect<boolean, GitCommandError>;297 readonly remoteExists: (input: GitRemoteExistsInput) => Effect.Effect<boolean, GitCommandError>;
294 readonly resolveRemoteTrackingCommit: (298 readonly resolveRemoteTrackingCommit: (
apps/server/src/vcs/GitVcsDriverCore.ts+1
...@@ -3189,6 +3189,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -3189,6 +3189,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
3189 withListRefsInvalidation(input.cwd, refreshCheckedOutBranch(input)),3189 withListRefsInvalidation(input.cwd, refreshCheckedOutBranch(input)),
3190 ensureRemote: (input) => withListRefsInvalidation(input.cwd, ensureRemote(input)),3190 ensureRemote: (input) => withListRefsInvalidation(input.cwd, ensureRemote(input)),
3191 resolvePrimaryRemoteName,3191 resolvePrimaryRemoteName,
3192 resolveDefaultBranchName,
3192 fetchRemote: (input) => withListRefsInvalidation(input.cwd, fetchRemote(input)),3193 fetchRemote: (input) => withListRefsInvalidation(input.cwd, fetchRemote(input)),
3193 remoteExists,3194 remoteExists,
3194 resolveRemoteTrackingCommit,3195 resolveRemoteTrackingCommit,