diff --git a/apps/server/src/git/Layers/GitCore.test.ts b/apps/server/src/git/Layers/GitCore.test.ts index 7e6a7c6c0941de915f5a4d79035e92c3acf597f5..5f76ec22f0cb9424f8136dee70be38b8bcbd4d7f 100644 --- a/apps/server/src/git/Layers/GitCore.test.ts +++ b/apps/server/src/git/Layers/GitCore.test.ts @@ -1161,6 +1161,32 @@ it.layer(TestLayer)("git integration", (it) => { }), ); + it.effect( + "computes ahead count against origin/default when local default branch is missing", + () => + Effect.gen(function* () { + const remote = yield* makeTmpDir(); + const source = yield* makeTmpDir(); + yield* git(remote, ["init", "--bare"]); + + yield* initRepoWithCommit(source); + yield* git(source, ["remote", "add", "origin", remote]); + yield* git(source, ["push", "-u", "origin", "main"]); + yield* git(source, ["checkout", "-b", "feature/remote-base-only"]); + yield* writeTextFile(path.join(source, "feature.txt"), "ahead of origin/main\n"); + yield* git(source, ["add", "feature.txt"]); + yield* git(source, ["commit", "-m", "feature commit"]); + yield* git(source, ["branch", "-D", "main"]); + + const core = yield* GitCore; + const details = yield* core.statusDetails(source); + expect(details.branch).toBe("feature/remote-base-only"); + expect(details.hasUpstream).toBe(false); + expect(details.aheadCount).toBe(1); + expect(details.behindCount).toBe(0); + }), + ); + it.effect("skips push when no upstream is configured and branch is not ahead of base", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); diff --git a/apps/server/src/git/Layers/GitCore.ts b/apps/server/src/git/Layers/GitCore.ts index 570b214fbbc2f0396c15e25d8852481efd10b1ca..e362765758b590074882227dcf788ef40ab0b413 100644 --- a/apps/server/src/git/Layers/GitCore.ts +++ b/apps/server/src/git/Layers/GitCore.ts @@ -368,6 +368,19 @@ const makeGitCore = Effect.gen(function* () { allowNonZeroExit: true, }).pipe(Effect.map((result) => result.code === 0)); + const remoteBranchExists = ( + cwd: string, + branch: string, + ): Effect.Effect => + executeGit( + "GitCore.remoteBranchExists", + cwd, + ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${branch}`], + { + allowNonZeroExit: true, + }, + ).pipe(Effect.map((result) => result.code === 0)); + const resolveBaseBranchForNoUpstream = ( cwd: string, branch: string, @@ -388,12 +401,23 @@ const makeGitCore = Effect.gen(function* () { ]; for (const candidate of candidates) { - if (!candidate || candidate === branch) { + if (!candidate) { continue; } - if (yield* branchExists(cwd, candidate)) { - return candidate; + const normalizedCandidate = candidate.startsWith("origin/") + ? candidate.slice("origin/".length) + : candidate; + if (normalizedCandidate.length === 0 || normalizedCandidate === branch) { + continue; + } + + if (yield* branchExists(cwd, normalizedCandidate)) { + return normalizedCandidate; + } + + if (yield* remoteBranchExists(cwd, normalizedCandidate)) { + return `origin/${normalizedCandidate}`; } }