Skip to content

update add cmd to adopt existing branches#101

Open
skarim wants to merge 2 commits into
skarim/prompter-improvementsfrom
skarim/add-existing-branch
Open

update add cmd to adopt existing branches#101
skarim wants to merge 2 commits into
skarim/prompter-improvementsfrom
skarim/add-existing-branch

Conversation

@skarim
Copy link
Copy Markdown
Collaborator

@skarim skarim commented May 19, 2026

Allow gh stack add to adopt existing branches

Previously, gh stack add rejected any branch name that already existed
in git with a blanket "branch already exists" error. This was overly
restrictive — users who create branches ahead of time (e.g. from the
GitHub UI or via git branch) had no way to incorporate them into a
stack without deleting and recreating them.

Now, if the specified branch exists in git but is not part of any
existing stack, add adopts it: it skips branch creation, checks out
the existing branch, and appends it to the stack metadata. This mirrors
the adopt-or-create pattern already used by gh stack init.

Branches that belong to another stack are still rejected by the existing
ValidateNoDuplicateBranch guard, so there is no risk of cross-stack
conflicts.

Behavioral summary:

  • Existing branch, not in any stack → adopted (checkout only, no create)
  • Existing branch, already in a stack → error (unchanged)
  • Non-existent branch → created (unchanged)
  • Staging/commit flags (-A, -u, -m) work with adopted branches

Tests added:

  • TestAdd_AdoptsExistingBranch
  • TestAdd_RejectsExistingBranchInStack
  • TestAdd_AdoptsExistingBranchWithCommit

Stack created with GitHub Stacks CLIGive Feedback 💬

@skarim skarim force-pushed the skarim/prompter-improvements branch from f105d53 to 9a6a247 Compare May 22, 2026 16:26
@skarim skarim force-pushed the skarim/add-existing-branch branch from 1fdd772 to d522c91 Compare May 22, 2026 16:27
@skarim skarim linked an issue May 23, 2026 that may be closed by this pull request
@skarim skarim marked this pull request as ready for review May 23, 2026 21:35
Copilot AI review requested due to automatic review settings May 23, 2026 21:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates gh stack add to allow adopting an existing local git branch (when it’s not already tracked by any stack) instead of rejecting it, aligning add with the adopt-or-create behavior used by gh stack init.

Changes:

  • Replace the “branch already exists” hard error with an adopt flow that skips branch creation when the branch already exists in git.
  • Update success messaging to distinguish between “created” vs “adopted” branches (with/without commits).
  • Add tests covering adoption, rejection when the branch is already in a stack, and adoption combined with commit flags.
Show a summary per file
File Description
cmd/add.go Adds adopt logic for existing branches and adjusts branch creation + output paths accordingly.
cmd/add_test.go Adds new tests for adopting existing branches and ensuring stack metadata updates.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (2)

cmd/add.go:207

  • When adopting an existing branch, stageAndValidate runs before CheckoutBranch(branchName). That means staging is done on the current branch and can also make the checkout fail due to staged/working-tree changes, so the subsequent commit may not apply cleanly to the adopted branch. Consider checking out the adopted branch first (or moving staging/commit to after checkout unconditionally) so -A/-u/-m reliably operate on the branch being added.

This issue also appears on line 183 of the same file.

	// 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)

	// Stage changes before creating the branch so we can fail early if
	// there's nothing to commit (avoids leaving an empty orphan branch).
	if wantsCommit {
		if err := stageAndValidate(cfg, opts); err != nil {
			return ErrSilent
		}
	}

	if !adopted {
		// Create the new branch from the current HEAD and check it out
		if err := git.CreateBranch(branchName, currentBranch); err != nil {
			cfg.Errorf("failed to create branch: %s", err)
			return ErrSilent
		}
	}

	if err := git.CheckoutBranch(branchName); err != nil {
		cfg.Errorf("failed to checkout branch: %s", err)
		return ErrSilent
	}

cmd/add.go:186

  • adopted := git.BranchExists(branchName) will adopt any existing local branch, even if it isn’t actually based on currentBranch. Previously, add always created the new layer from currentBranch, which guaranteed a linear “on top of the stack” relationship. It would be safer to validate the adopted branch is a descendant of currentBranch (e.g., using git.IsAncestor(currentBranch, branchName) or git.MergeBase) and error with a clear message if not.
	// 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)

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread cmd/add_test.go Outdated
Comment thread cmd/add_test.go
Comment on lines +551 to +591
func TestAdd_AdoptsExistingBranchWithCommit(t *testing.T) {
gitDir := t.TempDir()
saveStack(t, gitDir, stack.Stack{
Trunk: stack.BranchRef{Branch: "main"},
Branches: []stack.BranchRef{{Branch: "b1"}},
})

createBranchCalled := false
commitCalled := 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 == "existing-branch" },
RevParseMultiFn: func(refs []string) ([]string, error) {
return []string{"aaa", "bbb"}, nil // different SHAs = branch has commits
},
CreateBranchFn: func(name, base string) error {
createBranchCalled = true
return nil
},
CheckoutBranchFn: func(name string) error { return nil },
RevParseFn: func(ref string) (string, error) { return "abc", nil },
StageAllFn: func() error { return nil },
HasStagedChangesFn: func() bool { return true },
CommitFn: func(msg string) (string, error) {
commitCalled = true
return "def1234567890", nil
},
})
defer restore()

cfg, outR, errR := config.NewTestConfig()
err := runAdd(cfg, &addOptions{stageAll: true, message: "new commit"}, []string{"existing-branch"})
output := collectOutput(cfg, outR, errR)

require.NoError(t, err)
require.NotContains(t, output, "\u2717", "unexpected error")
assert.False(t, createBranchCalled, "CreateBranch should NOT be called")
assert.True(t, commitCalled, "Commit should be called on the adopted branch")
assert.Contains(t, output, "Adopted")
}
@skarim skarim force-pushed the skarim/add-existing-branch branch from d522c91 to 4f79dfe Compare May 23, 2026 22:05
@skarim skarim force-pushed the skarim/prompter-improvements branch 2 times, most recently from 6118a59 to 39f3375 Compare May 23, 2026 22:33
@skarim skarim force-pushed the skarim/add-existing-branch branch from 4f79dfe to 3ac2962 Compare May 23, 2026 22:33
Previously, `gh stack add` rejected any branch name that already existed
in git with a blanket "branch already exists" error. This was overly
restrictive — users who create branches ahead of time (e.g. from the
GitHub UI or via `git branch`) had no way to incorporate them into a
stack without deleting and recreating them.

Now, if the specified branch exists in git but is not part of any
existing stack, `add` adopts it: it skips branch creation, checks out
the existing branch, and appends it to the stack metadata. This mirrors
the adopt-or-create pattern already used by `gh stack init`.

Branches that belong to another stack are still rejected by the existing
`ValidateNoDuplicateBranch` guard, so there is no risk of cross-stack
conflicts.

Behavioral summary:
- Existing branch, not in any stack → adopted (checkout only, no create)
- Existing branch, already in a stack → error (unchanged)
- Non-existent branch → created (unchanged)
- Staging/commit flags (-A, -u, -m) work with adopted branches

Tests added:
- TestAdd_AdoptsExistingBranch
- TestAdd_RejectsExistingBranchInStack
- TestAdd_AdoptsExistingBranchWithCommit
@skarim skarim force-pushed the skarim/add-existing-branch branch from 3ac2962 to ae84c7d Compare May 24, 2026 00:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

allow to add existing branch to stack

2 participants