Skip to content

Commit 3aa8c8e

Browse files
committed
consolidated prefix input and validation
1 parent 103fc37 commit 3aa8c8e

2 files changed

Lines changed: 71 additions & 42 deletions

File tree

cmd/init.go

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,49 @@ func runInit(cfg *config.Config, opts *initOptions) error {
108108
return ErrInvalidArgs
109109
}
110110

111+
// Prompt for prefix interactively if not provided via flag and we're
112+
// in interactive mode (not adopt, not explicit branches).
113+
if opts.prefix == "" && !opts.adopt && len(opts.branches) == 0 && cfg.IsInteractive() {
114+
p := prompter.New(cfg.In, cfg.Out, cfg.Err)
115+
if opts.numbered {
116+
// --numbered requires a prefix; prompt specifically for one
117+
prefixInput, err := p.Input("Enter a branch prefix (required for --numbered)", "")
118+
if err != nil {
119+
if isInterruptError(err) {
120+
printInterrupt(cfg)
121+
return ErrSilent
122+
}
123+
cfg.Errorf("failed to read prefix: %s", err)
124+
return ErrSilent
125+
}
126+
opts.prefix = strings.TrimSpace(prefixInput)
127+
if opts.prefix == "" {
128+
cfg.Errorf("--numbered requires a prefix")
129+
return ErrInvalidArgs
130+
}
131+
} else {
132+
prefixInput, err := p.Input("Set a branch prefix? (leave blank to skip)", "")
133+
if err != nil {
134+
if isInterruptError(err) {
135+
printInterrupt(cfg)
136+
return ErrSilent
137+
}
138+
cfg.Errorf("failed to read prefix: %s", err)
139+
return ErrSilent
140+
}
141+
opts.prefix = strings.TrimSpace(prefixInput)
142+
}
143+
}
144+
145+
// Validate prefix, after it has been determined (from flag or prompt),
146+
// before any branch creation.
147+
if opts.prefix != "" {
148+
if err := git.ValidateRefName(opts.prefix); err != nil {
149+
cfg.Errorf("invalid prefix %q: must be a valid git ref component", opts.prefix)
150+
return ErrInvalidArgs
151+
}
152+
}
153+
111154
if opts.adopt {
112155
// Adopt mode: validate all specified branches exist
113156
if len(opts.branches) == 0 {
@@ -165,46 +208,13 @@ func runInit(cfg *config.Config, opts *initOptions) error {
165208
}
166209
branches = prefixed
167210
} else {
168-
// Interactive mode
211+
// Interactive mode — prefix was already prompted for above
169212
if !cfg.IsInteractive() {
170213
cfg.Errorf("interactive input required; provide branch names or use --adopt")
171214
return ErrInvalidArgs
172215
}
173216
p := prompter.New(cfg.In, cfg.Out, cfg.Err)
174217

175-
// Step 1: Ask for prefix
176-
if opts.prefix == "" {
177-
if opts.numbered {
178-
// --numbered requires a prefix; prompt specifically for one
179-
prefixInput, err := p.Input("Enter a branch prefix (required for --numbered)", "")
180-
if err != nil {
181-
if isInterruptError(err) {
182-
printInterrupt(cfg)
183-
return ErrSilent
184-
}
185-
cfg.Errorf("failed to read prefix: %s", err)
186-
return ErrSilent
187-
}
188-
opts.prefix = strings.TrimSpace(prefixInput)
189-
if opts.prefix == "" {
190-
cfg.Errorf("--numbered requires a prefix")
191-
return ErrInvalidArgs
192-
}
193-
} else {
194-
prefixInput, err := p.Input("Set a branch prefix? (leave blank to skip)", "")
195-
if err != nil {
196-
if isInterruptError(err) {
197-
printInterrupt(cfg)
198-
return ErrSilent
199-
}
200-
cfg.Errorf("failed to read prefix: %s", err)
201-
return ErrSilent
202-
}
203-
opts.prefix = strings.TrimSpace(prefixInput)
204-
}
205-
}
206-
207-
// Step 2: Ask for branch name (unless --numbered auto-generates it)
208218
if opts.numbered {
209219
// Auto-generate numbered branch name
210220
branchName := branch.NextNumberedName(opts.prefix, nil)
@@ -283,14 +293,6 @@ func runInit(cfg *config.Config, opts *initOptions) error {
283293
}
284294
}
285295

286-
// Validate prefix (from flag or interactive input)
287-
if opts.prefix != "" {
288-
if err := git.ValidateRefName(opts.prefix); err != nil {
289-
cfg.Errorf("invalid prefix %q: must be a valid git ref component", opts.prefix)
290-
return ErrInvalidArgs
291-
}
292-
}
293-
294296
// Build stack
295297
trunkSHA, _ := git.RevParse(trunk)
296298
branchRefs := make([]stack.BranchRef, len(branches))

cmd/init_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"fmt"
45
"io"
56
"os"
67
"testing"
@@ -137,6 +138,32 @@ func TestInit_PrefixAppliedToExplicitBranches(t *testing.T) {
137138
assert.Equal(t, []string{"feat/b1", "feat/b2"}, names, "stack should store prefixed branch names")
138139
}
139140

141+
func TestInit_InvalidPrefixRejectedBeforeBranchCreation(t *testing.T) {
142+
gitDir := t.TempDir()
143+
var created []string
144+
restore := git.SetOps(&git.MockOps{
145+
GitDirFn: func() (string, error) { return gitDir, nil },
146+
DefaultBranchFn: func() (string, error) { return "main", nil },
147+
CurrentBranchFn: func() (string, error) { return "main", nil },
148+
ValidateRefNameFn: func(name string) error {
149+
return fmt.Errorf("invalid ref name: %s", name)
150+
},
151+
CreateBranchFn: func(name, base string) error {
152+
created = append(created, name)
153+
return nil
154+
},
155+
})
156+
defer restore()
157+
158+
cfg, outR, errR := config.NewTestConfig()
159+
err := runInit(cfg, &initOptions{branches: []string{"mybranch"}, prefix: "bad..prefix"})
160+
output := collectOutput(cfg, outR, errR)
161+
162+
assert.ErrorIs(t, err, ErrInvalidArgs, "should reject invalid prefix")
163+
assert.Contains(t, output, "invalid prefix")
164+
assert.Empty(t, created, "no branches should be created when prefix is invalid")
165+
}
166+
140167
func TestInit_RerereAlreadyEnabled(t *testing.T) {
141168
gitDir := t.TempDir()
142169
enableRerereCalled := false

0 commit comments

Comments
 (0)