| author | |
| committer | |
| log | 82681606d0a4c0fecf5487751812487abb489c4c |
| tree | 1e59a3947385d4a4d1d9deb8584733302e7d3017 |
| parent | c91b79b6e2e6a77bd44a301689e995288f080a79 |
- remove `git.renameBranch` from WS/contracts/native API surface and update server/web tests
- use `git branch -m -- ...` in GitCore to safely separate branch rename arguments9 files changed, 48 insertions(+), 73 deletions(-)
apps/server/src/git/Layers/GitCore.test.ts+35| ... | ... | @@ -913,6 +913,41 @@ it.layer(TestLayer)("git integration", (it) => { |
| 913 | 913 | expect(renamed.branch).toBe("t3code/feat/session-2"); |
| 914 | 914 | }), |
| 915 | 915 | ); |
| 916 | ||
| 917 | it.effect("uses '--' separator for branch rename arguments", () => | |
| 918 | Effect.gen(function* () { | |
| 919 | const tmp = yield* makeTmpDir(); | |
| 920 | yield* initRepoWithCommit(tmp); | |
| 921 | yield* createGitBranch({ cwd: tmp, branch: "feature/old-name" }); | |
| 922 | yield* checkoutGitBranch({ cwd: tmp, branch: "feature/old-name" }); | |
| 923 | ||
| 924 | const realGitService = yield* GitService; | |
| 925 | let renameArgs: ReadonlyArray<string> | null = null; | |
| 926 | const core = yield* makeIsolatedGitCore({ | |
| 927 | execute: (input) => { | |
| 928 | if (input.args[0] === "branch" && input.args[1] === "-m") { | |
| 929 | renameArgs = [...input.args]; | |
| 930 | } | |
| 931 | return realGitService.execute(input); | |
| 932 | }, | |
| 933 | }); | |
| 934 | ||
| 935 | const renamed = yield* core.renameBranch({ | |
| 936 | cwd: tmp, | |
| 937 | oldBranch: "feature/old-name", | |
| 938 | newBranch: "feature/new-name", | |
| 939 | }); | |
| 940 | ||
| 941 | expect(renamed.branch).toBe("feature/new-name"); | |
| 942 | expect(renameArgs).toEqual([ | |
| 943 | "branch", | |
| 944 | "-m", | |
| 945 | "--", | |
| 946 | "feature/old-name", | |
| 947 | "feature/new-name", | |
| 948 | ]); | |
| 949 | }), | |
| 950 | ); | |
| 916 | 951 | }); |
| 917 | 952 | |
| 918 | 953 | // ── createGitWorktree + removeGitWorktree ── |
apps/server/src/git/Layers/GitCore.ts+2-2| ... | ... | @@ -282,7 +282,7 @@ const makeGitCore = Effect.gen(function* () { |
| 282 | 282 | return yield* createGitCommandError( |
| 283 | 283 | "GitCore.renameBranch", |
| 284 | 284 | cwd, |
| 285 | ["branch", "-m", desiredBranch], | |
| 285 | ["branch", "-m", "--", desiredBranch], | |
| 286 | 286 | `Could not find an available branch name for '${desiredBranch}'.`, |
| 287 | 287 | ); |
| 288 | 288 | }); |
| ... | ... | @@ -1075,7 +1075,7 @@ const makeGitCore = Effect.gen(function* () { |
| 1075 | 1075 | yield* executeGit( |
| 1076 | 1076 | "GitCore.renameBranch", |
| 1077 | 1077 | input.cwd, |
| 1078 | ["branch", "-m", input.oldBranch, targetBranch], | |
| 1078 | ["branch", "-m", "--", input.oldBranch, targetBranch], | |
| 1079 | 1079 | { |
| 1080 | 1080 | timeoutMs: 10_000, |
| 1081 | 1081 | fallbackErrorMessage: "git branch rename failed", |
apps/server/src/git/Services/GitCore.ts+10-2| ... | ... | @@ -17,8 +17,6 @@ import type { |
| 17 | 17 | GitListBranchesInput, |
| 18 | 18 | GitListBranchesResult, |
| 19 | 19 | GitPullResult, |
| 20 | GitRenameBranchInput, | |
| 21 | GitRenameBranchResult, | |
| 22 | 20 | GitRemoveWorktreeInput, |
| 23 | 21 | GitStatusInput, |
| 24 | 22 | GitStatusResult, |
| ... | ... | @@ -48,6 +46,16 @@ export interface GitRangeContext { |
| 48 | 46 | diffPatch: string; |
| 49 | 47 | } |
| 50 | 48 | |
| 49 | export interface GitRenameBranchInput { | |
| 50 | cwd: string; | |
| 51 | oldBranch: string; | |
| 52 | newBranch: string; | |
| 53 | } | |
| 54 | ||
| 55 | export interface GitRenameBranchResult { | |
| 56 | branch: string; | |
| 57 | } | |
| 58 | ||
| 51 | 59 | /** |
| 52 | 60 | * GitCoreShape - Service API for low-level Git repository interactions. |
| 53 | 61 | */ |
apps/server/src/wsServer.test.ts+1-15| ... | ... | @@ -371,7 +371,7 @@ describe("WebSocket Server", () => { |
| 371 | 371 | gitManager?: GitManagerShape; |
| 372 | 372 | gitCore?: Pick< |
| 373 | 373 | GitCoreShape, |
| 374 | "listBranches" | "initRepo" | "pullCurrentBranch" | "renameBranch" | |
| 374 | "listBranches" | "initRepo" | "pullCurrentBranch" | |
| 375 | 375 | >; |
| 376 | 376 | terminalManager?: TerminalManagerShape; |
| 377 | 377 | } = {}, |
| ... | ... | @@ -1471,7 +1471,6 @@ describe("WebSocket Server", () => { |
| 1471 | 1471 | }), |
| 1472 | 1472 | ); |
| 1473 | 1473 | const initRepo = vi.fn(() => Effect.void); |
| 1474 | const renameBranch = vi.fn(() => Effect.succeed({ branch: "t3code/new-name" })); | |
| 1475 | 1474 | const pullCurrentBranch = vi.fn(() => |
| 1476 | 1475 | Effect.fail( |
| 1477 | 1476 | new GitCommandError({ |
| ... | ... | @@ -1488,7 +1487,6 @@ describe("WebSocket Server", () => { |
| 1488 | 1487 | gitCore: { |
| 1489 | 1488 | listBranches, |
| 1490 | 1489 | initRepo, |
| 1491 | renameBranch, | |
| 1492 | 1490 | pullCurrentBranch, |
| 1493 | 1491 | }, |
| 1494 | 1492 | }); |
| ... | ... | @@ -1513,18 +1511,6 @@ describe("WebSocket Server", () => { |
| 1513 | 1511 | expect(pullResponse.error?.message).toContain("No upstream configured"); |
| 1514 | 1512 | expect(pullCurrentBranch).toHaveBeenCalledWith("/repo/path"); |
| 1515 | 1513 | |
| 1516 | const renameResponse = await sendRequest(ws, WS_METHODS.gitRenameBranch, { | |
| 1517 | cwd: "/repo/path", | |
| 1518 | oldBranch: "t3code/tmp-456", | |
| 1519 | newBranch: "t3code/new-name", | |
| 1520 | }); | |
| 1521 | expect(renameResponse.error).toBeUndefined(); | |
| 1522 | expect(renameResponse.result).toEqual({ branch: "t3code/new-name" }); | |
| 1523 | expect(renameBranch).toHaveBeenCalledWith({ | |
| 1524 | cwd: "/repo/path", | |
| 1525 | oldBranch: "t3code/tmp-456", | |
| 1526 | newBranch: "t3code/new-name", | |
| 1527 | }); | |
| 1528 | 1514 | }); |
| 1529 | 1515 | |
| 1530 | 1516 | it("supports git.status over websocket", async () => { |
apps/server/src/wsServer.ts-5| ... | ... | @@ -572,11 +572,6 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< |
| 572 | 572 | return yield* git.createWorktree(body); |
| 573 | 573 | } |
| 574 | 574 | |
| 575 | case WS_METHODS.gitRenameBranch: { | |
| 576 | const body = stripRequestTag(request.body); | |
| 577 | return yield* git.renameBranch(body); | |
| 578 | } | |
| 579 | ||
| 580 | 575 | case WS_METHODS.gitRemoveWorktree: { |
| 581 | 576 | const body = stripRequestTag(request.body); |
| 582 | 577 | return yield* git.removeWorktree(body); |
apps/web/src/wsNativeApi.ts-1| ... | ... | @@ -164,7 +164,6 @@ export function createWsNativeApi(): NativeApi { |
| 164 | 164 | runStackedAction: (input) => transport.request(WS_METHODS.gitRunStackedAction, input), |
| 165 | 165 | listBranches: (input) => transport.request(WS_METHODS.gitListBranches, input), |
| 166 | 166 | createWorktree: (input) => transport.request(WS_METHODS.gitCreateWorktree, input), |
| 167 | renameBranch: (input) => transport.request(WS_METHODS.gitRenameBranch, input), | |
| 168 | 167 | removeWorktree: (input) => transport.request(WS_METHODS.gitRemoveWorktree, input), |
| 169 | 168 | createBranch: (input) => transport.request(WS_METHODS.gitCreateBranch, input), |
| 170 | 169 | checkout: (input) => transport.request(WS_METHODS.gitCheckout, input), |
packages/contracts/src/git.ts-42| ... | ... | @@ -1,6 +1,5 @@ |
| 1 | 1 | import { Schema } from "effect"; |
| 2 | 2 | import { NonNegativeInt, PositiveInt, TrimmedNonEmptyString } from "./baseSchemas"; |
| 3 | import { ChatAttachment, PROVIDER_SEND_TURN_MAX_ATTACHMENTS } from "./orchestration"; | |
| 4 | 3 | |
| 5 | 4 | const TrimmedNonEmptyStringSchema = TrimmedNonEmptyString; |
| 6 | 5 | |
| ... | ... | @@ -70,32 +69,6 @@ export const GitCreateWorktreeInput = Schema.Struct({ |
| 70 | 69 | }); |
| 71 | 70 | export type GitCreateWorktreeInput = typeof GitCreateWorktreeInput.Type; |
| 72 | 71 | |
| 73 | export const GitGenerateBranchNameInput = Schema.Struct({ | |
| 74 | cwd: TrimmedNonEmptyStringSchema, | |
| 75 | message: TrimmedNonEmptyStringSchema, | |
| 76 | attachments: Schema.optional( | |
| 77 | Schema.Array(ChatAttachment).check(Schema.isMaxLength(PROVIDER_SEND_TURN_MAX_ATTACHMENTS)), | |
| 78 | ), | |
| 79 | }); | |
| 80 | export type GitGenerateBranchNameInput = typeof GitGenerateBranchNameInput.Type; | |
| 81 | ||
| 82 | export const GitGenerateAndRenameBranchInput = Schema.Struct({ | |
| 83 | cwd: TrimmedNonEmptyStringSchema, | |
| 84 | oldBranch: TrimmedNonEmptyStringSchema, | |
| 85 | message: TrimmedNonEmptyStringSchema, | |
| 86 | attachments: Schema.optional( | |
| 87 | Schema.Array(ChatAttachment).check(Schema.isMaxLength(PROVIDER_SEND_TURN_MAX_ATTACHMENTS)), | |
| 88 | ), | |
| 89 | }); | |
| 90 | export type GitGenerateAndRenameBranchInput = typeof GitGenerateAndRenameBranchInput.Type; | |
| 91 | ||
| 92 | export const GitRenameBranchInput = Schema.Struct({ | |
| 93 | cwd: TrimmedNonEmptyStringSchema, | |
| 94 | oldBranch: TrimmedNonEmptyStringSchema, | |
| 95 | newBranch: TrimmedNonEmptyStringSchema, | |
| 96 | }); | |
| 97 | export type GitRenameBranchInput = typeof GitRenameBranchInput.Type; | |
| 98 | ||
| 99 | 72 | export const GitRemoveWorktreeInput = Schema.Struct({ |
| 100 | 73 | cwd: TrimmedNonEmptyStringSchema, |
| 101 | 74 | path: TrimmedNonEmptyStringSchema, |
| ... | ... | @@ -164,21 +137,6 @@ export const GitCreateWorktreeResult = Schema.Struct({ |
| 164 | 137 | }); |
| 165 | 138 | export type GitCreateWorktreeResult = typeof GitCreateWorktreeResult.Type; |
| 166 | 139 | |
| 167 | export const GitGenerateBranchNameResult = Schema.Struct({ | |
| 168 | branch: Schema.NullOr(TrimmedNonEmptyStringSchema), | |
| 169 | }); | |
| 170 | export type GitGenerateBranchNameResult = typeof GitGenerateBranchNameResult.Type; | |
| 171 | ||
| 172 | export const GitGenerateAndRenameBranchResult = Schema.Struct({ | |
| 173 | branch: Schema.NullOr(TrimmedNonEmptyStringSchema), | |
| 174 | }); | |
| 175 | export type GitGenerateAndRenameBranchResult = typeof GitGenerateAndRenameBranchResult.Type; | |
| 176 | ||
| 177 | export const GitRenameBranchResult = Schema.Struct({ | |
| 178 | branch: TrimmedNonEmptyStringSchema, | |
| 179 | }); | |
| 180 | export type GitRenameBranchResult = typeof GitRenameBranchResult.Type; | |
| 181 | ||
| 182 | 140 | export const GitRunStackedActionResult = Schema.Struct({ |
| 183 | 141 | action: GitStackedAction, |
| 184 | 142 | commit: Schema.Struct({ |
packages/contracts/src/ipc.ts-3| ... | ... | @@ -1,8 +1,6 @@ |
| 1 | 1 | import type { |
| 2 | 2 | GitCheckoutInput, |
| 3 | 3 | GitCreateBranchInput, |
| 4 | GitRenameBranchInput, | |
| 5 | GitRenameBranchResult, | |
| 6 | 4 | GitCreateWorktreeInput, |
| 7 | 5 | GitCreateWorktreeResult, |
| 8 | 6 | GitInitInput, |
| ... | ... | @@ -76,7 +74,6 @@ export interface NativeApi { |
| 76 | 74 | // Existing branch/worktree API |
| 77 | 75 | listBranches: (input: GitListBranchesInput) => Promise<GitListBranchesResult>; |
| 78 | 76 | createWorktree: (input: GitCreateWorktreeInput) => Promise<GitCreateWorktreeResult>; |
| 79 | renameBranch: (input: GitRenameBranchInput) => Promise<GitRenameBranchResult>; | |
| 80 | 77 | removeWorktree: (input: GitRemoveWorktreeInput) => Promise<void>; |
| 81 | 78 | createBranch: (input: GitCreateBranchInput) => Promise<void>; |
| 82 | 79 | checkout: (input: GitCheckoutInput) => Promise<void>; |
packages/contracts/src/ws.ts-3| ... | ... | @@ -16,7 +16,6 @@ import { |
| 16 | 16 | GitInitInput, |
| 17 | 17 | GitListBranchesInput, |
| 18 | 18 | GitPullInput, |
| 19 | GitRenameBranchInput, | |
| 20 | 19 | GitRemoveWorktreeInput, |
| 21 | 20 | GitRunStackedActionInput, |
| 22 | 21 | GitStatusInput, |
| ... | ... | @@ -51,7 +50,6 @@ export const WS_METHODS = { |
| 51 | 50 | gitRunStackedAction: "git.runStackedAction", |
| 52 | 51 | gitListBranches: "git.listBranches", |
| 53 | 52 | gitCreateWorktree: "git.createWorktree", |
| 54 | gitRenameBranch: "git.renameBranch", | |
| 55 | 53 | gitRemoveWorktree: "git.removeWorktree", |
| 56 | 54 | gitCreateBranch: "git.createBranch", |
| 57 | 55 | gitCheckout: "git.checkout", |
| ... | ... | @@ -113,7 +111,6 @@ export const WebSocketRequestBody = Schema.Union([ |
| 113 | 111 | tagRequestBody(WS_METHODS.gitRunStackedAction, GitRunStackedActionInput), |
| 114 | 112 | tagRequestBody(WS_METHODS.gitListBranches, GitListBranchesInput), |
| 115 | 113 | tagRequestBody(WS_METHODS.gitCreateWorktree, GitCreateWorktreeInput), |
| 116 | tagRequestBody(WS_METHODS.gitRenameBranch, GitRenameBranchInput), | |
| 117 | 114 | tagRequestBody(WS_METHODS.gitRemoveWorktree, GitRemoveWorktreeInput), |
| 118 | 115 | tagRequestBody(WS_METHODS.gitCreateBranch, GitCreateBranchInput), |
| 119 | 116 | tagRequestBody(WS_METHODS.gitCheckout, GitCheckoutInput), |