From 1745100d7e32e862ab29d436ce964dc8a4f5400c Mon Sep 17 00:00:00 2001 From: Sameen Karim Date: Sun, 14 Jun 2026 23:35:37 -0400 Subject: [PATCH 1/2] 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. --- cmd/init.go | 7 ------- cmd/init_test.go | 11 +++++++---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/cmd/init.go b/cmd/init.go index 83b6375..98c39ae 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -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() { diff --git a/cmd/init_test.go b/cmd/init_test.go index 48afa85..314a315 100644 --- a/cmd/init_test.go +++ b/cmd/init_test.go @@ -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) { From dba445f64b4fcc13fbaf141564c831ee48f92677 Mon Sep 17 00:00:00 2001 From: Sameen Karim Date: Mon, 15 Jun 2026 00:02:36 -0400 Subject: [PATCH 2/2] 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> --- cmd/init.go | 23 ----------------------- cmd/init_test.go | 23 ----------------------- 2 files changed, 46 deletions(-) diff --git a/cmd/init.go b/cmd/init.go index 98c39ae..5e06d8f 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -448,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] diff --git a/cmd/init_test.go b/cmd/init_test.go index 314a315..a7ef5c8 100644 --- a/cmd/init_test.go +++ b/cmd/init_test.go @@ -798,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) - }) - } -}