Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ func runAdd(cfg *config.Config, opts *addOptions, args []string) error {
// If the branch already exists in git but is not part of any stack,
// adopt it instead of erroring. This mirrors the init command's behavior.
adopted := git.BranchExists(branchName)
var adoptedBase string
if adopted {
adoptedBase, err = git.MergeBase(currentBranch, branchName)
if err != nil {
cfg.Errorf("failed to determine the common base of %s and %s: %s", currentBranch, branchName, err)
return ErrSilent
}
}

// Stage changes before creating the branch so we can fail early if
// there's nothing to commit (avoids leaving an empty orphan branch).
Expand All @@ -191,9 +199,12 @@ func runAdd(cfg *config.Config, opts *addOptions, args []string) error {
return ErrSilent
}

base, err := git.RevParse(currentBranch)
if err != nil {
cfg.Warningf("could not resolve base SHA for %s: %s", currentBranch, err)
base := adoptedBase
if !adopted {
base, err = git.RevParse(currentBranch)
if err != nil {
cfg.Warningf("could not resolve base SHA for %s: %s", currentBranch, err)
}
}
s.Branches = append(s.Branches, stack.BranchRef{Branch: branchName, Base: base})

Expand Down
41 changes: 41 additions & 0 deletions cmd/add_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,11 @@ func TestAdd_AdoptsExistingBranch(t *testing.T) {
GitDirFn: func() (string, error) { return gitDir, nil },
CurrentBranchFn: func() (string, error) { return "b1", nil },
BranchExistsFn: func(name string) bool { return name == "existing-branch" },
MergeBaseFn: func(parent, branch string) (string, error) {
assert.Equal(t, "b1", parent)
assert.Equal(t, "existing-branch", branch)
return "common-base", nil
},
CreateBranchFn: func(name, base string) error {
createBranchCalled = true
return nil
Expand All @@ -472,6 +477,8 @@ func TestAdd_AdoptsExistingBranch(t *testing.T) {
require.NoError(t, err)
names := sf.Stacks[0].BranchNames()
assert.Equal(t, "existing-branch", names[len(names)-1], "adopted branch appended to stack")
assert.Equal(t, "common-base", sf.Stacks[0].Branches[len(sf.Stacks[0].Branches)-1].Base,
"adopted branch should record the actual common ancestor")
}

func TestAdd_RejectsExistingBranchInStack(t *testing.T) {
Expand Down Expand Up @@ -520,6 +527,7 @@ func TestAdd_AdoptsExistingBranchWithCommit(t *testing.T) {
GitDirFn: func() (string, error) { return gitDir, nil },
CurrentBranchFn: func() (string, error) { return "b1", nil },
BranchExistsFn: func(name string) bool { return name == "existing-branch" },
MergeBaseFn: func(string, string) (string, error) { return "common-base", nil },
RevParseMultiFn: func(refs []string) ([]string, error) {
return []string{"aaa", "bbb"}, nil // different SHAs = branch has commits
},
Expand Down Expand Up @@ -548,3 +556,36 @@ func TestAdd_AdoptsExistingBranchWithCommit(t *testing.T) {
assert.True(t, commitCalled, "Commit should be called on the adopted branch")
assert.Contains(t, output, "Adopted")
}

func TestAdd_AdoptExistingBranchWithoutCommonBaseFails(t *testing.T) {
gitDir := t.TempDir()
saveStack(t, gitDir, stack.Stack{
Trunk: stack.BranchRef{Branch: "main"},
Branches: []stack.BranchRef{{Branch: "b1"}},
})

checkedOut := false
restore := git.SetOps(&git.MockOps{
GitDirFn: func() (string, error) { return gitDir, nil },
CurrentBranchFn: func() (string, error) { return "b1", nil },
BranchExistsFn: func(name string) bool { return name == "unrelated" },
MergeBaseFn: func(string, string) (string, error) { return "", assert.AnError },
CheckoutBranchFn: func(string) error {
checkedOut = true
return nil
},
})
defer restore()

cfg, outR, errR := config.NewTestConfig()
err := runAdd(cfg, &addOptions{}, []string{"unrelated"})
output := collectOutput(cfg, outR, errR)

assert.ErrorIs(t, err, ErrSilent)
assert.False(t, checkedOut)
assert.Contains(t, output, "failed to determine the common base")

sf, loadErr := stack.Load(gitDir)
require.NoError(t, loadErr)
assert.Equal(t, []string{"b1"}, sf.Stacks[0].BranchNames())
}
Loading