| author | |
| committer | |
| log | 9ea443d73afc72cf9fa617bdc1344e67913e4c63 |
| tree | 6ef41cc03f794a7a26968d9ef8d240d0647a78cb |
| parent | 60f7ae86bbeaa95ebdfeb0fd83edc564119258df |
| signature | Signed by PGP key B5690EEEBB952194 |
4 files changed, 61 insertions(+), 7 deletions(-)
apps/server/src/git/Layers/GitManager.test.ts+11-1| ... | ... | @@ -2739,7 +2739,17 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2739 | 2739 | expect.objectContaining({ |
| 2740 | 2740 | kind: "phase_started", |
| 2741 | 2741 | phase: "pr", |
| 2742 | label: "Creating PR...", | |
| 2742 | label: "Preparing PR...", | |
| 2743 | }), | |
| 2744 | expect.objectContaining({ | |
| 2745 | kind: "phase_started", | |
| 2746 | phase: "pr", | |
| 2747 | label: "Generating PR content...", | |
| 2748 | }), | |
| 2749 | expect.objectContaining({ | |
| 2750 | kind: "phase_started", | |
| 2751 | phase: "pr", | |
| 2752 | label: "Creating GitHub pull request...", | |
| 2743 | 2753 | }), |
| 2744 | 2754 | ]); |
| 2745 | 2755 | }), |
apps/server/src/git/Layers/GitManager.ts+16-2| ... | ... | @@ -37,6 +37,7 @@ const STATUS_RESULT_CACHE_TTL = Duration.seconds(1); |
| 37 | 37 | const STATUS_RESULT_CACHE_CAPACITY = 2_048; |
| 38 | 38 | type StripProgressContext<T> = T extends any ? Omit<T, "actionId" | "cwd" | "action"> : never; |
| 39 | 39 | type GitActionProgressPayload = StripProgressContext<GitActionProgressEvent>; |
| 40 | type GitActionProgressEmitter = (event: GitActionProgressPayload) => Effect.Effect<void, never>; | |
| 40 | 41 | |
| 41 | 42 | interface OpenPrInfo { |
| 42 | 43 | number: number; |
| ... | ... | @@ -1200,6 +1201,7 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () { |
| 1200 | 1201 | modelSelection: ModelSelection, |
| 1201 | 1202 | cwd: string, |
| 1202 | 1203 | fallbackBranch: string | null, |
| 1204 | emit: GitActionProgressEmitter, | |
| 1203 | 1205 | ) { |
| 1204 | 1206 | const details = yield* gitCore.statusDetails(cwd); |
| 1205 | 1207 | const branch = details.branch ?? fallbackBranch; |
| ... | ... | @@ -1234,6 +1236,11 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () { |
| 1234 | 1236 | } |
| 1235 | 1237 | |
| 1236 | 1238 | const baseBranch = yield* resolveBaseBranch(cwd, branch, details.upstreamRef, headContext); |
| 1239 | yield* emit({ | |
| 1240 | kind: "phase_started", | |
| 1241 | phase: "pr", | |
| 1242 | label: "Generating PR content...", | |
| 1243 | }); | |
| 1237 | 1244 | const rangeContext = yield* gitCore.readRangeContext(cwd, baseBranch); |
| 1238 | 1245 | |
| 1239 | 1246 | const generated = yield* textGeneration.generatePrContent({ |
| ... | ... | @@ -1254,6 +1261,11 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () { |
| 1254 | 1261 | gitManagerError("runPrStep", "Failed to write pull request body temp file.", cause), |
| 1255 | 1262 | ), |
| 1256 | 1263 | ); |
| 1264 | yield* emit({ | |
| 1265 | kind: "phase_started", | |
| 1266 | phase: "pr", | |
| 1267 | label: "Creating GitHub pull request...", | |
| 1268 | }); | |
| 1257 | 1269 | yield* gitHubCli |
| 1258 | 1270 | .createPullRequest({ |
| 1259 | 1271 | cwd, |
| ... | ... | @@ -1612,11 +1624,13 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () { |
| 1612 | 1624 | .emit({ |
| 1613 | 1625 | kind: "phase_started", |
| 1614 | 1626 | phase: "pr", |
| 1615 | label: "Creating PR...", | |
| 1627 | label: "Preparing PR...", | |
| 1616 | 1628 | }) |
| 1617 | 1629 | .pipe( |
| 1618 | 1630 | Effect.tap(() => Ref.set(currentPhase, Option.some("pr"))), |
| 1619 | Effect.flatMap(() => runPrStep(modelSelection, input.cwd, currentBranch)), | |
| 1631 | Effect.flatMap(() => | |
| 1632 | runPrStep(modelSelection, input.cwd, currentBranch, progress.emit), | |
| 1633 | ), | |
| 1620 | 1634 | ) |
| 1621 | 1635 | : { status: "skipped_not_requested" as const }; |
| 1622 | 1636 |
apps/web/src/components/GitActionsControl.logic.test.ts+27-2| ... | ... | @@ -877,7 +877,12 @@ describe("buildGitActionProgressStages", () => { |
| 877 | 877 | pushTarget: "origin/feature/test", |
| 878 | 878 | shouldPushBeforePr: true, |
| 879 | 879 | }); |
| 880 | assert.deepEqual(stages, ["Pushing to origin/feature/test...", "Creating PR..."]); | |
| 880 | assert.deepEqual(stages, [ | |
| 881 | "Pushing to origin/feature/test...", | |
| 882 | "Preparing PR...", | |
| 883 | "Generating PR content...", | |
| 884 | "Creating GitHub pull request...", | |
| 885 | ]); | |
| 881 | 886 | }); |
| 882 | 887 | |
| 883 | 888 | it("shows only PR progress when create-pr can skip the push", () => { |
| ... | ... | @@ -887,7 +892,11 @@ describe("buildGitActionProgressStages", () => { |
| 887 | 892 | hasWorkingTreeChanges: false, |
| 888 | 893 | shouldPushBeforePr: false, |
| 889 | 894 | }); |
| 890 | assert.deepEqual(stages, ["Creating PR..."]); | |
| 895 | assert.deepEqual(stages, [ | |
| 896 | "Preparing PR...", | |
| 897 | "Generating PR content...", | |
| 898 | "Creating GitHub pull request...", | |
| 899 | ]); | |
| 891 | 900 | }); |
| 892 | 901 | |
| 893 | 902 | it("includes commit stages for commit+push when working tree is dirty", () => { |
| ... | ... | @@ -903,6 +912,22 @@ describe("buildGitActionProgressStages", () => { |
| 903 | 912 | "Pushing to origin/feature/test...", |
| 904 | 913 | ]); |
| 905 | 914 | }); |
| 915 | ||
| 916 | it("includes granular PR stages for commit+push+PR actions", () => { | |
| 917 | const stages = buildGitActionProgressStages({ | |
| 918 | action: "commit_push_pr", | |
| 919 | hasCustomCommitMessage: true, | |
| 920 | hasWorkingTreeChanges: true, | |
| 921 | pushTarget: "origin/feature/test", | |
| 922 | }); | |
| 923 | assert.deepEqual(stages, [ | |
| 924 | "Committing...", | |
| 925 | "Pushing to origin/feature/test...", | |
| 926 | "Preparing PR...", | |
| 927 | "Generating PR content...", | |
| 928 | "Creating GitHub pull request...", | |
| 929 | ]); | |
| 930 | }); | |
| 906 | 931 | }); |
| 907 | 932 | |
| 908 | 933 | describe("resolveThreadBranchUpdate", () => { |
apps/web/src/components/GitActionsControl.logic.ts+7-2| ... | ... | @@ -47,12 +47,17 @@ export function buildGitActionProgressStages(input: { |
| 47 | 47 | }): string[] { |
| 48 | 48 | const branchStages = input.featureBranch ? ["Preparing feature branch..."] : []; |
| 49 | 49 | const pushStage = input.pushTarget ? `Pushing to ${input.pushTarget}...` : "Pushing..."; |
| 50 | const prStages = [ | |
| 51 | "Preparing PR...", | |
| 52 | "Generating PR content...", | |
| 53 | "Creating GitHub pull request...", | |
| 54 | ]; | |
| 50 | 55 | |
| 51 | 56 | if (input.action === "push") { |
| 52 | 57 | return [pushStage]; |
| 53 | 58 | } |
| 54 | 59 | if (input.action === "create_pr") { |
| 55 | return input.shouldPushBeforePr ? [pushStage, "Creating PR..."] : ["Creating PR..."]; | |
| 60 | return input.shouldPushBeforePr ? [pushStage, ...prStages] : prStages; | |
| 56 | 61 | } |
| 57 | 62 | |
| 58 | 63 | const shouldIncludeCommitStages = input.action === "commit" || input.hasWorkingTreeChanges; |
| ... | ... | @@ -67,7 +72,7 @@ export function buildGitActionProgressStages(input: { |
| 67 | 72 | if (input.action === "commit_push") { |
| 68 | 73 | return [...branchStages, ...commitStages, pushStage]; |
| 69 | 74 | } |
| 70 | return [...branchStages, ...commitStages, pushStage, "Creating PR..."]; | |
| 75 | return [...branchStages, ...commitStages, pushStage, ...prStages]; | |
| 71 | 76 | } |
| 72 | 77 | |
| 73 | 78 | export function buildMenuItems( |