consolidated prefix input and validation

This commit is contained in:
Sameen Karim
2026-03-25 15:48:32 -04:00
parent 103fc37383
commit 3aa8c8eda3
2 changed files with 71 additions and 42 deletions
+44 -42
View File
@@ -108,6 +108,49 @@ func runInit(cfg *config.Config, opts *initOptions) error {
return ErrInvalidArgs
}
// Prompt for prefix interactively if not provided via flag and we're
// in interactive mode (not adopt, not explicit branches).
if opts.prefix == "" && !opts.adopt && len(opts.branches) == 0 && cfg.IsInteractive() {
p := prompter.New(cfg.In, cfg.Out, cfg.Err)
if opts.numbered {
// --numbered requires a prefix; prompt specifically for one
prefixInput, err := p.Input("Enter a branch prefix (required for --numbered)", "")
if err != nil {
if isInterruptError(err) {
printInterrupt(cfg)
return ErrSilent
}
cfg.Errorf("failed to read prefix: %s", err)
return ErrSilent
}
opts.prefix = strings.TrimSpace(prefixInput)
if opts.prefix == "" {
cfg.Errorf("--numbered requires a prefix")
return ErrInvalidArgs
}
} else {
prefixInput, err := p.Input("Set a branch prefix? (leave blank to skip)", "")
if err != nil {
if isInterruptError(err) {
printInterrupt(cfg)
return ErrSilent
}
cfg.Errorf("failed to read prefix: %s", err)
return ErrSilent
}
opts.prefix = strings.TrimSpace(prefixInput)
}
}
// Validate prefix, after it has been determined (from flag or prompt),
// before any branch creation.
if opts.prefix != "" {
if err := git.ValidateRefName(opts.prefix); err != nil {
cfg.Errorf("invalid prefix %q: must be a valid git ref component", opts.prefix)
return ErrInvalidArgs
}
}
if opts.adopt {
// Adopt mode: validate all specified branches exist
if len(opts.branches) == 0 {
@@ -165,46 +208,13 @@ func runInit(cfg *config.Config, opts *initOptions) error {
}
branches = prefixed
} else {
// Interactive mode
// Interactive mode — prefix was already prompted for above
if !cfg.IsInteractive() {
cfg.Errorf("interactive input required; provide branch names or use --adopt")
return ErrInvalidArgs
}
p := prompter.New(cfg.In, cfg.Out, cfg.Err)
// Step 1: Ask for prefix
if opts.prefix == "" {
if opts.numbered {
// --numbered requires a prefix; prompt specifically for one
prefixInput, err := p.Input("Enter a branch prefix (required for --numbered)", "")
if err != nil {
if isInterruptError(err) {
printInterrupt(cfg)
return ErrSilent
}
cfg.Errorf("failed to read prefix: %s", err)
return ErrSilent
}
opts.prefix = strings.TrimSpace(prefixInput)
if opts.prefix == "" {
cfg.Errorf("--numbered requires a prefix")
return ErrInvalidArgs
}
} else {
prefixInput, err := p.Input("Set a branch prefix? (leave blank to skip)", "")
if err != nil {
if isInterruptError(err) {
printInterrupt(cfg)
return ErrSilent
}
cfg.Errorf("failed to read prefix: %s", err)
return ErrSilent
}
opts.prefix = strings.TrimSpace(prefixInput)
}
}
// Step 2: Ask for branch name (unless --numbered auto-generates it)
if opts.numbered {
// Auto-generate numbered branch name
branchName := branch.NextNumberedName(opts.prefix, nil)
@@ -283,14 +293,6 @@ func runInit(cfg *config.Config, opts *initOptions) error {
}
}
// Validate prefix (from flag or interactive input)
if opts.prefix != "" {
if err := git.ValidateRefName(opts.prefix); err != nil {
cfg.Errorf("invalid prefix %q: must be a valid git ref component", opts.prefix)
return ErrInvalidArgs
}
}
// Build stack
trunkSHA, _ := git.RevParse(trunk)
branchRefs := make([]stack.BranchRef, len(branches))
+27
View File
@@ -1,6 +1,7 @@
package cmd
import (
"fmt"
"io"
"os"
"testing"
@@ -137,6 +138,32 @@ func TestInit_PrefixAppliedToExplicitBranches(t *testing.T) {
assert.Equal(t, []string{"feat/b1", "feat/b2"}, names, "stack should store prefixed branch names")
}
func TestInit_InvalidPrefixRejectedBeforeBranchCreation(t *testing.T) {
gitDir := t.TempDir()
var created []string
restore := git.SetOps(&git.MockOps{
GitDirFn: func() (string, error) { return gitDir, nil },
DefaultBranchFn: func() (string, error) { return "main", nil },
CurrentBranchFn: func() (string, error) { return "main", nil },
ValidateRefNameFn: func(name string) error {
return fmt.Errorf("invalid ref name: %s", name)
},
CreateBranchFn: func(name, base string) error {
created = append(created, name)
return nil
},
})
defer restore()
cfg, outR, errR := config.NewTestConfig()
err := runInit(cfg, &initOptions{branches: []string{"mybranch"}, prefix: "bad..prefix"})
output := collectOutput(cfg, outR, errR)
assert.ErrorIs(t, err, ErrInvalidArgs, "should reject invalid prefix")
assert.Contains(t, output, "invalid prefix")
assert.Empty(t, created, "no branches should be created when prefix is invalid")
}
func TestInit_RerereAlreadyEnabled(t *testing.T) {
gitDir := t.TempDir()
enableRerereCalled := false