| 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 | 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 | 2819 | it.effect("launches setup only when creating a new PR worktree", () => |
| 2761 | 2820 | Effect.gen(function* () { |
| 2762 | 2821 | const repoDir = yield* makeTempDir("t3code-git-manager-"); |
apps/server/src/git/GitManager.ts+36-13| ... | ... | @@ -41,7 +41,7 @@ import { |
| 41 | 41 | type ChangeRequestTerminology, |
| 42 | 42 | } from "@t3tools/shared/sourceControl"; |
| 43 | 43 | |
| 44 | import { GitManagerError } from "@t3tools/contracts"; | |
| 44 | import { GitManagerError, GitPullRequestMaterializationError } from "@t3tools/contracts"; | |
| 45 | 45 | import * as TextGeneration from "../textGeneration/TextGeneration.ts"; |
| 46 | 46 | import * as ProjectSetupScriptRunner from "../project/ProjectSetupScriptRunner.ts"; |
| 47 | 47 | import { extractBranchNameFromRemoteRef } from "./remoteRefs.ts"; |
| ... | ... | @@ -631,9 +631,12 @@ export const make = Effect.gen(function* () { |
| 631 | 631 | ) => |
| 632 | 632 | configurePullRequestHeadUpstreamBase(cwd, pullRequest, localBranch).pipe( |
| 633 | 633 | Effect.catch((error) => |
| 634 | Effect.logWarning( | |
| 635 | `GitManager.configurePullRequestHeadUpstream: failed to configure upstream for ${localBranch} -> ${pullRequest.headBranch} in ${cwd}: ${error.message}`, | |
| 636 | ).pipe(Effect.asVoid), | |
| 634 | Effect.logWarning("GitManager.configurePullRequestHeadUpstream failed", { | |
| 635 | cwd, | |
| 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 | 694 | localBranch = pullRequest.headBranch, |
| 692 | 695 | ) => |
| 693 | 696 | materializePullRequestHeadBranchBase(cwd, pullRequest, localBranch).pipe( |
| 694 | Effect.catch(() => | |
| 695 | gitCore.fetchPullRequestBranch({ | |
| 696 | cwd, | |
| 697 | prNumber: pullRequest.number, | |
| 698 | branch: localBranch, | |
| 699 | }), | |
| 697 | Effect.catch((primaryCause) => | |
| 698 | gitCore | |
| 699 | .fetchPullRequestBranch({ | |
| 700 | cwd, | |
| 701 | prNumber: pullRequest.number, | |
| 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 | 723 | const fileSystem = yield* FileSystem.FileSystem; |
| ... | ... | @@ -1452,9 +1473,11 @@ export const make = Effect.gen(function* () { |
| 1452 | 1473 | }) |
| 1453 | 1474 | .pipe( |
| 1454 | 1475 | Effect.catch((error) => |
| 1455 | Effect.logWarning( | |
| 1456 | `GitManager.preparePullRequestThread: failed to launch worktree setup script for thread ${input.threadId} in ${worktreePath}: ${error.message}`, | |
| 1457 | ).pipe(Effect.asVoid), | |
| 1476 | Effect.logWarning("GitManager.preparePullRequestThread setup script failed", { | |
| 1477 | threadId: input.threadId, | |
| 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 | 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 | 380 | export const GitManagerServiceError = Schema.Union([ |
| 365 | 381 | GitManagerError, |
| 382 | GitPullRequestMaterializationError, | |
| 366 | 383 | GitCommandError, |
| 367 | 384 | SourceControlProviderError, |
| 368 | 385 | TextGenerationError, |