remove silent prefix detection in init (#126)

* Remove silent prefix detection from args path in init

When explicit branch names containing slashes were passed to `gh stack
init` (e.g. `gh stack init myprefix/branch`), detectPrefix would
silently extract the prefix and store it in the stack config. This
caused `gh stack add otherbranch` to unexpectedly produce
`myprefix/otherbranch` without the user ever opting in.

Remove the automatic prefix detection from the args path so that
explicit branch names are taken literally. Users who want a prefix
should use `--prefix`. The interactive path (no args) continues to
prompt for confirmation before setting a prefix.

* Remove dead detectPrefix function and its tests

After removing the silent prefix detection from the args path,
detectPrefix has no production callers. Remove the function and
its table-driven unit test to avoid maintaining unused code.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Sameen Karim
2026-06-15 13:54:19 -04:00
committed by GitHub
parent 8c2d9e3da6
commit f8c0100602
2 changed files with 7 additions and 57 deletions
-30
View File
@@ -149,13 +149,6 @@ func runInit(cfg *config.Config, opts *initOptions) error {
return err
}
// Prefix detection (only when --prefix not explicitly set)
if opts.prefix == "" {
if detected := detectPrefix(branches); detected != "" {
opts.prefix = detected
}
}
} else if opts.numbered {
// === NUMBERED PATH (unchanged) ===
if opts.prefix == "" && cfg.IsInteractive() {
@@ -455,29 +448,6 @@ func promptBranchName(cfg *config.Config, prefix string) (string, error) {
return branchName, nil
}
// detectPrefix finds a common prefix across branches by splitting each
// at its last slash. Returns the prefix (without trailing slash) if all
// branches share the same one, or "" otherwise.
func detectPrefix(branches []string) string {
if len(branches) == 0 {
return ""
}
var common string
for i, b := range branches {
lastSlash := strings.LastIndex(b, "/")
if lastSlash <= 0 {
return "" // no slash or leading slash — no prefix
}
prefix := b[:lastSlash]
if i == 0 {
common = prefix
} else if prefix != common {
return "" // different prefixes
}
}
return common
}
// printWhatsNext prints the scenario-aware "What's next" block after init.
func printWhatsNext(cfg *config.Config, s *stack.Stack, branches []string, hasAdopted bool, prCount int) {
lastBranch := branches[len(branches)-1]
+7 -27
View File
@@ -465,7 +465,9 @@ func TestInit_ImplicitAdopt_Mixed(t *testing.T) {
}
func TestInit_PrefixDetection_ArgsCommonPrefix(t *testing.T) {
// Scenario 9: args all share prefix → set silently
// Explicit branch names with a common prefix should NOT auto-detect
// a prefix — the slash is part of the branch name, not a convention.
// Users who want a prefix should use --prefix.
gitDir := t.TempDir()
restore := git.SetOps(&git.MockOps{
GitDirFn: func() (string, error) { return gitDir, nil },
@@ -481,7 +483,7 @@ func TestInit_PrefixDetection_ArgsCommonPrefix(t *testing.T) {
require.NoError(t, err)
sf, _ := stack.Load(gitDir)
assert.Equal(t, "feat", sf.Stacks[0].Prefix)
assert.Equal(t, "", sf.Stacks[0].Prefix)
}
func TestInit_PrefixDetection_ArgsMixedPrefix(t *testing.T) {
@@ -525,7 +527,8 @@ func TestInit_PrefixDetection_ArgsNoSlash(t *testing.T) {
}
func TestInit_PrefixDetection_NestedPrefix(t *testing.T) {
// Scenario 6: sameen/feat/x → prefix "sameen/feat"
// Explicit branch names with nested slashes should NOT auto-detect
// a prefix — the user typed the full branch name deliberately.
gitDir := t.TempDir()
restore := git.SetOps(&git.MockOps{
GitDirFn: func() (string, error) { return gitDir, nil },
@@ -541,7 +544,7 @@ func TestInit_PrefixDetection_NestedPrefix(t *testing.T) {
require.NoError(t, err)
sf, _ := stack.Load(gitDir)
assert.Equal(t, "sameen/feat", sf.Stacks[0].Prefix)
assert.Equal(t, "", sf.Stacks[0].Prefix)
}
func TestInit_ExplicitPrefixSkipsDetection(t *testing.T) {
@@ -795,26 +798,3 @@ func TestInit_TwoPassValidation_InvalidRefName(t *testing.T) {
assert.Contains(t, output, "invalid branch name")
assert.Empty(t, created, "no branches should be created when an arg has an invalid ref name")
}
func TestDetectPrefix(t *testing.T) {
tests := []struct {
name string
branches []string
want string
}{
{"common prefix", []string{"feat/a", "feat/b", "feat/c"}, "feat"},
{"nested prefix", []string{"sameen/feat/a", "sameen/feat/b"}, "sameen/feat"},
{"mixed prefixes", []string{"feat/a", "bug/b"}, ""},
{"no slashes", []string{"auth", "api", "ui"}, ""},
{"empty list", []string{}, ""},
{"single branch with slash", []string{"feat/x"}, "feat"},
{"single branch no slash", []string{"auth"}, ""},
{"leading slash only", []string{"/x"}, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := detectPrefix(tt.branches)
assert.Equal(t, tt.want, got)
})
}
}