| author | |
| committer | |
| log | 5edf7c56bafeeaf4d5af1a63f010dde45e6652dd |
| tree | 50c9a39197c22720b57ab23510ecc7165545cf85 |
| parent | 90dc76b11eac811e8bc89cad3f60bb5c7b56514f |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>3 files changed, 112 insertions(+), 13 deletions(-)
apps/server/src/git/GitManager.test.ts+59| ... | @@ -2757,6 +2757,65 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2757,6 +2757,65 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2757 | }), | 2757 | }), |
| 2758 | ); | 2758 | ); |
| 2759 | 2759 | ||
| 2760 | it.effect("preserves both branch materialization failures when the fallback also fails", () => | ||
| 2761 | Effect.gen(function* () { | ||
| 2762 | const repoDir = yield* makeTempDir("t3code-git-manager-"); | ||
| 2763 | yield* initRepo(repoDir); | ||
| 2764 | const originDir = yield* createBareRemote(); | ||
| 2765 | yield* runGit(repoDir, ["remote", "add", "origin", originDir]); | ||
| 2766 | yield* runGit(repoDir, ["push", "-u", "origin", "main"]); | ||
| 2767 | |||
| 2768 | const missingForkDir = NodePath.join(repoDir, "missing-fork.git"); | ||
| 2769 | const { manager } = yield* makeManager({ | ||
| 2770 | ghScenario: { | ||
| 2771 | pullRequest: { | ||
| 2772 | number: 93, | ||
| 2773 | title: "Missing fork branch", | ||
| 2774 | url: "https://github.com/pingdotgg/codething-mvp/pull/93", | ||
| 2775 | baseRefName: "main", | ||
| 2776 | headRefName: "feature/missing-fork-branch", | ||
| 2777 | state: "open", | ||
| 2778 | isCrossRepository: true, | ||
| 2779 | headRepositoryNameWithOwner: "octocat/codething-mvp", | ||
| 2780 | headRepositoryOwnerLogin: "octocat", | ||
| 2781 | }, | ||
| 2782 | repositoryCloneUrls: { | ||
| 2783 | "octocat/codething-mvp": { | ||
| 2784 | url: missingForkDir, | ||
| 2785 | sshUrl: missingForkDir, | ||
| 2786 | }, | ||
| 2787 | }, | ||
| 2788 | }, | ||
| 2789 | }); | ||
| 2790 | |||
| 2791 | const error = yield* preparePullRequestThread(manager, { | ||
| 2792 | cwd: repoDir, | ||
| 2793 | reference: "93", | ||
| 2794 | mode: "worktree", | ||
| 2795 | }).pipe(Effect.flip); | ||
| 2796 | |||
| 2797 | if (error._tag !== "GitPullRequestMaterializationError") { | ||
| 2798 | return yield* Effect.die(error); | ||
| 2799 | } | ||
| 2800 | expect(error).toMatchObject({ | ||
| 2801 | cwd: repoDir, | ||
| 2802 | pullRequestNumber: 93, | ||
| 2803 | headRepository: "octocat/codething-mvp", | ||
| 2804 | headBranch: "feature/missing-fork-branch", | ||
| 2805 | localBranch: "t3code/pr-93/feature/missing-fork-branch", | ||
| 2806 | }); | ||
| 2807 | if (!(error.cause instanceof AggregateError)) { | ||
| 2808 | return yield* Effect.die(error.cause); | ||
| 2809 | } | ||
| 2810 | expect(error.cause.errors).toHaveLength(2); | ||
| 2811 | expect(error.cause.errors).toEqual([ | ||
| 2812 | expect.objectContaining({ _tag: "GitCommandError" }), | ||
| 2813 | expect.objectContaining({ _tag: "GitCommandError" }), | ||
| 2814 | ]); | ||
| 2815 | expect(error.cause.cause).toBe(error.cause.errors[0]); | ||
| 2816 | }), | ||
| 2817 | ); | ||
| 2818 | |||
| 2760 | it.effect("launches setup only when creating a new PR worktree", () => | 2819 | it.effect("launches setup only when creating a new PR worktree", () => |
| 2761 | Effect.gen(function* () { | 2820 | Effect.gen(function* () { |
| 2762 | const repoDir = yield* makeTempDir("t3code-git-manager-"); | 2821 | const repoDir = yield* makeTempDir("t3code-git-manager-"); |
apps/server/src/git/GitManager.ts+36-13| ... | @@ -41,7 +41,7 @@ import { | ... | @@ -41,7 +41,7 @@ import { |
| 41 | type ChangeRequestTerminology, | 41 | type ChangeRequestTerminology, |
| 42 | } from "@t3tools/shared/sourceControl"; | 42 | } from "@t3tools/shared/sourceControl"; |
| 43 | 43 | ||
| 44 | import { GitManagerError } from "@t3tools/contracts"; | 44 | import { GitManagerError, GitPullRequestMaterializationError } from "@t3tools/contracts"; |
| 45 | import * as TextGeneration from "../textGeneration/TextGeneration.ts"; | 45 | import * as TextGeneration from "../textGeneration/TextGeneration.ts"; |
| 46 | import * as ProjectSetupScriptRunner from "../project/ProjectSetupScriptRunner.ts"; | 46 | import * as ProjectSetupScriptRunner from "../project/ProjectSetupScriptRunner.ts"; |
| 47 | import { extractBranchNameFromRemoteRef } from "./remoteRefs.ts"; | 47 | import { extractBranchNameFromRemoteRef } from "./remoteRefs.ts"; |
| ... | @@ -631,9 +631,12 @@ export const make = Effect.gen(function* () { | ... | @@ -631,9 +631,12 @@ export const make = Effect.gen(function* () { |
| 631 | ) => | 631 | ) => |
| 632 | configurePullRequestHeadUpstreamBase(cwd, pullRequest, localBranch).pipe( | 632 | configurePullRequestHeadUpstreamBase(cwd, pullRequest, localBranch).pipe( |
| 633 | Effect.catch((error) => | 633 | Effect.catch((error) => |
| 634 | Effect.logWarning( | 634 | Effect.logWarning("GitManager.configurePullRequestHeadUpstream failed", { |
| 635 | `GitManager.configurePullRequestHeadUpstream: failed to configure upstream for ${localBranch} -> ${pullRequest.headBranch} in ${cwd}: ${error.message}`, | 635 | cwd, |
| 636 | ).pipe(Effect.asVoid), | 636 | localBranch, |
| 637 | headBranch: pullRequest.headBranch, | ||
| 638 | cause: error, | ||
| 639 | }).pipe(Effect.asVoid), | ||
| 637 | ), | 640 | ), |
| 638 | ); | 641 | ); |
| 639 | 642 | ||
| ... | @@ -691,12 +694,30 @@ export const make = Effect.gen(function* () { | ... | @@ -691,12 +694,30 @@ export const make = Effect.gen(function* () { |
| 691 | localBranch = pullRequest.headBranch, | 694 | localBranch = pullRequest.headBranch, |
| 692 | ) => | 695 | ) => |
| 693 | materializePullRequestHeadBranchBase(cwd, pullRequest, localBranch).pipe( | 696 | materializePullRequestHeadBranchBase(cwd, pullRequest, localBranch).pipe( |
| 694 | Effect.catch(() => | 697 | Effect.catch((primaryCause) => |
| 695 | gitCore.fetchPullRequestBranch({ | 698 | gitCore |
| 696 | cwd, | 699 | .fetchPullRequestBranch({ |
| 697 | prNumber: pullRequest.number, | 700 | cwd, |
| 698 | branch: localBranch, | 701 | prNumber: pullRequest.number, |
| 699 | }), | 702 | branch: localBranch, |
| 703 | }) | ||
| 704 | .pipe( | ||
| 705 | Effect.mapError( | ||
| 706 | (fallbackCause) => | ||
| 707 | new GitPullRequestMaterializationError({ | ||
| 708 | cwd, | ||
| 709 | pullRequestNumber: pullRequest.number, | ||
| 710 | headRepository: resolveHeadRepositoryNameWithOwner(pullRequest), | ||
| 711 | headBranch: pullRequest.headBranch, | ||
| 712 | localBranch, | ||
| 713 | cause: new AggregateError( | ||
| 714 | [primaryCause, fallbackCause], | ||
| 715 | `Repository-head and pull-request-ref fetches both failed for pull request #${pullRequest.number}.`, | ||
| 716 | { cause: primaryCause }, | ||
| 717 | ), | ||
| 718 | }), | ||
| 719 | ), | ||
| 720 | ), | ||
| 700 | ), | 721 | ), |
| 701 | ); | 722 | ); |
| 702 | const fileSystem = yield* FileSystem.FileSystem; | 723 | const fileSystem = yield* FileSystem.FileSystem; |
| ... | @@ -1452,9 +1473,11 @@ export const make = Effect.gen(function* () { | ... | @@ -1452,9 +1473,11 @@ export const make = Effect.gen(function* () { |
| 1452 | }) | 1473 | }) |
| 1453 | .pipe( | 1474 | .pipe( |
| 1454 | Effect.catch((error) => | 1475 | Effect.catch((error) => |
| 1455 | Effect.logWarning( | 1476 | Effect.logWarning("GitManager.preparePullRequestThread setup script failed", { |
| 1456 | `GitManager.preparePullRequestThread: failed to launch worktree setup script for thread ${input.threadId} in ${worktreePath}: ${error.message}`, | 1477 | threadId: input.threadId, |
| 1457 | ).pipe(Effect.asVoid), | 1478 | worktreePath, |
| 1479 | cause: error, | ||
| 1480 | }).pipe(Effect.asVoid), | ||
| 1458 | ), | 1481 | ), |
| 1459 | ); | 1482 | ); |
| 1460 | }; | 1483 | }; |
packages/contracts/src/git.ts+17| ... | @@ -361,8 +361,25 @@ export class GitManagerError extends Schema.TaggedErrorClass<GitManagerError>()( | ... | @@ -361,8 +361,25 @@ export class GitManagerError extends Schema.TaggedErrorClass<GitManagerError>()( |
| 361 | } | 361 | } |
| 362 | } | 362 | } |
| 363 | 363 | ||
| 364 | export class GitPullRequestMaterializationError extends Schema.TaggedErrorClass<GitPullRequestMaterializationError>()( | ||
| 365 | "GitPullRequestMaterializationError", | ||
| 366 | { | ||
| 367 | cwd: TrimmedNonEmptyStringSchema, | ||
| 368 | pullRequestNumber: PositiveInt, | ||
| 369 | headRepository: Schema.NullOr(TrimmedNonEmptyStringSchema), | ||
| 370 | headBranch: TrimmedNonEmptyStringSchema, | ||
| 371 | localBranch: TrimmedNonEmptyStringSchema, | ||
| 372 | cause: Schema.Defect(), | ||
| 373 | }, | ||
| 374 | ) { | ||
| 375 | override get message(): string { | ||
| 376 | return `Failed to materialize pull request #${this.pullRequestNumber} branch ${this.headBranch} as ${this.localBranch}.`; | ||
| 377 | } | ||
| 378 | } | ||
| 379 | |||
| 364 | export const GitManagerServiceError = Schema.Union([ | 380 | export const GitManagerServiceError = Schema.Union([ |
| 365 | GitManagerError, | 381 | GitManagerError, |
| 382 | GitPullRequestMaterializationError, | ||
| 366 | GitCommandError, | 383 | GitCommandError, |
| 367 | SourceControlProviderError, | 384 | SourceControlProviderError, |
| 368 | TextGenerationError, | 385 | TextGenerationError, |