From 563839c54921cb694c6d0d4739b90771fa0c5979 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Dec 2025 15:22:54 +0000 Subject: [PATCH 1/5] Refactor: Move worktrees to home directory Co-authored-by: dahl.jonatan --- cmd/worktree.go | 103 +++++++----------------------------------------- 1 file changed, 14 insertions(+), 89 deletions(-) diff --git a/cmd/worktree.go b/cmd/worktree.go index d18e300..e22ae75 100644 --- a/cmd/worktree.go +++ b/cmd/worktree.go @@ -1,7 +1,6 @@ package cmd import ( - "bufio" "fmt" "os" "path/filepath" @@ -18,8 +17,8 @@ var worktreePrune bool var worktreeCmd = &cobra.Command{ Use: "worktree [base-branch]", - Short: "Create a worktree in .worktrees/ directory", - Long: `Create a git worktree in the .worktrees/ directory for the specified branch. + Short: "Create a worktree in ~/.stack-worktrees/ directory", + Long: `Create a git worktree in the ~/.stack-worktrees/ directory for the specified branch. If the branch exists locally or on the remote, it will be used. If the branch doesn't exist, a new branch will be created from the current branch @@ -78,19 +77,14 @@ func init() { } func runWorktree(gitClient git.GitClient, githubClient github.GitHubClient, branchName, baseBranch string) error { - // Get repo root - repoRoot, err := gitClient.GetRepoRoot() + // Get home directory + homeDir, err := os.UserHomeDir() if err != nil { - return fmt.Errorf("failed to get repo root: %w", err) - } - - // Ensure .worktrees is in .gitignore - if err := ensureWorktreesIgnored(repoRoot); err != nil { - return fmt.Errorf("failed to update .gitignore: %w", err) + return fmt.Errorf("failed to get home directory: %w", err) } // Worktree path - worktreePath := filepath.Join(repoRoot, ".worktrees", branchName) + worktreePath := filepath.Join(homeDir, ".stack-worktrees", branchName) // Check if worktree already exists if _, err := os.Stat(worktreePath); err == nil { @@ -198,17 +192,17 @@ func createWorktreeForExisting(gitClient git.GitClient, branchName, worktreePath } func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) error { - // Get repo root - repoRoot, err := gitClient.GetRepoRoot() + // Get home directory + homeDir, err := os.UserHomeDir() if err != nil { - return fmt.Errorf("failed to get repo root: %w", err) + return fmt.Errorf("failed to get home directory: %w", err) } - worktreesDir := filepath.Join(repoRoot, ".worktrees") + worktreesDir := filepath.Join(homeDir, ".stack-worktrees") - // Check if .worktrees directory exists + // Check if ~/.stack-worktrees directory exists if _, err := os.Stat(worktreesDir); os.IsNotExist(err) { - fmt.Println("No .worktrees directory found.") + fmt.Println("No ~/.stack-worktrees directory found.") return nil } @@ -218,7 +212,7 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) return fmt.Errorf("failed to list worktrees: %w", err) } - // Filter to only worktrees in .worktrees/ directory + // Filter to only worktrees in ~/.stack-worktrees directory var worktreesToCheck []struct { path string branch string @@ -233,7 +227,7 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) } if len(worktreesToCheck) == 0 { - fmt.Println("No worktrees found in .worktrees/ directory.") + fmt.Println("No worktrees found in ~/.stack-worktrees directory.") return nil } @@ -294,72 +288,3 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) return nil } - -func ensureWorktreesIgnored(repoRoot string) error { - gitignorePath := filepath.Join(repoRoot, ".gitignore") - - // Check if .worktrees is already in .gitignore - if _, err := os.Stat(gitignorePath); err == nil { - file, err := os.Open(gitignorePath) - if err != nil { - return err - } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if line == ".worktrees" || line == ".worktrees/" { - return nil // Already ignored - } - } - if err := scanner.Err(); err != nil { - return err - } - } - - if dryRun { - fmt.Println(" [DRY RUN] Adding .worktrees to .gitignore") - return nil - } - - // Append .worktrees to .gitignore - file, err := os.OpenFile(gitignorePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return err - } - defer file.Close() - - // Check if file ends with newline, if not add one - info, err := file.Stat() - if err != nil { - return err - } - - var prefix string - if info.Size() > 0 { - // Read last byte to check for newline - tempFile, err := os.Open(gitignorePath) - if err != nil { - return err - } - defer tempFile.Close() - - buf := make([]byte, 1) - _, err = tempFile.ReadAt(buf, info.Size()-1) - if err != nil { - return err - } - if buf[0] != '\n' { - prefix = "\n" - } - } - - _, err = file.WriteString(prefix + ".worktrees/\n") - if err != nil { - return err - } - - fmt.Println("Added .worktrees/ to .gitignore") - return nil -} From 19fd6477ae670c0acb757cef03006bcc66c56d66 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Dec 2025 15:30:55 +0000 Subject: [PATCH 2/5] Checkpoint before follow-up message Co-authored-by: dahl.jonatan --- cmd/worktree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/worktree.go b/cmd/worktree.go index e22ae75..410592d 100644 --- a/cmd/worktree.go +++ b/cmd/worktree.go @@ -17,8 +17,8 @@ var worktreePrune bool var worktreeCmd = &cobra.Command{ Use: "worktree [base-branch]", - Short: "Create a worktree in ~/.stack-worktrees/ directory", - Long: `Create a git worktree in the ~/.stack-worktrees/ directory for the specified branch. + Short: "Create a worktree in ~/stack/worktrees directory", + Long: `Create a git worktree in the ~/stack/worktrees directory for the specified branch. If the branch exists locally or on the remote, it will be used. If the branch doesn't exist, a new branch will be created from the current branch From 53026c302f09a9037b8ec4d73f48bfcc9e7099e8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Dec 2025 15:31:33 +0000 Subject: [PATCH 3/5] Refactor worktree directory to ~/.stack/worktrees Co-authored-by: dahl.jonatan --- cmd/worktree.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/worktree.go b/cmd/worktree.go index 410592d..64115cd 100644 --- a/cmd/worktree.go +++ b/cmd/worktree.go @@ -17,8 +17,8 @@ var worktreePrune bool var worktreeCmd = &cobra.Command{ Use: "worktree [base-branch]", - Short: "Create a worktree in ~/stack/worktrees directory", - Long: `Create a git worktree in the ~/stack/worktrees directory for the specified branch. + Short: "Create a worktree in ~/.stack/worktrees directory", + Long: `Create a git worktree in the ~/.stack/worktrees directory for the specified branch. If the branch exists locally or on the remote, it will be used. If the branch doesn't exist, a new branch will be created from the current branch @@ -84,7 +84,7 @@ func runWorktree(gitClient git.GitClient, githubClient github.GitHubClient, bran } // Worktree path - worktreePath := filepath.Join(homeDir, ".stack-worktrees", branchName) + worktreePath := filepath.Join(homeDir, ".stack", "worktrees", branchName) // Check if worktree already exists if _, err := os.Stat(worktreePath); err == nil { @@ -198,11 +198,11 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) return fmt.Errorf("failed to get home directory: %w", err) } - worktreesDir := filepath.Join(homeDir, ".stack-worktrees") + worktreesDir := filepath.Join(homeDir, ".stack", "worktrees") - // Check if ~/.stack-worktrees directory exists + // Check if ~/.stack/worktrees directory exists if _, err := os.Stat(worktreesDir); os.IsNotExist(err) { - fmt.Println("No ~/.stack-worktrees directory found.") + fmt.Println("No ~/.stack/worktrees directory found.") return nil } @@ -212,7 +212,7 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) return fmt.Errorf("failed to list worktrees: %w", err) } - // Filter to only worktrees in ~/.stack-worktrees directory + // Filter to only worktrees in ~/.stack/worktrees directory var worktreesToCheck []struct { path string branch string @@ -227,7 +227,7 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) } if len(worktreesToCheck) == 0 { - fmt.Println("No worktrees found in ~/.stack-worktrees directory.") + fmt.Println("No worktrees found in ~/.stack/worktrees directory.") return nil } From f9e456d16d35880ddb3b8b57f07df8ab79dc75aa Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Dec 2025 15:34:47 +0000 Subject: [PATCH 4/5] feat: Organize worktrees by repository name Co-authored-by: dahl.jonatan --- cmd/worktree.go | 30 +++++++++++++++++++++--------- internal/git/git.go | 14 ++++++++++++++ internal/git/interface.go | 1 + internal/testutil/mocks.go | 5 +++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/cmd/worktree.go b/cmd/worktree.go index 64115cd..4b93509 100644 --- a/cmd/worktree.go +++ b/cmd/worktree.go @@ -17,8 +17,8 @@ var worktreePrune bool var worktreeCmd = &cobra.Command{ Use: "worktree [base-branch]", - Short: "Create a worktree in ~/.stack/worktrees directory", - Long: `Create a git worktree in the ~/.stack/worktrees directory for the specified branch. + Short: "Create a worktree in ~/.stack/worktrees/ directory", + Long: `Create a git worktree in the ~/.stack/worktrees/ directory for the specified branch. If the branch exists locally or on the remote, it will be used. If the branch doesn't exist, a new branch will be created from the current branch @@ -83,8 +83,14 @@ func runWorktree(gitClient git.GitClient, githubClient github.GitHubClient, bran return fmt.Errorf("failed to get home directory: %w", err) } - // Worktree path - worktreePath := filepath.Join(homeDir, ".stack", "worktrees", branchName) + // Get repository name + repoName, err := gitClient.GetRepoName() + if err != nil { + return fmt.Errorf("failed to get repo name: %w", err) + } + + // Worktree path: ~/.stack/worktrees// + worktreePath := filepath.Join(homeDir, ".stack", "worktrees", repoName, branchName) // Check if worktree already exists if _, err := os.Stat(worktreePath); err == nil { @@ -198,11 +204,17 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) return fmt.Errorf("failed to get home directory: %w", err) } - worktreesDir := filepath.Join(homeDir, ".stack", "worktrees") + // Get repository name + repoName, err := gitClient.GetRepoName() + if err != nil { + return fmt.Errorf("failed to get repo name: %w", err) + } + + worktreesDir := filepath.Join(homeDir, ".stack", "worktrees", repoName) - // Check if ~/.stack/worktrees directory exists + // Check if ~/.stack/worktrees/ directory exists if _, err := os.Stat(worktreesDir); os.IsNotExist(err) { - fmt.Println("No ~/.stack/worktrees directory found.") + fmt.Printf("No ~/.stack/worktrees/%s directory found.\n", repoName) return nil } @@ -212,7 +224,7 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) return fmt.Errorf("failed to list worktrees: %w", err) } - // Filter to only worktrees in ~/.stack/worktrees directory + // Filter to only worktrees in ~/.stack/worktrees/ directory var worktreesToCheck []struct { path string branch string @@ -227,7 +239,7 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) } if len(worktreesToCheck) == 0 { - fmt.Println("No worktrees found in ~/.stack/worktrees directory.") + fmt.Printf("No worktrees found in ~/.stack/worktrees/%s directory.\n", repoName) return nil } diff --git a/internal/git/git.go b/internal/git/git.go index 9e537ed..06305af 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -58,6 +58,20 @@ func (c *gitClient) GetRepoRoot() (string, error) { return c.runCmd("rev-parse", "--show-toplevel") } +// GetRepoName returns the name of the git repository (directory name) +func (c *gitClient) GetRepoName() (string, error) { + repoRoot, err := c.GetRepoRoot() + if err != nil { + return "", err + } + // Extract the last component of the path (the directory name) + parts := strings.Split(repoRoot, "/") + if len(parts) == 0 { + return "", fmt.Errorf("invalid repo root path: %s", repoRoot) + } + return parts[len(parts)-1], nil +} + // GetCurrentBranch returns the name of the currently checked out branch func (c *gitClient) GetCurrentBranch() (string, error) { return c.runCmd("branch", "--show-current") diff --git a/internal/git/interface.go b/internal/git/interface.go index 6fdafde..10303b8 100644 --- a/internal/git/interface.go +++ b/internal/git/interface.go @@ -3,6 +3,7 @@ package git // GitClient defines the interface for all git operations type GitClient interface { GetRepoRoot() (string, error) + GetRepoName() (string, error) GetCurrentBranch() (string, error) ListBranches() ([]string, error) GetConfig(key string) string diff --git a/internal/testutil/mocks.go b/internal/testutil/mocks.go index 2938279..073b7ce 100644 --- a/internal/testutil/mocks.go +++ b/internal/testutil/mocks.go @@ -15,6 +15,11 @@ func (m *MockGitClient) GetRepoRoot() (string, error) { return args.String(0), args.Error(1) } +func (m *MockGitClient) GetRepoName() (string, error) { + args := m.Called() + return args.String(0), args.Error(1) +} + func (m *MockGitClient) GetCurrentBranch() (string, error) { args := m.Called() return args.String(0), args.Error(1) From a98ec7c83f2a6855d4f3d6fefb443397757e5758 Mon Sep 17 00:00:00 2001 From: Jonatan Dahl Date: Fri, 30 Jan 2026 12:03:51 -0500 Subject: [PATCH 5/5] feat: Add --list and --all flags to worktree command - Add `stack worktree --list` to show worktrees for current repo - Add `stack worktree --list --all` to show worktrees for all repos - Add `stack worktree --prune --all` to remove all worktrees - Show paths in "no worktrees found" messages for clarity Co-Authored-By: Claude Opus 4.5 --- cmd/sync.go | 9 +- cmd/worktree.go | 221 +++++++++++++++++++++++++++++++++++++------- internal/git/git.go | 8 +- 3 files changed, 194 insertions(+), 44 deletions(-) diff --git a/cmd/sync.go b/cmd/sync.go index f9db1ec..a8e0c6d 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -429,7 +429,7 @@ func runSync(gitClient git.GitClient, githubClient github.GitHubClient) error { } // Wait for parallel network operations to complete - if err := spinner.WrapWithSuccess("Fetching from origin and loading PRs...", "Fetched from origin and loaded PRs", func() error { + if err := spinner.WrapWithSuccess("Fetching from origin and loading PRs...", "✓ Fetched from origin and loaded PRs", func() error { wg.Wait() return nil }); err != nil { @@ -493,7 +493,7 @@ func runSync(gitClient git.GitClient, githubClient github.GitHubClient) error { continue } - fmt.Printf("%s Processing %s...\n", progress, ui.Branch(branch.Name)) + fmt.Printf("\n%s %s\n", progress, ui.Branch(branch.Name)) // Check if parent PR is merged oldParent := "" // Track old parent for --onto rebase @@ -870,8 +870,6 @@ func runSync(gitClient git.GitClient, githubClient github.GitHubClient) error { } else { fmt.Printf(" No PR found (create one with '%s')\n", ui.Command("gh pr create")) } - - fmt.Println() } // Return to original branch @@ -880,9 +878,8 @@ func runSync(gitClient git.GitClient, githubClient github.GitHubClient) error { fmt.Fprintf(os.Stderr, "Warning: failed to return to original branch: %v\n", err) } - fmt.Println() - // Display the updated stack status (reuse prCache to avoid redundant API call) + fmt.Println() if err := displayStatusAfterSync(gitClient, githubClient, prCache); err != nil { // Don't fail if we can't display status, just warn fmt.Fprintf(os.Stderr, "Warning: failed to display stack status: %v\n", err) diff --git a/cmd/worktree.go b/cmd/worktree.go index 4b93509..6bd9cb1 100644 --- a/cmd/worktree.go +++ b/cmd/worktree.go @@ -14,6 +14,8 @@ import ( ) var worktreePrune bool +var worktreePruneAll bool +var worktreeList bool var worktreeCmd = &cobra.Command{ Use: "worktree [base-branch]", @@ -23,7 +25,9 @@ var worktreeCmd = &cobra.Command{ If the branch exists locally or on the remote, it will be used. If the branch doesn't exist, a new branch will be created from the current branch (or from base-branch if specified) and stack tracking will be set up automatically. -Use --prune to clean up worktrees for branches with merged PRs.`, +Use --list to show worktrees for this repository, or --list --all for all repos. +Use --prune to clean up worktrees for branches with merged PRs. +Use --prune --all to remove all worktrees for this repository.`, Example: ` # Create worktree for new branch (from current branch, with stack tracking) stack worktree my-feature @@ -33,12 +37,27 @@ Use --prune to clean up worktrees for branches with merged PRs.`, # Create worktree for existing local or remote branch stack worktree existing-branch + # List worktrees for this repository + stack worktree --list + + # List worktrees for all repositories + stack worktree --list --all + # Clean up worktrees for merged branches stack worktree --prune + # Remove all worktrees for this repository + stack worktree --prune --all + # Preview without executing stack worktree my-feature --dry-run`, Args: func(cmd *cobra.Command, args []string) error { + if worktreeList { + if len(args) > 0 { + return fmt.Errorf("--list does not take arguments") + } + return nil + } if worktreePrune { if len(args) > 0 { return fmt.Errorf("--prune does not take a branch argument") @@ -56,7 +75,9 @@ Use --prune to clean up worktrees for branches with merged PRs.`, githubClient := github.NewGitHubClient(repo) var err error - if worktreePrune { + if worktreeList { + err = runWorktreeList(gitClient) + } else if worktreePrune { err = runWorktreePrune(gitClient, githubClient) } else { var baseBranch string @@ -73,7 +94,9 @@ Use --prune to clean up worktrees for branches with merged PRs.`, } func init() { + worktreeCmd.Flags().BoolVarP(&worktreeList, "list", "l", false, "List all worktrees for this repository") worktreeCmd.Flags().BoolVar(&worktreePrune, "prune", false, "Remove worktrees for branches with merged PRs") + worktreeCmd.Flags().BoolVarP(&worktreePruneAll, "all", "a", false, "With --list: show all repos. With --prune: remove all worktrees") } func runWorktree(gitClient git.GitClient, githubClient github.GitHubClient, branchName, baseBranch string) error { @@ -106,6 +129,126 @@ func runWorktree(gitClient git.GitClient, githubClient github.GitHubClient, bran return createWorktreeForExisting(gitClient, branchName, worktreePath) } +func runWorktreeList(gitClient git.GitClient) error { + // Get home directory + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get home directory: %w", err) + } + + worktreesBaseDir := filepath.Join(homeDir, ".stack", "worktrees") + + // Check if ~/.stack/worktrees directory exists + if _, err := os.Stat(worktreesBaseDir); os.IsNotExist(err) { + fmt.Printf("No worktrees found in %s\n", worktreesBaseDir) + return nil + } + + if worktreePruneAll { + // List worktrees for all repositories + return listAllWorktrees(worktreesBaseDir) + } + + // List worktrees for current repository only + repoName, err := gitClient.GetRepoName() + if err != nil { + return fmt.Errorf("failed to get repo name: %w", err) + } + + worktreesDir := filepath.Join(worktreesBaseDir, repoName) + + // Check if ~/.stack/worktrees/ directory exists + if _, err := os.Stat(worktreesDir); os.IsNotExist(err) { + fmt.Printf("No worktrees found in %s\n", worktreesDir) + return nil + } + + // Get all worktrees and their branches + worktreeBranches, err := gitClient.GetWorktreeBranches() + if err != nil { + return fmt.Errorf("failed to list worktrees: %w", err) + } + + // Filter to only worktrees in ~/.stack/worktrees/ directory + var worktrees []struct { + path string + branch string + } + for branch, path := range worktreeBranches { + if strings.HasPrefix(path, worktreesDir) { + worktrees = append(worktrees, struct { + path string + branch string + }{path: path, branch: branch}) + } + } + + if len(worktrees) == 0 { + fmt.Printf("No worktrees found in %s\n", worktreesDir) + return nil + } + + fmt.Printf("Worktrees in %s:\n\n", worktreesDir) + for _, wt := range worktrees { + fmt.Printf(" %s\n %s\n\n", ui.Branch(wt.branch), wt.path) + } + + return nil +} + +func listAllWorktrees(worktreesBaseDir string) error { + // Read all repo directories + entries, err := os.ReadDir(worktreesBaseDir) + if err != nil { + return fmt.Errorf("failed to read worktrees directory: %w", err) + } + + if len(entries) == 0 { + fmt.Println("No worktrees found.") + return nil + } + + totalCount := 0 + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + repoName := entry.Name() + repoWorktreesDir := filepath.Join(worktreesBaseDir, repoName) + + // Read worktree directories for this repo + worktreeEntries, err := os.ReadDir(repoWorktreesDir) + if err != nil { + continue + } + + var branches []string + for _, wt := range worktreeEntries { + if wt.IsDir() { + branches = append(branches, wt.Name()) + } + } + + if len(branches) == 0 { + continue + } + + fmt.Printf("%s:\n", repoName) + for _, branch := range branches { + path := filepath.Join(repoWorktreesDir, branch) + fmt.Printf(" %s\n %s\n\n", ui.Branch(branch), path) + } + totalCount += len(branches) + } + + if totalCount == 0 { + fmt.Printf("No worktrees found in %s\n", worktreesBaseDir) + } + + return nil +} + func createNewBranchWorktree(gitClient git.GitClient, branchName, baseBranch, worktreePath string) error { // Check if branch already exists if gitClient.BranchExists(branchName) { @@ -243,40 +386,52 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) return nil } - // Fetch PR info - var prCache map[string]*github.PRInfo - if err := spinner.WrapWithSuccess("Fetching PRs...", "Fetched PRs", func() error { - var prErr error - prCache, prErr = githubClient.GetAllPRs() - return prErr - }); err != nil { - return fmt.Errorf("failed to fetch PRs: %w", err) - } - - // Find worktrees with merged PRs - var mergedWorktrees []struct { + // Determine which worktrees to prune + var worktreesToPrune []struct { path string branch string } - for _, wt := range worktreesToCheck { - if pr, exists := prCache[wt.branch]; exists && pr.State == "MERGED" { - mergedWorktrees = append(mergedWorktrees, wt) + + if worktreePruneAll { + // Prune all worktrees + worktreesToPrune = worktreesToCheck + + fmt.Println() + fmt.Printf("Found %d worktree(s) to remove:\n", len(worktreesToPrune)) + for _, wt := range worktreesToPrune { + fmt.Printf(" - %s (%s)\n", wt.branch, wt.path) + } + fmt.Println() + } else { + // Prune only worktrees with merged PRs + var prCache map[string]*github.PRInfo + if err := spinner.WrapWithSuccess("Fetching PRs...", "Fetched PRs", func() error { + var prErr error + prCache, prErr = githubClient.GetAllPRs() + return prErr + }); err != nil { + return fmt.Errorf("failed to fetch PRs: %w", err) } - } - if len(mergedWorktrees) == 0 { - fmt.Println("\nNo worktrees with merged PRs to prune.") - return nil - } + for _, wt := range worktreesToCheck { + if pr, exists := prCache[wt.branch]; exists && pr.State == "MERGED" { + worktreesToPrune = append(worktreesToPrune, wt) + } + } - // Show what will be pruned - fmt.Println() - fmt.Printf("Found %d worktree(s) with merged PRs:\n", len(mergedWorktrees)) - for _, wt := range mergedWorktrees { - pr := prCache[wt.branch] - fmt.Printf(" - %s (%s, PR #%d)\n", ui.Branch(wt.branch), wt.path, pr.Number) + if len(worktreesToPrune) == 0 { + fmt.Println("\nNo worktrees with merged PRs to prune.") + return nil + } + + fmt.Println() + fmt.Printf("Found %d worktree(s) with merged PRs:\n", len(worktreesToPrune)) + for _, wt := range worktreesToPrune { + pr := prCache[wt.branch] + fmt.Printf(" - %s (%s, PR #%d)\n", ui.Branch(wt.branch), wt.path, pr.Number) + } + fmt.Println() } - fmt.Println() if dryRun { fmt.Println("Dry run - no changes made.") @@ -284,8 +439,8 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) } // Remove each worktree - for i, wt := range mergedWorktrees { - fmt.Printf("%s Removing worktree for %s...\n", ui.Progress(i+1, len(mergedWorktrees)), ui.Branch(wt.branch)) + for i, wt := range worktreesToPrune { + fmt.Printf("%s Removing worktree for %s...\n", ui.Progress(i+1, len(worktreesToPrune)), ui.Branch(wt.branch)) if err := gitClient.RemoveWorktree(wt.path); err != nil { fmt.Fprintf(os.Stderr, " Warning: failed to remove worktree: %v\n", err) @@ -296,7 +451,9 @@ func runWorktreePrune(gitClient git.GitClient, githubClient github.GitHubClient) fmt.Println() fmt.Println(ui.Success("Worktree prune complete!")) - fmt.Printf("Tip: Run '%s' to also delete the merged branches.\n", ui.Command("stack prune")) + if !worktreePruneAll { + fmt.Printf("Tip: Run '%s' to also delete the merged branches.\n", ui.Command("stack prune")) + } return nil } diff --git a/internal/git/git.go b/internal/git/git.go index 06305af..7c5f9bf 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "os/exec" + "path/filepath" "strings" ) @@ -64,12 +65,7 @@ func (c *gitClient) GetRepoName() (string, error) { if err != nil { return "", err } - // Extract the last component of the path (the directory name) - parts := strings.Split(repoRoot, "/") - if len(parts) == 0 { - return "", fmt.Errorf("invalid repo root path: %s", repoRoot) - } - return parts[len(parts)-1], nil + return filepath.Base(repoRoot), nil } // GetCurrentBranch returns the name of the currently checked out branch