From da728176424df8ae199c7a5305336e24939c1597 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Dec 2025 15:22:54 +0000 Subject: [PATCH 1/4] 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 1d850ed..4c2598a 100644 --- a/cmd/worktree.go +++ b/cmd/worktree.go @@ -1,7 +1,6 @@ package cmd import ( - "bufio" "fmt" "os" "path/filepath" @@ -17,8 +16,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 @@ -76,19 +75,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 { @@ -196,17 +190,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 } @@ -216,7 +210,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 @@ -231,7 +225,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 } @@ -291,72 +285,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 efe0a3afa390309855f9d8ca02e139b8e2439e05 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Dec 2025 15:30:55 +0000 Subject: [PATCH 2/4] 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 4c2598a..6c3eca0 100644 --- a/cmd/worktree.go +++ b/cmd/worktree.go @@ -16,8 +16,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 aa2245227f860a8e71e97a44203893023d245f1c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Dec 2025 15:31:33 +0000 Subject: [PATCH 3/4] 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 6c3eca0..6c332b0 100644 --- a/cmd/worktree.go +++ b/cmd/worktree.go @@ -16,8 +16,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 @@ -82,7 +82,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 { @@ -196,11 +196,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 } @@ -210,7 +210,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 @@ -225,7 +225,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 e657537248ef8d2ecaa47455eb52e33e2e36727a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Dec 2025 15:34:47 +0000 Subject: [PATCH 4/4] 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 6c332b0..5e79272 100644 --- a/cmd/worktree.go +++ b/cmd/worktree.go @@ -16,8 +16,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 @@ -81,8 +81,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 { @@ -196,11 +202,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 } @@ -210,7 +222,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 @@ -225,7 +237,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 e4cb4f3..eb630d7 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 f98e9ec..4b412ec 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 bf54b88..ca1143b 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)