Unified · Split
| @@ -638,6 +638,7 @@ function makeManager(input?: { · 637 unmodified lines | |||
| 638 | ghScenario?: FakeGhScenario; | 638 | ghScenario?: FakeGhScenario; |
| 639 | textGeneration?: Partial<FakeGitTextGeneration>; | 639 | textGeneration?: Partial<FakeGitTextGeneration>; |
| 640 | setupScriptRunner?: ProjectSetupScriptRunner.ProjectSetupScriptRunner["Service"]; | 640 | setupScriptRunner?: ProjectSetupScriptRunner.ProjectSetupScriptRunner["Service"]; |
| 641 | vcsDriverLayer?: Layer.Layer<GitVcsDriver.GitVcsDriver>; | ||
| 641 | }) { | 642 | }) { |
| 642 | const { service: gitHubCli, ghCalls } = createGitHubCliWithFakeGh(input?.ghScenario); | 643 | const { service: gitHubCli, ghCalls } = createGitHubCliWithFakeGh(input?.ghScenario); |
| 643 | const textGeneration = createTextGeneration(input?.textGeneration); | 644 | const textGeneration = createTextGeneration(input?.textGeneration); |
| @@ -647,11 +648,13 @@ function makeManager(input?: { · 3 unmodified lines | |||
| 647 | 648 | ||
| 648 | const serverSettingsLayer = ServerSettings.ServerSettingsService.layerTest(); | 649 | const serverSettingsLayer = ServerSettings.ServerSettingsService.layerTest(); |
| 649 | 650 | ||
| 650 | const vcsDriverLayer = GitVcsDriver.layer.pipe( | 651 | const vcsDriverLayer = |
| 651 | Layer.provideMerge(VcsProcess.layer), | 652 | input?.vcsDriverLayer ?? |
| 652 | Layer.provideMerge(NodeServices.layer), | 653 | GitVcsDriver.layer.pipe( |
| 653 | Layer.provideMerge(serverConfigLayer), | 654 | Layer.provideMerge(VcsProcess.layer), |
| 654 | ); | 655 | Layer.provideMerge(NodeServices.layer), |
| 656 | Layer.provideMerge(serverConfigLayer), | ||
| 657 | ); | ||
| 655 | const sourceControlRegistryLayer = Layer.effect( | 658 | const sourceControlRegistryLayer = Layer.effect( |
| 656 | SourceControlProviderRegistry.SourceControlProviderRegistry, | 659 | SourceControlProviderRegistry.SourceControlProviderRegistry, |
| 657 | GitHubSourceControlProvider.make.pipe( | 660 | GitHubSourceControlProvider.make.pipe( |
| @@ -934,6 +937,30 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { · 276 unmodified lines | |||
| 934 | }), | 937 | }), |
| 935 | ); | 938 | ); |
| 936 | 939 | ||
| 940 | it.effect("does not classify Git command failures from message text", () => | ||
| 941 | Effect.gen(function* () { | ||
| 942 | const cwd = "/workspace"; | ||
| 943 | const cause = new Error("spawn failed"); | ||
| 944 | const commandError = new GitCommandError({ | ||
| 945 | operation: "GitVcsDriver.statusDetails.status", | ||
| 946 | command: "git", | ||
| 947 | cwd, | ||
| 948 | detail: "not a git repository", | ||
| 949 | cause, | ||
| 950 | }); | ||
| 951 | const { manager } = yield* makeManager({ | ||
| 952 | vcsDriverLayer: Layer.mock(GitVcsDriver.GitVcsDriver)({ | ||
| 953 | statusDetailsLocal: () => Effect.fail(commandError), | ||
| 954 | }), | ||
| 955 | }); | ||
| 956 | |||
| 957 | const error = yield* manager.localStatus({ cwd }).pipe(Effect.flip); | ||
| 958 | |||
| 959 | expect(error).toBe(commandError); | ||
| 960 | expect(error.cause).toBe(cause); | ||
| 961 | }), | ||
| 962 | ); | ||
| 963 | |||
| 937 | it.effect("status briefly caches repeated lookups for the same cwd", () => | 964 | it.effect("status briefly caches repeated lookups for the same cwd", () => |
| 938 | Effect.gen(function* () { | 965 | Effect.gen(function* () { |
| 939 | const repoDir = yield* makeTempDir("t3code-git-manager-"); | 966 | const repoDir = yield* makeTempDir("t3code-git-manager-"); |
| @@ -15,7 +15,6 @@ import * as Ref from "effect/Ref"; · 14 unmodified lines | |||
| 15 | import { | 15 | import { |
| 16 | GitActionProgressEvent, | 16 | GitActionProgressEvent, |
| 17 | GitActionProgressPhase, | 17 | GitActionProgressPhase, |
| 18 | GitCommandError, | ||
| 19 | GitPreparePullRequestThreadInput, | 18 | GitPreparePullRequestThreadInput, |
| 20 | GitPreparePullRequestThreadResult, | 19 | GitPreparePullRequestThreadResult, |
| 21 | GitPullRequestRefInput, | 20 | GitPullRequestRefInput, |
| @@ -99,10 +98,6 @@ type StripProgressContext<T> = T extends any ? Omit<T, "actionId" | "cwd" | "act · 77 unmodified lines | |||
| 99 | type GitActionProgressPayload = StripProgressContext<GitActionProgressEvent>; | 98 | type GitActionProgressPayload = StripProgressContext<GitActionProgressEvent>; |
| 100 | type GitActionProgressEmitter = (event: GitActionProgressPayload) => Effect.Effect<void, never>; | 99 | type GitActionProgressEmitter = (event: GitActionProgressPayload) => Effect.Effect<void, never>; |
| 101 | 100 | ||
| 102 | function isNotGitRepositoryError(error: GitCommandError): boolean { | ||
| 103 | return error.message.toLowerCase().includes("not a git repository"); | ||
| 104 | } | ||
| 105 | |||
| 106 | interface OpenPrInfo { | 101 | interface OpenPrInfo { |
| 107 | number: number; | 102 | number: number; |
| 108 | title: string; | 103 | title: string; |
| @@ -727,25 +722,8 @@ export const make = Effect.gen(function* () { · 618 unmodified lines | |||
| 727 | const canonicalizeExistingPath = (value: string) => | 722 | const canonicalizeExistingPath = (value: string) => |
| 728 | fileSystem.realPath(value).pipe(Effect.orElseSucceed(() => value)); | 723 | fileSystem.realPath(value).pipe(Effect.orElseSucceed(() => value)); |
| 729 | const normalizeStatusCacheKey = canonicalizeExistingPath; | 724 | const normalizeStatusCacheKey = canonicalizeExistingPath; |
| 730 | const nonRepositoryStatusDetails = { | ||
| 731 | isRepo: false, | ||
| 732 | hasOriginRemote: false, | ||
| 733 | isDefaultBranch: false, | ||
| 734 | branch: null, | ||
| 735 | upstreamRef: null, | ||
| 736 | hasWorkingTreeChanges: false, | ||
| 737 | workingTree: { files: [], insertions: 0, deletions: 0 }, | ||
| 738 | hasUpstream: false, | ||
| 739 | aheadCount: 0, | ||
| 740 | behindCount: 0, | ||
| 741 | aheadOfDefaultCount: 0, | ||
| 742 | } satisfies GitVcsDriver.GitStatusDetails; | ||
| 743 | const readLocalStatus = Effect.fn("readLocalStatus")(function* (cwd: string) { | 725 | const readLocalStatus = Effect.fn("readLocalStatus")(function* (cwd: string) { |
| 744 | const details = yield* gitCore | 726 | const details = yield* gitCore.statusDetailsLocal(cwd); |
| 745 | .statusDetailsLocal(cwd) | ||
| 746 | .pipe( | ||
| 747 | Effect.catchIf(isNotGitRepositoryError, () => Effect.succeed(nonRepositoryStatusDetails)), | ||
| 748 | ); | ||
| 749 | const hostingProvider = details.isRepo | 727 | const hostingProvider = details.isRepo |
| 750 | ? yield* resolveHostingProvider(cwd, details.branch) | 728 | ? yield* resolveHostingProvider(cwd, details.branch) |
| 751 | : null; | 729 | : null; |
| @@ -772,9 +750,7 @@ export const make = Effect.gen(function* () { · 20 unmodified lines | |||
| 772 | cwd: string, | 750 | cwd: string, |
| 773 | options?: GitVcsDriver.GitRemoteStatusOptions, | 751 | options?: GitVcsDriver.GitRemoteStatusOptions, |
| 774 | ) { | 752 | ) { |
| 775 | const details = yield* gitCore | 753 | const details = yield* gitCore.statusDetailsRemote(cwd, options); |
| 776 | .statusDetailsRemote(cwd, options) | ||
| 777 | .pipe(Effect.catchIf(isNotGitRepositoryError, () => Effect.succeed(null))); | ||
| 778 | if (details === null || !details.isRepo) { | 754 | if (details === null || !details.isRepo) { |
| 779 | return null; | 755 | return null; |
| 780 | } | 756 | } |