| 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,6 +913,41 @@ it.layer(TestLayer)("git integration", (it) => { |
| 913 | expect(renamed.branch).toBe("t3code/feat/session-2"); | 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 | // ── createGitWorktree + removeGitWorktree ── | 953 | // ── createGitWorktree + removeGitWorktree ── |
apps/server/src/git/Layers/GitCore.ts+2-2| ... | @@ -282,7 +282,7 @@ const makeGitCore = Effect.gen(function* () { | ... | @@ -282,7 +282,7 @@ const makeGitCore = Effect.gen(function* () { |
| 282 | return yield* createGitCommandError( | 282 | return yield* createGitCommandError( |
| 283 | "GitCore.renameBranch", | 283 | "GitCore.renameBranch", |
| 284 | cwd, | 284 | cwd, |
| 285 | ["branch", "-m", desiredBranch], | 285 | ["branch", "-m", "--", desiredBranch], |
| 286 | `Could not find an available branch name for '${desiredBranch}'.`, | 286 | `Could not find an available branch name for '${desiredBranch}'.`, |
| 287 | ); | 287 | ); |
| 288 | }); | 288 | }); |
| ... | @@ -1075,7 +1075,7 @@ const makeGitCore = Effect.gen(function* () { | ... | @@ -1075,7 +1075,7 @@ const makeGitCore = Effect.gen(function* () { |
| 1075 | yield* executeGit( | 1075 | yield* executeGit( |
| 1076 | "GitCore.renameBranch", | 1076 | "GitCore.renameBranch", |
| 1077 | input.cwd, | 1077 | input.cwd, |
| 1078 | ["branch", "-m", input.oldBranch, targetBranch], | 1078 | ["branch", "-m", "--", input.oldBranch, targetBranch], |
| 1079 | { | 1079 | { |
| 1080 | timeoutMs: 10_000, | 1080 | timeoutMs: 10_000, |
| 1081 | fallbackErrorMessage: "git branch rename failed", | 1081 | fallbackErrorMessage: "git branch rename failed", |
apps/server/src/git/Services/GitCore.ts+10-2| ... | @@ -17,8 +17,6 @@ import type { | ... | @@ -17,8 +17,6 @@ import type { |
| 17 | GitListBranchesInput, | 17 | GitListBranchesInput, |
| 18 | GitListBranchesResult, | 18 | GitListBranchesResult, |
| 19 | GitPullResult, | 19 | GitPullResult, |
| 20 | GitRenameBranchInput, | ||
| 21 | GitRenameBranchResult, | ||
| 22 | GitRemoveWorktreeInput, | 20 | GitRemoveWorktreeInput, |
| 23 | GitStatusInput, | 21 | GitStatusInput, |
| 24 | GitStatusResult, | 22 | GitStatusResult, |
| ... | @@ -48,6 +46,16 @@ export interface GitRangeContext { | ... | @@ -48,6 +46,16 @@ export interface GitRangeContext { |
| 48 | diffPatch: string; | 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 | * GitCoreShape - Service API for low-level Git repository interactions. | 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,7 +371,7 @@ describe("WebSocket Server", () => { |
| 371 | gitManager?: GitManagerShape; | 371 | gitManager?: GitManagerShape; |
| 372 | gitCore?: Pick< | 372 | gitCore?: Pick< |
| 373 | GitCoreShape, | 373 | GitCoreShape, |
| 374 | "listBranches" | "initRepo" | "pullCurrentBranch" | "renameBranch" | 374 | "listBranches" | "initRepo" | "pullCurrentBranch" |
| 375 | >; | 375 | >; |
| 376 | terminalManager?: TerminalManagerShape; | 376 | terminalManager?: TerminalManagerShape; |
| 377 | } = {}, | 377 | } = {}, |
| ... | @@ -1471,7 +1471,6 @@ describe("WebSocket Server", () => { | ... | @@ -1471,7 +1471,6 @@ describe("WebSocket Server", () => { |
| 1471 | }), | 1471 | }), |
| 1472 | ); | 1472 | ); |
| 1473 | const initRepo = vi.fn(() => Effect.void); | 1473 | const initRepo = vi.fn(() => Effect.void); |
| 1474 | const renameBranch = vi.fn(() => Effect.succeed({ branch: "t3code/new-name" })); | ||
| 1475 | const pullCurrentBranch = vi.fn(() => | 1474 | const pullCurrentBranch = vi.fn(() => |
| 1476 | Effect.fail( | 1475 | Effect.fail( |
| 1477 | new GitCommandError({ | 1476 | new GitCommandError({ |
| ... | @@ -1488,7 +1487,6 @@ describe("WebSocket Server", () => { | ... | @@ -1488,7 +1487,6 @@ describe("WebSocket Server", () => { |
| 1488 | gitCore: { | 1487 | gitCore: { |
| 1489 | listBranches, | 1488 | listBranches, |
| 1490 | initRepo, | 1489 | initRepo, |
| 1491 | renameBranch, | ||
| 1492 | pullCurrentBranch, | 1490 | pullCurrentBranch, |
| 1493 | }, | 1491 | }, |
| 1494 | }); | 1492 | }); |
| ... | @@ -1513,18 +1511,6 @@ describe("WebSocket Server", () => { | ... | @@ -1513,18 +1511,6 @@ describe("WebSocket Server", () => { |
| 1513 | expect(pullResponse.error?.message).toContain("No upstream configured"); | 1511 | expect(pullResponse.error?.message).toContain("No upstream configured"); |
| 1514 | expect(pullCurrentBranch).toHaveBeenCalledWith("/repo/path"); | 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 | it("supports git.status over websocket", async () => { | 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,11 +572,6 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< |
| 572 | return yield* git.createWorktree(body); | 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 | case WS_METHODS.gitRemoveWorktree: { | 575 | case WS_METHODS.gitRemoveWorktree: { |
| 581 | const body = stripRequestTag(request.body); | 576 | const body = stripRequestTag(request.body); |
| 582 | return yield* git.removeWorktree(body); | 577 | return yield* git.removeWorktree(body); |
apps/web/src/wsNativeApi.ts-1| ... | @@ -164,7 +164,6 @@ export function createWsNativeApi(): NativeApi { | ... | @@ -164,7 +164,6 @@ export function createWsNativeApi(): NativeApi { |
| 164 | runStackedAction: (input) => transport.request(WS_METHODS.gitRunStackedAction, input), | 164 | runStackedAction: (input) => transport.request(WS_METHODS.gitRunStackedAction, input), |
| 165 | listBranches: (input) => transport.request(WS_METHODS.gitListBranches, input), | 165 | listBranches: (input) => transport.request(WS_METHODS.gitListBranches, input), |
| 166 | createWorktree: (input) => transport.request(WS_METHODS.gitCreateWorktree, input), | 166 | createWorktree: (input) => transport.request(WS_METHODS.gitCreateWorktree, input), |
| 167 | renameBranch: (input) => transport.request(WS_METHODS.gitRenameBranch, input), | ||
| 168 | removeWorktree: (input) => transport.request(WS_METHODS.gitRemoveWorktree, input), | 167 | removeWorktree: (input) => transport.request(WS_METHODS.gitRemoveWorktree, input), |
| 169 | createBranch: (input) => transport.request(WS_METHODS.gitCreateBranch, input), | 168 | createBranch: (input) => transport.request(WS_METHODS.gitCreateBranch, input), |
| 170 | checkout: (input) => transport.request(WS_METHODS.gitCheckout, input), | 169 | checkout: (input) => transport.request(WS_METHODS.gitCheckout, input), |
packages/contracts/src/git.ts-42| ... | @@ -1,6 +1,5 @@ | ... | @@ -1,6 +1,5 @@ |
| 1 | import { Schema } from "effect"; | 1 | import { Schema } from "effect"; |
| 2 | import { NonNegativeInt, PositiveInt, TrimmedNonEmptyString } from "./baseSchemas"; | 2 | import { NonNegativeInt, PositiveInt, TrimmedNonEmptyString } from "./baseSchemas"; |
| 3 | import { ChatAttachment, PROVIDER_SEND_TURN_MAX_ATTACHMENTS } from "./orchestration"; | ||
| 4 | 3 | ||
| 5 | const TrimmedNonEmptyStringSchema = TrimmedNonEmptyString; | 4 | const TrimmedNonEmptyStringSchema = TrimmedNonEmptyString; |
| 6 | 5 | ||
| ... | @@ -70,32 +69,6 @@ export const GitCreateWorktreeInput = Schema.Struct({ | ... | @@ -70,32 +69,6 @@ export const GitCreateWorktreeInput = Schema.Struct({ |
| 70 | }); | 69 | }); |
| 71 | export type GitCreateWorktreeInput = typeof GitCreateWorktreeInput.Type; | 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 | export const GitRemoveWorktreeInput = Schema.Struct({ | 72 | export const GitRemoveWorktreeInput = Schema.Struct({ |
| 100 | cwd: TrimmedNonEmptyStringSchema, | 73 | cwd: TrimmedNonEmptyStringSchema, |
| 101 | path: TrimmedNonEmptyStringSchema, | 74 | path: TrimmedNonEmptyStringSchema, |
| ... | @@ -164,21 +137,6 @@ export const GitCreateWorktreeResult = Schema.Struct({ | ... | @@ -164,21 +137,6 @@ export const GitCreateWorktreeResult = Schema.Struct({ |
| 164 | }); | 137 | }); |
| 165 | export type GitCreateWorktreeResult = typeof GitCreateWorktreeResult.Type; | 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 | export const GitRunStackedActionResult = Schema.Struct({ | 140 | export const GitRunStackedActionResult = Schema.Struct({ |
| 183 | action: GitStackedAction, | 141 | action: GitStackedAction, |
| 184 | commit: Schema.Struct({ | 142 | commit: Schema.Struct({ |
packages/contracts/src/ipc.ts-3| ... | @@ -1,8 +1,6 @@ | ... | @@ -1,8 +1,6 @@ |
| 1 | import type { | 1 | import type { |
| 2 | GitCheckoutInput, | 2 | GitCheckoutInput, |
| 3 | GitCreateBranchInput, | 3 | GitCreateBranchInput, |
| 4 | GitRenameBranchInput, | ||
| 5 | GitRenameBranchResult, | ||
| 6 | GitCreateWorktreeInput, | 4 | GitCreateWorktreeInput, |
| 7 | GitCreateWorktreeResult, | 5 | GitCreateWorktreeResult, |
| 8 | GitInitInput, | 6 | GitInitInput, |
| ... | @@ -76,7 +74,6 @@ export interface NativeApi { | ... | @@ -76,7 +74,6 @@ export interface NativeApi { |
| 76 | // Existing branch/worktree API | 74 | // Existing branch/worktree API |
| 77 | listBranches: (input: GitListBranchesInput) => Promise<GitListBranchesResult>; | 75 | listBranches: (input: GitListBranchesInput) => Promise<GitListBranchesResult>; |
| 78 | createWorktree: (input: GitCreateWorktreeInput) => Promise<GitCreateWorktreeResult>; | 76 | createWorktree: (input: GitCreateWorktreeInput) => Promise<GitCreateWorktreeResult>; |
| 79 | renameBranch: (input: GitRenameBranchInput) => Promise<GitRenameBranchResult>; | ||
| 80 | removeWorktree: (input: GitRemoveWorktreeInput) => Promise<void>; | 77 | removeWorktree: (input: GitRemoveWorktreeInput) => Promise<void>; |
| 81 | createBranch: (input: GitCreateBranchInput) => Promise<void>; | 78 | createBranch: (input: GitCreateBranchInput) => Promise<void>; |
| 82 | checkout: (input: GitCheckoutInput) => Promise<void>; | 79 | checkout: (input: GitCheckoutInput) => Promise<void>; |
packages/contracts/src/ws.ts-3| ... | @@ -16,7 +16,6 @@ import { | ... | @@ -16,7 +16,6 @@ import { |
| 16 | GitInitInput, | 16 | GitInitInput, |
| 17 | GitListBranchesInput, | 17 | GitListBranchesInput, |
| 18 | GitPullInput, | 18 | GitPullInput, |
| 19 | GitRenameBranchInput, | ||
| 20 | GitRemoveWorktreeInput, | 19 | GitRemoveWorktreeInput, |
| 21 | GitRunStackedActionInput, | 20 | GitRunStackedActionInput, |
| 22 | GitStatusInput, | 21 | GitStatusInput, |
| ... | @@ -51,7 +50,6 @@ export const WS_METHODS = { | ... | @@ -51,7 +50,6 @@ export const WS_METHODS = { |
| 51 | gitRunStackedAction: "git.runStackedAction", | 50 | gitRunStackedAction: "git.runStackedAction", |
| 52 | gitListBranches: "git.listBranches", | 51 | gitListBranches: "git.listBranches", |
| 53 | gitCreateWorktree: "git.createWorktree", | 52 | gitCreateWorktree: "git.createWorktree", |
| 54 | gitRenameBranch: "git.renameBranch", | ||
| 55 | gitRemoveWorktree: "git.removeWorktree", | 53 | gitRemoveWorktree: "git.removeWorktree", |
| 56 | gitCreateBranch: "git.createBranch", | 54 | gitCreateBranch: "git.createBranch", |
| 57 | gitCheckout: "git.checkout", | 55 | gitCheckout: "git.checkout", |
| ... | @@ -113,7 +111,6 @@ export const WebSocketRequestBody = Schema.Union([ | ... | @@ -113,7 +111,6 @@ export const WebSocketRequestBody = Schema.Union([ |
| 113 | tagRequestBody(WS_METHODS.gitRunStackedAction, GitRunStackedActionInput), | 111 | tagRequestBody(WS_METHODS.gitRunStackedAction, GitRunStackedActionInput), |
| 114 | tagRequestBody(WS_METHODS.gitListBranches, GitListBranchesInput), | 112 | tagRequestBody(WS_METHODS.gitListBranches, GitListBranchesInput), |
| 115 | tagRequestBody(WS_METHODS.gitCreateWorktree, GitCreateWorktreeInput), | 113 | tagRequestBody(WS_METHODS.gitCreateWorktree, GitCreateWorktreeInput), |
| 116 | tagRequestBody(WS_METHODS.gitRenameBranch, GitRenameBranchInput), | ||
| 117 | tagRequestBody(WS_METHODS.gitRemoveWorktree, GitRemoveWorktreeInput), | 114 | tagRequestBody(WS_METHODS.gitRemoveWorktree, GitRemoveWorktreeInput), |
| 118 | tagRequestBody(WS_METHODS.gitCreateBranch, GitCreateBranchInput), | 115 | tagRequestBody(WS_METHODS.gitCreateBranch, GitCreateBranchInput), |
| 119 | tagRequestBody(WS_METHODS.gitCheckout, GitCheckoutInput), | 116 | tagRequestBody(WS_METHODS.gitCheckout, GitCheckoutInput), |