diff --git a/cmd/status.go b/cmd/status.go index e81b31e..b27fd61 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -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 @@ -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 @@ -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 ")) + 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)) @@ -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) diff --git a/internal/spinner/spinner.go b/internal/spinner/spinner.go index e1a7f51..ac2bf42 100644 --- a/internal/spinner/spinner.go +++ b/internal/spinner/spinner.go @@ -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++ } @@ -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) @@ -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)