mirror of
https://github.com/github/gh-stack.git
synced 2026-09-14 20:26:28 +08:00
consolidated prefix input and validation
This commit is contained in:
+44
-42
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user