| author | |
| committer | |
| log | a81a52afbb4e03ba82b2743801772151ed8c7d70 |
| tree | a8592f61ceedce664445b6f5c7a3b74a40e9bdd7 |
| parent | a56b0cd7178dfef299aa49b26773a80a34abe205 |
| signature | Signed by PGP key B5690EEEBB952194 |
6 files changed, 179 insertions(+), 109 deletions(-)
apps/server/src/git/GitWorkflowService.ts+9| ... | @@ -73,6 +73,11 @@ export class GitWorkflowService extends Context.Service< | ... | @@ -73,6 +73,11 @@ export class GitWorkflowService extends Context.Service< |
| 73 | readonly cwd: string; | 73 | readonly cwd: string; |
| 74 | readonly remoteName: string; | 74 | readonly remoteName: string; |
| 75 | }) => Effect.Effect<boolean, GitCommandError>; | 75 | }) => Effect.Effect<boolean, GitCommandError>; |
| 76 | readonly remoteBranchExists: (input: { | ||
| 77 | readonly cwd: string; | ||
| 78 | readonly remoteName: string; | ||
| 79 | readonly refName: string; | ||
| 80 | }) => Effect.Effect<boolean, GitCommandError>; | ||
| 76 | readonly resolveRemoteTrackingCommit: (input: { | 81 | readonly resolveRemoteTrackingCommit: (input: { |
| 77 | readonly cwd: string; | 82 | readonly cwd: string; |
| 78 | readonly refName: string; | 83 | readonly refName: string; |
| ... | @@ -314,6 +319,10 @@ export const make = Effect.gen(function* () { | ... | @@ -314,6 +319,10 @@ export const make = Effect.gen(function* () { |
| 314 | ensureGitCommand("GitWorkflowService.remoteExists", input.cwd).pipe( | 319 | ensureGitCommand("GitWorkflowService.remoteExists", input.cwd).pipe( |
| 315 | Effect.andThen(git.remoteExists(input)), | 320 | Effect.andThen(git.remoteExists(input)), |
| 316 | ), | 321 | ), |
| 322 | remoteBranchExists: (input) => | ||
| 323 | ensureGitCommand("GitWorkflowService.remoteBranchExists", input.cwd).pipe( | ||
| 324 | Effect.andThen(git.remoteBranchExists(input)), | ||
| 325 | ), | ||
| 317 | resolveRemoteTrackingCommit: (input) => | 326 | resolveRemoteTrackingCommit: (input) => |
| 318 | ensureGitCommand("GitWorkflowService.resolveRemoteTrackingCommit", input.cwd).pipe( | 327 | ensureGitCommand("GitWorkflowService.resolveRemoteTrackingCommit", input.cwd).pipe( |
| 319 | Effect.andThen(git.resolveRemoteTrackingCommit(input)), | 328 | Effect.andThen(git.resolveRemoteTrackingCommit(input)), |
apps/server/src/server.test.ts+120-92| ... | @@ -8378,6 +8378,13 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -8378,6 +8378,13 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 8378 | bootstrapGitOperations.push("fetch"); | 8378 | bootstrapGitOperations.push("fetch"); |
| 8379 | }), | 8379 | }), |
| 8380 | ); | 8380 | ); |
| 8381 | const remoteBranchExists = vi.fn( | ||
| 8382 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteBranchExists"]>[0]) => | ||
| 8383 | Effect.sync(() => { | ||
| 8384 | bootstrapGitOperations.push("remote-branch-exists"); | ||
| 8385 | return true; | ||
| 8386 | }), | ||
| 8387 | ); | ||
| 8381 | const fetchedOriginCommit = "0123456789abcdef0123456789abcdef01234567"; | 8388 | const fetchedOriginCommit = "0123456789abcdef0123456789abcdef01234567"; |
| 8382 | const resolveRemoteTrackingCommit = vi.fn( | 8389 | const resolveRemoteTrackingCommit = vi.fn( |
| 8383 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["resolveRemoteTrackingCommit"]>[0]) => | 8390 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["resolveRemoteTrackingCommit"]>[0]) => |
| ... | @@ -8421,6 +8428,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -8421,6 +8428,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 8421 | gitVcsDriver: { | 8428 | gitVcsDriver: { |
| 8422 | remoteExists, | 8429 | remoteExists, |
| 8423 | fetchRemote, | 8430 | fetchRemote, |
| 8431 | remoteBranchExists, | ||
| 8424 | resolveRemoteTrackingCommit, | 8432 | resolveRemoteTrackingCommit, |
| 8425 | createWorktree, | 8433 | createWorktree, |
| 8426 | }, | 8434 | }, |
| ... | @@ -8504,6 +8512,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -8504,6 +8512,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 8504 | cwd: "/tmp/project", | 8512 | cwd: "/tmp/project", |
| 8505 | remoteName: "origin", | 8513 | remoteName: "origin", |
| 8506 | }); | 8514 | }); |
| 8515 | assert.deepEqual(remoteBranchExists.mock.calls[0]?.[0], { | ||
| 8516 | cwd: "/tmp/project", | ||
| 8517 | remoteName: "origin", | ||
| 8518 | refName: "main", | ||
| 8519 | }); | ||
| 8507 | assert.deepEqual(resolveRemoteTrackingCommit.mock.calls[0]?.[0], { | 8520 | assert.deepEqual(resolveRemoteTrackingCommit.mock.calls[0]?.[0], { |
| 8508 | cwd: "/tmp/project", | 8521 | cwd: "/tmp/project", |
| 8509 | refName: "main", | 8522 | refName: "main", |
| ... | @@ -8512,6 +8525,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -8512,6 +8525,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 8512 | assert.deepEqual(bootstrapGitOperations, [ | 8525 | assert.deepEqual(bootstrapGitOperations, [ |
| 8513 | "remote-exists", | 8526 | "remote-exists", |
| 8514 | "fetch", | 8527 | "fetch", |
| 8528 | "remote-branch-exists", | ||
| 8515 | "resolve-remote-commit", | 8529 | "resolve-remote-commit", |
| 8516 | "create-worktree", | 8530 | "create-worktree", |
| 8517 | ]); | 8531 | ]); |
| ... | @@ -8539,108 +8553,122 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -8539,108 +8553,122 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 8539 | }).pipe(Effect.provide(NodeHttpServer.layerTest)), | 8553 | }).pipe(Effect.provide(NodeHttpServer.layerTest)), |
| 8540 | ); | 8554 | ); |
| 8541 | 8555 | ||
| 8542 | it.effect( | 8556 | it.effect.each([ |
| 8543 | "falls back to the local base branch when startFromOrigin is set but no origin remote exists", | 8557 | { caseName: "the origin remote is missing", hasOrigin: false }, |
| 8544 | () => | 8558 | { caseName: "the base branch exists only locally", hasOrigin: true }, |
| 8545 | Effect.gen(function* () { | 8559 | ])("falls back to the local base branch when $caseName", ({ hasOrigin }) => |
| 8546 | const dispatchedCommands: Array<OrchestrationCommand> = []; | 8560 | Effect.gen(function* () { |
| 8547 | const remoteExists = vi.fn( | 8561 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| 8548 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteExists"]>[0]) => | 8562 | const remoteExists = vi.fn( |
| 8549 | Effect.succeed(false), | 8563 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteExists"]>[0]) => |
| 8550 | ); | 8564 | Effect.succeed(hasOrigin), |
| 8551 | const fetchRemote = vi.fn( | 8565 | ); |
| 8552 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["fetchRemote"]>[0]) => Effect.void, | 8566 | const fetchRemote = vi.fn( |
| 8553 | ); | 8567 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["fetchRemote"]>[0]) => Effect.void, |
| 8554 | const resolveRemoteTrackingCommit = vi.fn( | 8568 | ); |
| 8555 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["resolveRemoteTrackingCommit"]>[0]) => | 8569 | const resolveRemoteTrackingCommit = vi.fn( |
| 8556 | Effect.succeed({ | 8570 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["resolveRemoteTrackingCommit"]>[0]) => |
| 8557 | commitSha: "0123456789abcdef0123456789abcdef01234567", | 8571 | Effect.succeed({ |
| 8558 | remoteRefName: "origin/main", | 8572 | commitSha: "0123456789abcdef0123456789abcdef01234567", |
| 8559 | }), | 8573 | remoteRefName: "origin/main", |
| 8560 | ); | 8574 | }), |
| 8561 | const createWorktree = vi.fn( | 8575 | ); |
| 8562 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) => | 8576 | const remoteBranchExists = vi.fn( |
| 8563 | Effect.succeed({ | 8577 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["remoteBranchExists"]>[0]) => |
| 8564 | worktree: { | 8578 | Effect.succeed(false), |
| 8565 | refName: "t3code/bootstrap-refName", | 8579 | ); |
| 8566 | path: "/tmp/bootstrap-worktree", | 8580 | const createWorktree = vi.fn( |
| 8567 | }, | 8581 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) => |
| 8568 | }), | 8582 | Effect.succeed({ |
| 8569 | ); | 8583 | worktree: { |
| 8570 | 8584 | refName: "t3code/bootstrap-refName", | |
| 8571 | yield* buildAppUnderTest({ | 8585 | path: "/tmp/bootstrap-worktree", |
| 8572 | layers: { | ||
| 8573 | gitVcsDriver: { | ||
| 8574 | remoteExists, | ||
| 8575 | fetchRemote, | ||
| 8576 | resolveRemoteTrackingCommit, | ||
| 8577 | createWorktree, | ||
| 8578 | }, | ||
| 8579 | orchestrationEngine: { | ||
| 8580 | dispatch: (command) => | ||
| 8581 | Effect.sync(() => { | ||
| 8582 | dispatchedCommands.push(command); | ||
| 8583 | return { sequence: dispatchedCommands.length }; | ||
| 8584 | }), | ||
| 8585 | readEvents: () => Stream.empty, | ||
| 8586 | }, | 8586 | }, |
| 8587 | }), | ||
| 8588 | ); | ||
| 8589 | |||
| 8590 | yield* buildAppUnderTest({ | ||
| 8591 | layers: { | ||
| 8592 | gitVcsDriver: { | ||
| 8593 | remoteExists, | ||
| 8594 | fetchRemote, | ||
| 8595 | remoteBranchExists, | ||
| 8596 | resolveRemoteTrackingCommit, | ||
| 8597 | createWorktree, | ||
| 8587 | }, | 8598 | }, |
| 8588 | }); | 8599 | orchestrationEngine: { |
| 8600 | dispatch: (command) => | ||
| 8601 | Effect.sync(() => { | ||
| 8602 | dispatchedCommands.push(command); | ||
| 8603 | return { sequence: dispatchedCommands.length }; | ||
| 8604 | }), | ||
| 8605 | readEvents: () => Stream.empty, | ||
| 8606 | }, | ||
| 8607 | }, | ||
| 8608 | }); | ||
| 8589 | 8609 | ||
| 8590 | const createdAt = "2026-01-01T00:00:00.000Z"; | 8610 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 8591 | const wsUrl = yield* getWsServerUrl("/ws"); | 8611 | const wsUrl = yield* getWsServerUrl("/ws"); |
| 8592 | yield* Effect.scoped( | 8612 | yield* Effect.scoped( |
| 8593 | withWsRpcClient(wsUrl, (client) => | 8613 | withWsRpcClient(wsUrl, (client) => |
| 8594 | client[ORCHESTRATION_WS_METHODS.dispatchCommand]({ | 8614 | client[ORCHESTRATION_WS_METHODS.dispatchCommand]({ |
| 8595 | type: "thread.turn.start", | 8615 | type: "thread.turn.start", |
| 8596 | commandId: CommandId.make("cmd-bootstrap-turn-start-no-origin"), | 8616 | commandId: CommandId.make("cmd-bootstrap-turn-start-no-origin"), |
| 8597 | threadId: ThreadId.make("thread-bootstrap-no-origin"), | 8617 | threadId: ThreadId.make("thread-bootstrap-no-origin"), |
| 8598 | message: { | 8618 | message: { |
| 8599 | messageId: MessageId.make("msg-bootstrap-no-origin"), | 8619 | messageId: MessageId.make("msg-bootstrap-no-origin"), |
| 8600 | role: "user", | 8620 | role: "user", |
| 8601 | text: "hello", | 8621 | text: "hello", |
| 8602 | attachments: [], | 8622 | attachments: [], |
| 8623 | }, | ||
| 8624 | modelSelection: defaultModelSelection, | ||
| 8625 | runtimeMode: "full-access", | ||
| 8626 | interactionMode: "default", | ||
| 8627 | bootstrap: { | ||
| 8628 | createThread: { | ||
| 8629 | projectId: defaultProjectId, | ||
| 8630 | title: "Bootstrap Thread", | ||
| 8631 | modelSelection: defaultModelSelection, | ||
| 8632 | runtimeMode: "full-access", | ||
| 8633 | interactionMode: "default", | ||
| 8634 | branch: "main", | ||
| 8635 | worktreePath: null, | ||
| 8636 | createdAt, | ||
| 8603 | }, | 8637 | }, |
| 8604 | modelSelection: defaultModelSelection, | 8638 | prepareWorktree: { |
| 8605 | runtimeMode: "full-access", | 8639 | projectCwd: "/tmp/project", |
| 8606 | interactionMode: "default", | 8640 | baseBranch: "main", |
| 8607 | bootstrap: { | 8641 | branch: "t3code/bootstrap-refName", |
| 8608 | createThread: { | 8642 | startFromOrigin: true, |
| 8609 | projectId: defaultProjectId, | ||
| 8610 | title: "Bootstrap Thread", | ||
| 8611 | modelSelection: defaultModelSelection, | ||
| 8612 | runtimeMode: "full-access", | ||
| 8613 | interactionMode: "default", | ||
| 8614 | branch: "main", | ||
| 8615 | worktreePath: null, | ||
| 8616 | createdAt, | ||
| 8617 | }, | ||
| 8618 | prepareWorktree: { | ||
| 8619 | projectCwd: "/tmp/project", | ||
| 8620 | baseBranch: "main", | ||
| 8621 | branch: "t3code/bootstrap-refName", | ||
| 8622 | startFromOrigin: true, | ||
| 8623 | }, | ||
| 8624 | }, | 8643 | }, |
| 8625 | createdAt, | 8644 | }, |
| 8626 | }), | 8645 | createdAt, |
| 8627 | ), | 8646 | }), |
| 8628 | ); | 8647 | ), |
| 8648 | ); | ||
| 8629 | 8649 | ||
| 8630 | assert.deepEqual(remoteExists.mock.calls[0]?.[0], { | 8650 | assert.deepEqual(remoteExists.mock.calls[0]?.[0], { |
| 8651 | cwd: "/tmp/project", | ||
| 8652 | remoteName: "origin", | ||
| 8653 | }); | ||
| 8654 | assert.equal(fetchRemote.mock.calls.length, hasOrigin ? 1 : 0); | ||
| 8655 | assert.equal(remoteBranchExists.mock.calls.length, hasOrigin ? 1 : 0); | ||
| 8656 | if (hasOrigin) { | ||
| 8657 | assert.deepEqual(remoteBranchExists.mock.calls[0]?.[0], { | ||
| 8631 | cwd: "/tmp/project", | 8658 | cwd: "/tmp/project", |
| 8632 | remoteName: "origin", | 8659 | remoteName: "origin", |
| 8633 | }); | ||
| 8634 | assert.equal(fetchRemote.mock.calls.length, 0); | ||
| 8635 | assert.equal(resolveRemoteTrackingCommit.mock.calls.length, 0); | ||
| 8636 | assert.deepEqual(createWorktree.mock.calls[0]?.[0], { | ||
| 8637 | cwd: "/tmp/project", | ||
| 8638 | refName: "main", | 8660 | refName: "main", |
| 8639 | newRefName: "t3code/bootstrap-refName", | ||
| 8640 | baseRefName: "main", | ||
| 8641 | path: null, | ||
| 8642 | }); | 8661 | }); |
| 8643 | }).pipe(Effect.provide(NodeHttpServer.layerTest)), | 8662 | } |
| 8663 | assert.equal(resolveRemoteTrackingCommit.mock.calls.length, 0); | ||
| 8664 | assert.deepEqual(createWorktree.mock.calls[0]?.[0], { | ||
| 8665 | cwd: "/tmp/project", | ||
| 8666 | refName: "main", | ||
| 8667 | newRefName: "t3code/bootstrap-refName", | ||
| 8668 | baseRefName: "main", | ||
| 8669 | path: null, | ||
| 8670 | }); | ||
| 8671 | }).pipe(Effect.provide(NodeHttpServer.layerTest)), | ||
| 8644 | ); | 8672 | ); |
| 8645 | 8673 | ||
| 8646 | it.effect("records setup-script failures without aborting bootstrap turn start", () => | 8674 | it.effect("records setup-script failures without aborting bootstrap turn start", () => |
apps/server/src/vcs/GitVcsDriver.ts+7| ... | @@ -204,6 +204,10 @@ export interface GitRemoteExistsInput { | ... | @@ -204,6 +204,10 @@ export interface GitRemoteExistsInput { |
| 204 | remoteName: string; | 204 | remoteName: string; |
| 205 | } | 205 | } |
| 206 | 206 | ||
| 207 | export interface GitRemoteBranchExistsInput extends GitRemoteExistsInput { | ||
| 208 | refName: string; | ||
| 209 | } | ||
| 210 | |||
| 207 | export interface GitResolveRemoteTrackingCommitInput { | 211 | export interface GitResolveRemoteTrackingCommitInput { |
| 208 | cwd: string; | 212 | cwd: string; |
| 209 | refName: string; | 213 | refName: string; |
| ... | @@ -295,6 +299,9 @@ export class GitVcsDriver extends Context.Service< | ... | @@ -295,6 +299,9 @@ export class GitVcsDriver extends Context.Service< |
| 295 | ) => Effect.Effect<string | null, GitCommandError>; | 299 | ) => Effect.Effect<string | null, GitCommandError>; |
| 296 | readonly fetchRemote: (input: GitFetchRemoteInput) => Effect.Effect<void, GitCommandError>; | 300 | readonly fetchRemote: (input: GitFetchRemoteInput) => Effect.Effect<void, GitCommandError>; |
| 297 | readonly remoteExists: (input: GitRemoteExistsInput) => Effect.Effect<boolean, GitCommandError>; | 301 | readonly remoteExists: (input: GitRemoteExistsInput) => Effect.Effect<boolean, GitCommandError>; |
| 302 | readonly remoteBranchExists: ( | ||
| 303 | input: GitRemoteBranchExistsInput, | ||
| 304 | ) => Effect.Effect<boolean, GitCommandError>; | ||
| 298 | readonly resolveRemoteTrackingCommit: ( | 305 | readonly resolveRemoteTrackingCommit: ( |
| 299 | input: GitResolveRemoteTrackingCommitInput, | 306 | input: GitResolveRemoteTrackingCommitInput, |
| 300 | ) => Effect.Effect<GitResolveRemoteTrackingCommitResult, GitCommandError>; | 307 | ) => Effect.Effect<GitResolveRemoteTrackingCommitResult, GitCommandError>; |
apps/server/src/vcs/GitVcsDriverCore.test.ts+17| ... | @@ -1665,6 +1665,23 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { | ... | @@ -1665,6 +1665,23 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 1665 | const driver = yield* GitVcsDriver.GitVcsDriver; | 1665 | const driver = yield* GitVcsDriver.GitVcsDriver; |
| 1666 | yield* driver.fetchRemote({ cwd, remoteName: "origin" }); | 1666 | yield* driver.fetchRemote({ cwd, remoteName: "origin" }); |
| 1667 | 1667 | ||
| 1668 | assert.equal( | ||
| 1669 | yield* driver.remoteBranchExists({ | ||
| 1670 | cwd, | ||
| 1671 | remoteName: "origin", | ||
| 1672 | refName: initialBranch, | ||
| 1673 | }), | ||
| 1674 | true, | ||
| 1675 | ); | ||
| 1676 | assert.equal( | ||
| 1677 | yield* driver.remoteBranchExists({ | ||
| 1678 | cwd, | ||
| 1679 | remoteName: "origin", | ||
| 1680 | refName: "local-only", | ||
| 1681 | }), | ||
| 1682 | false, | ||
| 1683 | ); | ||
| 1684 | |||
| 1668 | const resolvedBase = yield* driver.resolveRemoteTrackingCommit({ | 1685 | const resolvedBase = yield* driver.resolveRemoteTrackingCommit({ |
| 1669 | cwd, | 1686 | cwd, |
| 1670 | refName: initialBranch, | 1687 | refName: initialBranch, |
apps/server/src/vcs/GitVcsDriverCore.ts+14-11| ... | @@ -1292,15 +1292,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* | ... | @@ -1292,15 +1292,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* |
| 1292 | }), | 1292 | }), |
| 1293 | ); | 1293 | ); |
| 1294 | 1294 | ||
| 1295 | const remoteBranchExists = ( | 1295 | const remoteBranchExists: GitVcsDriver.GitVcsDriver["Service"]["remoteBranchExists"] = (input) => |
| 1296 | cwd: string, | ||
| 1297 | remoteName: string, | ||
| 1298 | refName: string, | ||
| 1299 | ): Effect.Effect<boolean, GitCommandError> => | ||
| 1300 | executeGit( | 1296 | executeGit( |
| 1301 | "GitVcsDriver.remoteBranchExists", | 1297 | "GitVcsDriver.remoteBranchExists", |
| 1302 | cwd, | 1298 | input.cwd, |
| 1303 | ["show-ref", "--verify", "--quiet", `refs/remotes/${remoteName}/${refName}`], | 1299 | ["show-ref", "--verify", "--quiet", `refs/remotes/${input.remoteName}/${input.refName}`], |
| 1304 | { | 1300 | { |
| 1305 | allowNonZeroExit: true, | 1301 | allowNonZeroExit: true, |
| 1306 | }, | 1302 | }, |
| ... | @@ -1447,7 +1443,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* | ... | @@ -1447,7 +1443,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* |
| 1447 | 1443 | ||
| 1448 | if ( | 1444 | if ( |
| 1449 | primaryRemoteName && | 1445 | primaryRemoteName && |
| 1450 | (yield* remoteBranchExists(cwd, primaryRemoteName, normalizedCandidate)) | 1446 | (yield* remoteBranchExists({ |
| 1447 | cwd, | ||
| 1448 | remoteName: primaryRemoteName, | ||
| 1449 | refName: normalizedCandidate, | ||
| 1450 | })) | ||
| 1451 | ) { | 1451 | ) { |
| 1452 | return `${primaryRemoteName}/${normalizedCandidate}`; | 1452 | return `${primaryRemoteName}/${normalizedCandidate}`; |
| 1453 | } | 1453 | } |
| ... | @@ -1965,9 +1965,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* | ... | @@ -1965,9 +1965,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* |
| 1965 | }; | 1965 | }; |
| 1966 | } | 1966 | } |
| 1967 | 1967 | ||
| 1968 | const hasRemoteBranch = yield* remoteBranchExists(cwd, publishRemoteName, branch).pipe( | 1968 | const hasRemoteBranch = yield* remoteBranchExists({ |
| 1969 | Effect.orElseSucceed(() => false), | 1969 | cwd, |
| 1970 | ); | 1970 | remoteName: publishRemoteName, |
| 1971 | refName: branch, | ||
| 1972 | }).pipe(Effect.orElseSucceed(() => false)); | ||
| 1971 | if (hasRemoteBranch) { | 1973 | if (hasRemoteBranch) { |
| 1972 | return { | 1974 | return { |
| 1973 | status: "skipped_up_to_date" as const, | 1975 | status: "skipped_up_to_date" as const, |
| ... | @@ -3315,6 +3317,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* | ... | @@ -3315,6 +3317,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* |
| 3315 | resolveDefaultBranchName, | 3317 | resolveDefaultBranchName, |
| 3316 | fetchRemote: (input) => withListRefsInvalidation(input.cwd, fetchRemote(input)), | 3318 | fetchRemote: (input) => withListRefsInvalidation(input.cwd, fetchRemote(input)), |
| 3317 | remoteExists, | 3319 | remoteExists, |
| 3320 | remoteBranchExists, | ||
| 3318 | resolveRemoteTrackingCommit, | 3321 | resolveRemoteTrackingCommit, |
| 3319 | fetchRemoteBranch: (input) => withListRefsInvalidation(input.cwd, fetchRemoteBranch(input)), | 3322 | fetchRemoteBranch: (input) => withListRefsInvalidation(input.cwd, fetchRemoteBranch(input)), |
| 3320 | fetchRemoteTrackingBranch: (input) => | 3323 | fetchRemoteTrackingBranch: (input) => |
apps/server/src/ws.ts+12-6| ... | @@ -1094,9 +1094,8 @@ const makeWsRpcLayer = ( | ... | @@ -1094,9 +1094,8 @@ const makeWsRpcLayer = ( |
| 1094 | 1094 | ||
| 1095 | if (bootstrap?.prepareWorktree) { | 1095 | if (bootstrap?.prepareWorktree) { |
| 1096 | let worktreeBaseRef = bootstrap.prepareWorktree.baseBranch; | 1096 | let worktreeBaseRef = bootstrap.prepareWorktree.baseBranch; |
| 1097 | // "Start from origin" is a stored default; repos without an | 1097 | // "Start from origin" is a stored default; repos without the |
| 1098 | // origin remote fall back to the local base branch instead of | 1098 | // requested remote branch fall back to the local base branch. |
| 1099 | // failing the whole bootstrap on `git fetch origin`. | ||
| 1100 | const startFromOrigin = | 1099 | const startFromOrigin = |
| 1101 | bootstrap.prepareWorktree.startFromOrigin === true && | 1100 | bootstrap.prepareWorktree.startFromOrigin === true && |
| 1102 | (yield* gitWorkflow.remoteExists({ | 1101 | (yield* gitWorkflow.remoteExists({ |
| ... | @@ -1108,12 +1107,19 @@ const makeWsRpcLayer = ( | ... | @@ -1108,12 +1107,19 @@ const makeWsRpcLayer = ( |
| 1108 | cwd: bootstrap.prepareWorktree.projectCwd, | 1107 | cwd: bootstrap.prepareWorktree.projectCwd, |
| 1109 | remoteName: "origin", | 1108 | remoteName: "origin", |
| 1110 | }); | 1109 | }); |
| 1111 | const resolvedRemoteBase = yield* gitWorkflow.resolveRemoteTrackingCommit({ | 1110 | const remoteBaseExists = yield* gitWorkflow.remoteBranchExists({ |
| 1112 | cwd: bootstrap.prepareWorktree.projectCwd, | 1111 | cwd: bootstrap.prepareWorktree.projectCwd, |
| 1113 | refName: bootstrap.prepareWorktree.baseBranch, | 1112 | refName: bootstrap.prepareWorktree.baseBranch, |
| 1114 | fallbackRemoteName: "origin", | 1113 | remoteName: "origin", |
| 1115 | }); | 1114 | }); |
| 1116 | worktreeBaseRef = resolvedRemoteBase.commitSha; | 1115 | if (remoteBaseExists) { |
| 1116 | const resolvedRemoteBase = yield* gitWorkflow.resolveRemoteTrackingCommit({ | ||
| 1117 | cwd: bootstrap.prepareWorktree.projectCwd, | ||
| 1118 | refName: bootstrap.prepareWorktree.baseBranch, | ||
| 1119 | fallbackRemoteName: "origin", | ||
| 1120 | }); | ||
| 1121 | worktreeBaseRef = resolvedRemoteBase.commitSha; | ||
| 1122 | } | ||
| 1117 | } | 1123 | } |
| 1118 | const worktree = yield* gitWorkflow.createWorktree({ | 1124 | const worktree = yield* gitWorkflow.createWorktree({ |
| 1119 | cwd: bootstrap.prepareWorktree.projectCwd, | 1125 | cwd: bootstrap.prepareWorktree.projectCwd, |