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
76 changes: 48 additions & 28 deletions cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func runStatus(gitClient git.GitClient, githubClient github.GitHubClient) error
prCache = make(map[string]*github.PRInfo)
}

// Build the stack tree (runs in parallel with PR fetch)
// Build the stack tree AND wait for PR fetch (runs in parallel)
if err := spinner.WrapWithAutoDelay("Loading stack...", 300*time.Millisecond, func() error {
// Get current branch
var err error
Expand Down Expand Up @@ -125,6 +125,45 @@ func runStatus(gitClient git.GitClient, githubClient github.GitHubClient) error

// Get ALL branch names in the tree (including intermediate branches without stackparent)
allTreeBranches = getAllBranchNamesFromTree(tree)

// Wait for PR fetch to complete (if running)
if !noPR {
wg.Wait()

// GetAllPRs only fetches open PRs (to avoid 502 timeouts on large repos).
// For branches in our stack that aren't in the cache, check individually
// to detect merged PRs that need special handling.
// OPTIMIZATION: Only check branches in the current tree, not all stack branches.
branchSet := make(map[string]bool)
for _, name := range allTreeBranches {
branchSet[name] = true
}
baseBranch := stack.GetBaseBranch(gitClient)

for _, branch := range stackBranches {
// Skip branches not in the current tree
if !branchSet[branch.Name] {
continue
}
// Skip if already in cache (has open PR)
if _, exists := prCache[branch.Name]; exists {
continue
}
// Fetch PR info for this branch (might be merged or non-existent)
if pr, err := githubClient.GetPRForBranch(branch.Name); err == nil && pr != nil {
prCache[branch.Name] = pr
}
// Also check parent if not in cache and not base branch
if branch.Parent != baseBranch {
if _, exists := prCache[branch.Parent]; !exists {
if pr, err := githubClient.GetPRForBranch(branch.Parent); err == nil && pr != nil {
prCache[branch.Parent] = pr
}
}
}
}
}

return nil
}); err != nil {
return err
Expand All @@ -143,6 +182,14 @@ func runStatus(gitClient git.GitClient, githubClient github.GitHubClient) error
// Check this BEFORE waiting for PR fetch to avoid long delays
if tree == nil {
baseBranch := stack.GetBaseBranch(gitClient)

// Don't offer to add the base branch to a stack - it can't have a parent
if currentBranch == baseBranch {
fmt.Printf("Branch '%s' is the base branch and cannot be part of a stack.\n", ui.Branch(currentBranch))
fmt.Printf("\nUse '%s' to create a new stack branch.\n", ui.Command("stack new <branch-name>"))
return nil
}

fmt.Printf("Current branch '%s' is not part of a stack.\n\n", ui.Branch(currentBranch))
fmt.Printf("Add to stack with '%s' as parent? [Y/n] ", ui.Branch(baseBranch))

Expand All @@ -167,33 +214,6 @@ func runStatus(gitClient git.GitClient, githubClient github.GitHubClient) error
return nil
}

// Wait for PR fetch to complete (if running)
if !noPR {
wg.Wait()

// GetAllPRs only fetches open PRs (to avoid 502 timeouts on large repos).
// For branches in our stack that aren't in the cache, check individually
// to detect merged PRs that need special handling.
for _, branch := range stackBranches {
// Skip if already in cache (has open PR)
if _, exists := prCache[branch.Name]; exists {
continue
}
// Fetch PR info for this branch (might be merged or non-existent)
if pr, err := githubClient.GetPRForBranch(branch.Name); err == nil && pr != nil {
prCache[branch.Name] = pr
}
// Also check parent if not in cache and not base branch
if branch.Parent != stack.GetBaseBranch(gitClient) {
if _, exists := prCache[branch.Parent]; !exists {
if pr, err := githubClient.GetPRForBranch(branch.Parent); err == nil && pr != nil {
prCache[branch.Parent] = pr
}
}
}
}
}

// Print the tree
fmt.Println()
printTree(gitClient, tree, "", true, currentBranch, prCache)
Expand Down
7 changes: 4 additions & 3 deletions internal/spinner/spinner.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (s *Spinner) run() {
case <-ticker.C:
s.mu.Lock()
frame := s.frames[frameIdx%len(s.frames)]
fmt.Fprintf(s.writer, "\r%s %s", frame, s.message)
fmt.Fprintf(s.writer, "\r%s %s", frame, dim.Sprint(s.message))
s.mu.Unlock()
frameIdx++
}
Expand All @@ -133,13 +133,14 @@ func Wrap(message string, fn func() error) error {
var (
green = color.New(color.FgGreen)
red = color.New(color.FgRed)
dim = color.New(color.Faint)
)

// WrapWithSuccess runs a function with a spinner and shows success/error message
func WrapWithSuccess(message, successMessage string, fn func() error) error {
if !Enabled {
// When disabled (verbose mode), print message and run
fmt.Println(message)
fmt.Println(dim.Sprint(message))
err := fn()
if err != nil {
fmt.Printf("%s Error: %v\n", red.Sprint("✗"), err)
Expand All @@ -160,7 +161,7 @@ func WrapWithSuccess(message, successMessage string, fn func() error) error {
func WrapWithSuccessIndented(indent, message, successMessage string, fn func() error) error {
if !Enabled {
// When disabled (verbose mode), print message and run
fmt.Println(indent + message)
fmt.Println(indent + dim.Sprint(message))
err := fn()
if err != nil {
fmt.Printf("%s%s Error: %v\n", indent, red.Sprint("✗"), err)
Expand Down