| author | |
| committer | |
| log | d0ab35931e90972d4217f319645a36601a1748bf |
| tree | dc2f3312e78e3d6013ea06f756da0a55ae2d0492 |
| parent | 882684a80cb7a74beafc8291e4949e266544c9ec |
Co-authored-by: codex <codex@users.noreply.github.com>2 files changed, 53 insertions(+), 3 deletions(-)
apps/server/src/git/Layers/GitCore.test.ts+26| ... | @@ -1161,6 +1161,32 @@ it.layer(TestLayer)("git integration", (it) => { | ... | @@ -1161,6 +1161,32 @@ it.layer(TestLayer)("git integration", (it) => { |
| 1161 | }), | 1161 | }), |
| 1162 | ); | 1162 | ); |
| 1163 | 1163 | ||
| 1164 | it.effect( | ||
| 1165 | "computes ahead count against origin/default when local default branch is missing", | ||
| 1166 | () => | ||
| 1167 | Effect.gen(function* () { | ||
| 1168 | const remote = yield* makeTmpDir(); | ||
| 1169 | const source = yield* makeTmpDir(); | ||
| 1170 | yield* git(remote, ["init", "--bare"]); | ||
| 1171 | |||
| 1172 | yield* initRepoWithCommit(source); | ||
| 1173 | yield* git(source, ["remote", "add", "origin", remote]); | ||
| 1174 | yield* git(source, ["push", "-u", "origin", "main"]); | ||
| 1175 | yield* git(source, ["checkout", "-b", "feature/remote-base-only"]); | ||
| 1176 | yield* writeTextFile(path.join(source, "feature.txt"), "ahead of origin/main\n"); | ||
| 1177 | yield* git(source, ["add", "feature.txt"]); | ||
| 1178 | yield* git(source, ["commit", "-m", "feature commit"]); | ||
| 1179 | yield* git(source, ["branch", "-D", "main"]); | ||
| 1180 | |||
| 1181 | const core = yield* GitCore; | ||
| 1182 | const details = yield* core.statusDetails(source); | ||
| 1183 | expect(details.branch).toBe("feature/remote-base-only"); | ||
| 1184 | expect(details.hasUpstream).toBe(false); | ||
| 1185 | expect(details.aheadCount).toBe(1); | ||
| 1186 | expect(details.behindCount).toBe(0); | ||
| 1187 | }), | ||
| 1188 | ); | ||
| 1189 | |||
| 1164 | it.effect("skips push when no upstream is configured and branch is not ahead of base", () => | 1190 | it.effect("skips push when no upstream is configured and branch is not ahead of base", () => |
| 1165 | Effect.gen(function* () { | 1191 | Effect.gen(function* () { |
| 1166 | const tmp = yield* makeTmpDir(); | 1192 | const tmp = yield* makeTmpDir(); |
apps/server/src/git/Layers/GitCore.ts+27-3| ... | @@ -368,6 +368,19 @@ const makeGitCore = Effect.gen(function* () { | ... | @@ -368,6 +368,19 @@ const makeGitCore = Effect.gen(function* () { |
| 368 | allowNonZeroExit: true, | 368 | allowNonZeroExit: true, |
| 369 | }).pipe(Effect.map((result) => result.code === 0)); | 369 | }).pipe(Effect.map((result) => result.code === 0)); |
| 370 | 370 | ||
| 371 | const remoteBranchExists = ( | ||
| 372 | cwd: string, | ||
| 373 | branch: string, | ||
| 374 | ): Effect.Effect<boolean, GitCommandError> => | ||
| 375 | executeGit( | ||
| 376 | "GitCore.remoteBranchExists", | ||
| 377 | cwd, | ||
| 378 | ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${branch}`], | ||
| 379 | { | ||
| 380 | allowNonZeroExit: true, | ||
| 381 | }, | ||
| 382 | ).pipe(Effect.map((result) => result.code === 0)); | ||
| 383 | |||
| 371 | const resolveBaseBranchForNoUpstream = ( | 384 | const resolveBaseBranchForNoUpstream = ( |
| 372 | cwd: string, | 385 | cwd: string, |
| 373 | branch: string, | 386 | branch: string, |
| ... | @@ -388,12 +401,23 @@ const makeGitCore = Effect.gen(function* () { | ... | @@ -388,12 +401,23 @@ const makeGitCore = Effect.gen(function* () { |
| 388 | ]; | 401 | ]; |
| 389 | 402 | ||
| 390 | for (const candidate of candidates) { | 403 | for (const candidate of candidates) { |
| 391 | if (!candidate || candidate === branch) { | 404 | if (!candidate) { |
| 405 | continue; | ||
| 406 | } | ||
| 407 | |||
| 408 | const normalizedCandidate = candidate.startsWith("origin/") | ||
| 409 | ? candidate.slice("origin/".length) | ||
| 410 | : candidate; | ||
| 411 | if (normalizedCandidate.length === 0 || normalizedCandidate === branch) { | ||
| 392 | continue; | 412 | continue; |
| 393 | } | 413 | } |
| 394 | 414 | ||
| 395 | if (yield* branchExists(cwd, candidate)) { | 415 | if (yield* branchExists(cwd, normalizedCandidate)) { |
| 396 | return candidate; | 416 | return normalizedCandidate; |
| 417 | } | ||
| 418 | |||
| 419 | if (yield* remoteBranchExists(cwd, normalizedCandidate)) { | ||
| 420 | return `origin/${normalizedCandidate}`; | ||
| 397 | } | 421 | } |
| 398 | } | 422 | } |
| 399 | 423 |