diff --git a/apps/cli-go/cmd/branches.go b/apps/cli-go/cmd/branches.go index 3a77e899a5..9a016109ac 100644 --- a/apps/cli-go/cmd/branches.go +++ b/apps/cli-go/cmd/branches.go @@ -59,6 +59,9 @@ var ( if cmdFlags.Changed("notify-url") { body.NotifyUrl = ¬ifyURL } + if cmdFlags.Changed("git-branch") { + body.GitBranch = &gitBranch + } return create.Run(cmd.Context(), body, afero.NewOsFs()) }, } @@ -210,6 +213,7 @@ func init() { createFlags.BoolVar(&persistent, "persistent", false, "Whether to create a persistent branch.") createFlags.BoolVar(&withData, "with-data", false, "Whether to clone production data to the branch database.") createFlags.StringVar(¬ifyURL, "notify-url", "", "URL to notify when branch is active healthy.") + createFlags.StringVar(&gitBranch, "git-branch", "", "Associate a git branch with the new preview branch.") branchesCmd.AddCommand(branchCreateCmd) branchesCmd.AddCommand(branchListCmd) branchesCmd.AddCommand(branchGetCmd) diff --git a/apps/cli-go/internal/branches/create/create.go b/apps/cli-go/internal/branches/create/create.go index 25273456c1..8c0ac98c71 100644 --- a/apps/cli-go/internal/branches/create/create.go +++ b/apps/cli-go/internal/branches/create/create.go @@ -24,7 +24,9 @@ func Run(ctx context.Context, body api.CreateBranchBody, fsys afero.Fs) error { return errors.New(context.Canceled) } body.BranchName = gitBranch - body.GitBranch = &gitBranch + if body.GitBranch == nil { + body.GitBranch = &gitBranch + } } resp, err := utils.GetSupabase().V1CreateABranchWithResponse(ctx, flags.ProjectRef, body) diff --git a/apps/cli/src/legacy/commands/branches/create/create.command.ts b/apps/cli/src/legacy/commands/branches/create/create.command.ts index ace5cbadb7..69367b3f4d 100644 --- a/apps/cli/src/legacy/commands/branches/create/create.command.ts +++ b/apps/cli/src/legacy/commands/branches/create/create.command.ts @@ -83,6 +83,10 @@ const config = { Flag.withDescription("URL to notify when branch is active healthy."), Flag.optional, ), + gitBranch: Flag.string("git-branch").pipe( + Flag.withDescription("Associate a git branch with the new preview branch."), + Flag.optional, + ), } as const; export type LegacyBranchesCreateFlags = CliCommand.Command.Config.Infer; diff --git a/apps/cli/src/legacy/commands/branches/create/create.handler.ts b/apps/cli/src/legacy/commands/branches/create/create.handler.ts index 640ce83909..bb2d79f8f4 100644 --- a/apps/cli/src/legacy/commands/branches/create/create.handler.ts +++ b/apps/cli/src/legacy/commands/branches/create/create.handler.ts @@ -51,7 +51,10 @@ export const legacyBranchesCreate = Effect.fn("legacy.branches.create")(function // resolving the project ref so the linked-project cache write does not fire. // ----------------------------------------------------------------------- let branchName = Option.getOrElse(flags.name, () => ""); - let gitBranchForBody: string | undefined; + // An explicit `--git-branch` flag takes precedence over the auto-detected + // branch, mirroring Go's `cmd/branches.go` (the flag sets `body.GitBranch`) + // and `create.go`'s `if body.GitBranch == nil` guard during auto-detect. + let gitBranchForBody = Option.getOrUndefined(flags.gitBranch); if (branchName.length === 0) { const gitBranch = yield* detectGitBranch; @@ -68,7 +71,9 @@ export const legacyBranchesCreate = Effect.fn("legacy.branches.create")(function return yield* new LegacyBranchesCreateCancelledError({ message: "context canceled" }); } branchName = gitBranch.value; - gitBranchForBody = gitBranch.value; + if (gitBranchForBody === undefined) { + gitBranchForBody = gitBranch.value; + } } } diff --git a/apps/cli/src/legacy/commands/branches/create/create.integration.test.ts b/apps/cli/src/legacy/commands/branches/create/create.integration.test.ts index 7d8d1cf034..fcbb44aa0f 100644 --- a/apps/cli/src/legacy/commands/branches/create/create.integration.test.ts +++ b/apps/cli/src/legacy/commands/branches/create/create.integration.test.ts @@ -139,6 +139,7 @@ const baseFlags: LegacyBranchesCreateFlags = { persistent: Option.none(), withData: Option.none(), notifyUrl: Option.none(), + gitBranch: Option.none(), }; describe("legacy branches create integration", () => { @@ -179,6 +180,21 @@ describe("legacy branches create integration", () => { }).pipe(Effect.provide(layer)); }); + it.live("forwards an explicit --git-branch in the request body", () => { + const { layer, api } = setup(); + return Effect.gen(function* () { + yield* legacyBranchesCreate({ + ...baseFlags, + name: Option.some("feat-x"), + gitBranch: Option.some("feature/login-page"), + }); + expect(api.requests[0]?.body).toMatchObject({ + branch_name: "feat-x", + git_branch: "feature/login-page", + }); + }).pipe(Effect.provide(layer)); + }); + it.live("emits a success event for --output-format=json", () => { const { layer, out } = setup({ format: "json" }); return Effect.gen(function* () { diff --git a/apps/cli/src/next/commands/branches/create/create.command.ts b/apps/cli/src/next/commands/branches/create/create.command.ts index dd2efa1b5f..237939a58e 100644 --- a/apps/cli/src/next/commands/branches/create/create.command.ts +++ b/apps/cli/src/next/commands/branches/create/create.command.ts @@ -85,6 +85,12 @@ const config = { Flag.withDescription("HTTP endpoint to notify when the branch becomes active and healthy."), Flag.optional, ), + gitBranch: Flag.string("git-branch").pipe( + Flag.withDescription( + "Git branch to associate with the new branch. Defaults to the current local git branch when the branch name is auto-detected.", + ), + Flag.optional, + ), switchAfter: Flag.boolean("switch").pipe( Flag.withDescription("Switch to the new branch after creation. Pass --no-switch to skip."), Flag.withDefault(true), @@ -118,6 +124,10 @@ export const createBranchesCommand = Command.make("create", config).pipe( command: "supabase branches create my-feature --with-data", description: "Create a branch and clone production data into it", }, + { + command: "supabase branches create my-feature --git-branch feature/login-page", + description: "Associate a specific git branch with the new branch", + }, ]), Command.withHandler((flags) => create(flags).pipe(withCommandInstrumentation(), withJsonErrorHandling), diff --git a/apps/cli/src/next/commands/branches/create/create.handler.ts b/apps/cli/src/next/commands/branches/create/create.handler.ts index c0b0062463..2757101ed6 100644 --- a/apps/cli/src/next/commands/branches/create/create.handler.ts +++ b/apps/cli/src/next/commands/branches/create/create.handler.ts @@ -78,7 +78,8 @@ export const create = Effect.fn("branches.create")(function* (flags: CreateFlags const { project } = maybeLinkState.value; - const { branchName, gitBranch } = yield* resolveBranchName(flags.name); + const { branchName, gitBranch: detectedGitBranch } = yield* resolveBranchName(flags.name); + const gitBranch = Option.isSome(flags.gitBranch) ? flags.gitBranch : detectedGitBranch; const desiredInstanceSize = Option.getOrUndefined(flags.size); diff --git a/apps/cli/src/next/commands/branches/create/create.integration.test.ts b/apps/cli/src/next/commands/branches/create/create.integration.test.ts index ef129cf5f5..94db314cdf 100644 --- a/apps/cli/src/next/commands/branches/create/create.integration.test.ts +++ b/apps/cli/src/next/commands/branches/create/create.integration.test.ts @@ -64,6 +64,7 @@ const BASE_FLAGS: CreateFlags = { persistent: false, withData: false, notifyUrl: Option.none(), + gitBranch: Option.none(), switchAfter: true, }; @@ -316,6 +317,7 @@ describe("branches create handler", () => { persistent: true, withData: true, notifyUrl: Option.some("https://example.com/hook"), + gitBranch: Option.some("feature/login-page"), switchAfter: false, }; @@ -326,6 +328,25 @@ describe("branches create handler", () => { expect(api.capturedInput?.persistent).toBe(true); expect(api.capturedInput?.with_data).toBe(true); expect(api.capturedInput?.notify_url).toBe("https://example.com/hook"); + expect(api.capturedInput?.git_branch).toBe("feature/login-page"); + }), + ); + + it.live("prefers --git-branch over the auto-detected git branch", () => + Effect.gen(function* () { + const { layer, api } = setup({ + env: { GITHUB_HEAD_REF: "feature/auto-detect" }, + format: "json", + }); + const flags: CreateFlags = { + ...BASE_FLAGS, + gitBranch: Option.some("feature/explicit"), + }; + + yield* create(flags).pipe(Effect.provide(layer)); + + expect(api.capturedInput?.branch_name).toBe("feature/auto-detect"); + expect(api.capturedInput?.git_branch).toBe("feature/explicit"); }), );