From 96e673a5bfe680b8a8fce45cebf4f080e9c12a1c Mon Sep 17 00:00:00 2001 From: Daniel Boroujerdi <274674+DBoroujerdi@users.noreply.github.com> Date: Wed, 5 Nov 2025 10:46:02 +0000 Subject: [PATCH] fix formatting --- cmd/worktree/delete.go | 56 ++++----- cmd/worktree/list.go | 238 ++++++++++++++++++------------------- cmd/worktree/open.go | 187 +++++++++++++++-------------- cmd/worktree/root.go | 26 ++-- internal/editor/editor.go | 16 +-- internal/git/repository.go | 12 +- main.go | 2 +- 7 files changed, 271 insertions(+), 266 deletions(-) diff --git a/cmd/worktree/delete.go b/cmd/worktree/delete.go index a2e7fc9..5fff7d1 100644 --- a/cmd/worktree/delete.go +++ b/cmd/worktree/delete.go @@ -1,11 +1,11 @@ package worktree import ( - "bytes" - "fmt" - "os" - "os/exec" - "strings" + "bytes" + "fmt" + "os" + "os/exec" + "strings" "github.com/spf13/cobra" "github.com/todoengineering/wt/internal/git" @@ -15,8 +15,8 @@ import ( var forceDelete bool var deleteCmd = &cobra.Command{ - Use: "delete [worktree-name]", - Short: "Delete a worktree (interactive)", + Use: "delete [worktree-name]", + Short: "Delete a worktree (interactive)", Long: `Interactive selection of worktree to delete using fzf. Shows worktree name and associated branch, requires explicit confirmation, @@ -85,26 +85,26 @@ prevents deletion of main repository worktree.`, os.Exit(1) } - if !forceDelete { - fmt.Printf("\nšŸ—‘ļø Worktree Deletion Confirmation\n") - fmt.Printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n") - fmt.Printf("Worktree: %s\n", selectedWorktree.Name) - fmt.Printf("Branch: %s\n", selectedWorktree.Branch) - fmt.Printf("Path: %s\n", selectedWorktree.Path) - fmt.Printf("\nāš ļø WARNING: This will permanently remove:\n") - fmt.Printf(" • The worktree directory and all its contents\n") - fmt.Printf(" • Any uncommitted changes in this worktree\n") - fmt.Printf(" • Associated tmux session (if exists)\n") - fmt.Printf("\nType 'yes' to confirm deletion: ") - - var confirmation string - fmt.Scanln(&confirmation) - - if strings.ToLower(confirmation) != "yes" { - fmt.Println("āœ… Deletion cancelled") - return - } - } + if !forceDelete { + fmt.Printf("\nšŸ—‘ļø Worktree Deletion Confirmation\n") + fmt.Printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n") + fmt.Printf("Worktree: %s\n", selectedWorktree.Name) + fmt.Printf("Branch: %s\n", selectedWorktree.Branch) + fmt.Printf("Path: %s\n", selectedWorktree.Path) + fmt.Printf("\nāš ļø WARNING: This will permanently remove:\n") + fmt.Printf(" • The worktree directory and all its contents\n") + fmt.Printf(" • Any uncommitted changes in this worktree\n") + fmt.Printf(" • Associated tmux session (if exists)\n") + fmt.Printf("\nType 'yes' to confirm deletion: ") + + var confirmation string + fmt.Scanln(&confirmation) + + if strings.ToLower(confirmation) != "yes" { + fmt.Println("āœ… Deletion cancelled") + return + } + } // Check for and kill associated tmux session // Standard session name is -. For backward compatibility, @@ -143,7 +143,7 @@ prevents deletion of main repository worktree.`, } func init() { - deleteCmd.Flags().BoolVar(&forceDelete, "force", false, "skip confirmation and delete the worktree") + deleteCmd.Flags().BoolVar(&forceDelete, "force", false, "skip confirmation and delete the worktree") } func selectWorktreeForDeletion(worktrees []git.Worktree) (git.Worktree, error) { diff --git a/cmd/worktree/list.go b/cmd/worktree/list.go index 53c4db6..81176e5 100644 --- a/cmd/worktree/list.go +++ b/cmd/worktree/list.go @@ -1,136 +1,136 @@ package worktree import ( - "encoding/json" - "fmt" - "os" + "encoding/json" + "fmt" + "os" - "github.com/spf13/cobra" - "github.com/todoengineering/wt/internal/git" + "github.com/spf13/cobra" + "github.com/todoengineering/wt/internal/git" ) var ( - listAll bool - listJSON bool - listPathOnly bool + listAll bool + listJSON bool + listPathOnly bool ) type listEntry struct { - Project string `json:"project"` - Name string `json:"name"` - Path string `json:"path"` - Branch string `json:"branch"` + Project string `json:"project"` + Name string `json:"name"` + Path string `json:"path"` + Branch string `json:"branch"` } var listCmd = &cobra.Command{ - Use: "list", - Short: "List worktrees", - Long: `Lists on-disk worktrees for the current repository or across all projects when --all is provided. Supports JSON and path-only output for scripting.`, - Run: func(cmd *cobra.Command, args []string) { - if listAll { - projects, err := git.ListAllProjects() - if err != nil { - fmt.Fprintf(os.Stderr, "Error listing projects: %v\n", err) - os.Exit(1) - } - - if listJSON { - var out []listEntry - for _, p := range projects { - for _, wt := range p.Worktrees { - out = append(out, listEntry{Project: p.Name, Name: wt.Name, Path: wt.Path, Branch: wt.Branch}) - } - } - enc := json.NewEncoder(os.Stdout) - enc.SetIndent("", " ") - if err := enc.Encode(out); err != nil { - fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) - os.Exit(1) - } - return - } - - if listPathOnly { - for _, p := range projects { - for _, wt := range p.Worktrees { - fmt.Println(wt.Path) - } - } - return - } - - if len(projects) == 0 { - fmt.Println("No projects with worktrees found") - fmt.Printf("Worktree base directory: %s\n", git.GetWorktreeBaseDir()) - return - } - - for _, p := range projects { - fmt.Printf("%s:\n", p.Name) - if len(p.Worktrees) == 0 { - fmt.Println(" (no worktrees)") - continue - } - for _, wt := range p.Worktrees { - fmt.Printf(" %s -> %s\n", wt.Name, wt.Path) - } - } - return - } - - if !git.IsGitRepository() { - fmt.Fprintf(os.Stderr, "Error: not in a git repository\n") - os.Exit(1) - } - - repoName, err := git.GetRepositoryName() - if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - - worktrees, err := git.ListWorktrees(repoName) - if err != nil { - fmt.Fprintf(os.Stderr, "Error listing worktrees: %v\n", err) - os.Exit(1) - } - - if listJSON { - var out []listEntry - for _, wt := range worktrees { - out = append(out, listEntry{Project: repoName, Name: wt.Name, Path: wt.Path, Branch: wt.Branch}) - } - enc := json.NewEncoder(os.Stdout) - enc.SetIndent("", " ") - if err := enc.Encode(out); err != nil { - fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) - os.Exit(1) - } - return - } - - if listPathOnly { - for _, wt := range worktrees { - fmt.Println(wt.Path) - } - return - } - - if len(worktrees) == 0 { - fmt.Printf("No worktrees found for repository '%s'\n", repoName) - fmt.Printf("Worktree directory: %s\n", git.GetWorktreeDir(repoName)) - return - } - - fmt.Printf("Worktrees for repository '%s':\n", repoName) - for _, wt := range worktrees { - fmt.Printf(" %s -> %s\n", wt.Name, wt.Path) - } - }, + Use: "list", + Short: "List worktrees", + Long: `Lists on-disk worktrees for the current repository or across all projects when --all is provided. Supports JSON and path-only output for scripting.`, + Run: func(cmd *cobra.Command, args []string) { + if listAll { + projects, err := git.ListAllProjects() + if err != nil { + fmt.Fprintf(os.Stderr, "Error listing projects: %v\n", err) + os.Exit(1) + } + + if listJSON { + var out []listEntry + for _, p := range projects { + for _, wt := range p.Worktrees { + out = append(out, listEntry{Project: p.Name, Name: wt.Name, Path: wt.Path, Branch: wt.Branch}) + } + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + if err := enc.Encode(out); err != nil { + fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) + os.Exit(1) + } + return + } + + if listPathOnly { + for _, p := range projects { + for _, wt := range p.Worktrees { + fmt.Println(wt.Path) + } + } + return + } + + if len(projects) == 0 { + fmt.Println("No projects with worktrees found") + fmt.Printf("Worktree base directory: %s\n", git.GetWorktreeBaseDir()) + return + } + + for _, p := range projects { + fmt.Printf("%s:\n", p.Name) + if len(p.Worktrees) == 0 { + fmt.Println(" (no worktrees)") + continue + } + for _, wt := range p.Worktrees { + fmt.Printf(" %s -> %s\n", wt.Name, wt.Path) + } + } + return + } + + if !git.IsGitRepository() { + fmt.Fprintf(os.Stderr, "Error: not in a git repository\n") + os.Exit(1) + } + + repoName, err := git.GetRepositoryName() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + worktrees, err := git.ListWorktrees(repoName) + if err != nil { + fmt.Fprintf(os.Stderr, "Error listing worktrees: %v\n", err) + os.Exit(1) + } + + if listJSON { + var out []listEntry + for _, wt := range worktrees { + out = append(out, listEntry{Project: repoName, Name: wt.Name, Path: wt.Path, Branch: wt.Branch}) + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + if err := enc.Encode(out); err != nil { + fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) + os.Exit(1) + } + return + } + + if listPathOnly { + for _, wt := range worktrees { + fmt.Println(wt.Path) + } + return + } + + if len(worktrees) == 0 { + fmt.Printf("No worktrees found for repository '%s'\n", repoName) + fmt.Printf("Worktree directory: %s\n", git.GetWorktreeDir(repoName)) + return + } + + fmt.Printf("Worktrees for repository '%s':\n", repoName) + for _, wt := range worktrees { + fmt.Printf(" %s -> %s\n", wt.Name, wt.Path) + } + }, } func init() { - listCmd.Flags().BoolVar(&listAll, "all", false, "list across all projects") - listCmd.Flags().BoolVar(&listJSON, "json", false, "output JSON for scripting") - listCmd.Flags().BoolVar(&listPathOnly, "path-only", false, "output only worktree paths") + listCmd.Flags().BoolVar(&listAll, "all", false, "list across all projects") + listCmd.Flags().BoolVar(&listJSON, "json", false, "output JSON for scripting") + listCmd.Flags().BoolVar(&listPathOnly, "path-only", false, "output only worktree paths") } diff --git a/cmd/worktree/open.go b/cmd/worktree/open.go index d56c548..94cf597 100644 --- a/cmd/worktree/open.go +++ b/cmd/worktree/open.go @@ -14,13 +14,13 @@ import ( ) var ( - openAllFlag bool - openProjectFilter string + openAllFlag bool + openProjectFilter string ) var openCmd = &cobra.Command{ - Use: "open", - Short: "Open existing worktree (interactive)", + Use: "open", + Short: "Open existing worktree (interactive)", Long: `Interactive two-step selection process using fzf: 1. Select project from available repositories 2. Select specific worktree within that project @@ -33,41 +33,41 @@ Opens selected worktree in configured editor and creates/switches to tmux sessio os.Exit(1) } - // Determine project scope - projects, err := git.ListAllProjects() - if err != nil { - fmt.Fprintf(os.Stderr, "Error listing projects: %v\n", err) - os.Exit(1) - } - - // If filtering by project name - if openProjectFilter != "" { - var filtered []git.Project - for _, p := range projects { - if p.Name == openProjectFilter { - filtered = append(filtered, p) - } - } - projects = filtered - } else if !openAllFlag && git.IsGitRepository() { - // If inside a repo and --all not set, limit to current repo - if repoName, err := git.GetRepositoryName(); err == nil { - var filtered []git.Project - for _, p := range projects { - if p.Name == repoName { - filtered = append(filtered, p) - break - } - } - projects = filtered - } - } - - if len(projects) == 0 { - fmt.Println("No projects with worktrees found") - fmt.Printf("Worktree base directory: %s\n", git.GetWorktreeBaseDir()) - os.Exit(1) - } + // Determine project scope + projects, err := git.ListAllProjects() + if err != nil { + fmt.Fprintf(os.Stderr, "Error listing projects: %v\n", err) + os.Exit(1) + } + + // If filtering by project name + if openProjectFilter != "" { + var filtered []git.Project + for _, p := range projects { + if p.Name == openProjectFilter { + filtered = append(filtered, p) + } + } + projects = filtered + } else if !openAllFlag && git.IsGitRepository() { + // If inside a repo and --all not set, limit to current repo + if repoName, err := git.GetRepositoryName(); err == nil { + var filtered []git.Project + for _, p := range projects { + if p.Name == repoName { + filtered = append(filtered, p) + break + } + } + projects = filtered + } + } + + if len(projects) == 0 { + fmt.Println("No projects with worktrees found") + fmt.Printf("Worktree base directory: %s\n", git.GetWorktreeBaseDir()) + os.Exit(1) + } var selectedProject git.Project var selectedWorktree git.Worktree @@ -111,8 +111,13 @@ func selectProjectInteractive(projects []git.Project) (git.Project, error) { var input bytes.Buffer for _, project := range projects { worktreeCount := len(project.Worktrees) - line := fmt.Sprintf("%-30s (%d worktree%s)", project.Name, worktreeCount, - func() string { if worktreeCount != 1 { return "s" }; return "" }()) + line := fmt.Sprintf("%-30s (%d worktree%s)", project.Name, worktreeCount, + func() string { + if worktreeCount != 1 { + return "s" + } + return "" + }()) input.WriteString(line + "\n") } @@ -195,57 +200,57 @@ func selectWorktreeFromProject(project git.Project) (git.Worktree, error) { } func openWorktree(projectName string, worktree git.Worktree) { - fmt.Printf("Opening worktree: %s/%s\n", projectName, worktree.Name) - - // Create or switch to tmux session - sessionName := fmt.Sprintf("%s-%s", projectName, worktree.Name) - sessionName = tmux.SanitizeSessionName(sessionName) - - if noTmux { - if !noEditor { - if err := editor.OpenInEditor(worktree.Path); err != nil { - fmt.Fprintf(os.Stderr, "Warning: failed to open editor: %v\n", err) - } else { - fmt.Printf("Opened in editor\n") - } - } - return - } - if tmux.IsInstalled() { - if tmux.SessionExists(sessionName) { - fmt.Printf("Switching to existing tmux session: %s\n", sessionName) - if !noEditor { - if err := tmux.SendCommandToSession(sessionName, editor.GetEditorCommand(worktree.Path)); err != nil { - fmt.Fprintf(os.Stderr, "Warning: failed to open editor in session: %v\n", err) - } - } - if err := tmux.SwitchToSession(sessionName); err != nil { - fmt.Fprintf(os.Stderr, "Warning: failed to switch tmux session: %v\n", err) - } - } else { - fmt.Printf("Creating new tmux session: %s\n", sessionName) - if noEditor { - if err := tmux.CreateSession(sessionName, worktree.Path); err != nil { - fmt.Fprintf(os.Stderr, "Warning: failed to create tmux session: %v\n", err) - } - } else { - if err := tmux.CreateSessionWithCommand(sessionName, worktree.Path, editor.GetEditorCommand(worktree.Path)); err != nil { - fmt.Fprintf(os.Stderr, "Warning: failed to create tmux session: %v\n", err) - } - } - } - } else { - if !noEditor { - if err := editor.OpenInEditor(worktree.Path); err != nil { - fmt.Fprintf(os.Stderr, "Warning: failed to open editor: %v\n", err) - } else { - fmt.Printf("Opened in editor\n") - } - } - } + fmt.Printf("Opening worktree: %s/%s\n", projectName, worktree.Name) + + // Create or switch to tmux session + sessionName := fmt.Sprintf("%s-%s", projectName, worktree.Name) + sessionName = tmux.SanitizeSessionName(sessionName) + + if noTmux { + if !noEditor { + if err := editor.OpenInEditor(worktree.Path); err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to open editor: %v\n", err) + } else { + fmt.Printf("Opened in editor\n") + } + } + return + } + if tmux.IsInstalled() { + if tmux.SessionExists(sessionName) { + fmt.Printf("Switching to existing tmux session: %s\n", sessionName) + if !noEditor { + if err := tmux.SendCommandToSession(sessionName, editor.GetEditorCommand(worktree.Path)); err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to open editor in session: %v\n", err) + } + } + if err := tmux.SwitchToSession(sessionName); err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to switch tmux session: %v\n", err) + } + } else { + fmt.Printf("Creating new tmux session: %s\n", sessionName) + if noEditor { + if err := tmux.CreateSession(sessionName, worktree.Path); err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to create tmux session: %v\n", err) + } + } else { + if err := tmux.CreateSessionWithCommand(sessionName, worktree.Path, editor.GetEditorCommand(worktree.Path)); err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to create tmux session: %v\n", err) + } + } + } + } else { + if !noEditor { + if err := editor.OpenInEditor(worktree.Path); err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to open editor: %v\n", err) + } else { + fmt.Printf("Opened in editor\n") + } + } + } } func init() { - openCmd.Flags().BoolVar(&openAllFlag, "all", false, "list across all projects even when in a repository") - openCmd.Flags().StringVar(&openProjectFilter, "project", "", "filter to a specific project name") + openCmd.Flags().BoolVar(&openAllFlag, "all", false, "list across all projects even when in a repository") + openCmd.Flags().StringVar(&openProjectFilter, "project", "", "filter to a specific project name") } diff --git a/cmd/worktree/root.go b/cmd/worktree/root.go index 2d4e9d0..329f840 100644 --- a/cmd/worktree/root.go +++ b/cmd/worktree/root.go @@ -1,19 +1,19 @@ package worktree import ( - "fmt" - "os" + "fmt" + "os" - "github.com/spf13/cobra" + "github.com/spf13/cobra" ) var noEditor bool var noTmux bool var rootCmd = &cobra.Command{ - Use: "wt", - Short: "A CLI tool for managing Git worktrees", - Long: `wt is a Go-based CLI tool for managing Git worktrees + Use: "wt", + Short: "A CLI tool for managing Git worktrees", + Long: `wt is a Go-based CLI tool for managing Git worktrees with enhanced features including automatic tmux session management and editor integration.`, } @@ -26,12 +26,12 @@ func Execute() { } func init() { - // Common behavior flags across subcommands that open things - rootCmd.PersistentFlags().BoolVar(&noEditor, "no-editor", false, "don't open the editor") - rootCmd.PersistentFlags().BoolVar(&noTmux, "no-tmux", false, "don't create/switch tmux") + // Common behavior flags across subcommands that open things + rootCmd.PersistentFlags().BoolVar(&noEditor, "no-editor", false, "don't open the editor") + rootCmd.PersistentFlags().BoolVar(&noTmux, "no-tmux", false, "don't create/switch tmux") - rootCmd.AddCommand(listCmd) - rootCmd.AddCommand(newCmd) - rootCmd.AddCommand(openCmd) - rootCmd.AddCommand(deleteCmd) + rootCmd.AddCommand(listCmd) + rootCmd.AddCommand(newCmd) + rootCmd.AddCommand(openCmd) + rootCmd.AddCommand(deleteCmd) } diff --git a/internal/editor/editor.go b/internal/editor/editor.go index 29dc75e..1b97af2 100644 --- a/internal/editor/editor.go +++ b/internal/editor/editor.go @@ -10,9 +10,9 @@ import ( func GetEditorCommand(path string) string { editorCmd := os.Getenv("EDITOR") if editorCmd == "" { - editorCmd = "vi" // Default fallback + editorCmd = "vi" // Default fallback } - + // Return the full command with path return fmt.Sprintf("%s %s", editorCmd, path) } @@ -22,25 +22,25 @@ func OpenInEditor(path string) error { if editorCmd == "" { return fmt.Errorf("EDITOR environment variable not set") } - + // Split the editor command to handle flags (e.g., "code -n") parts := strings.Fields(editorCmd) if len(parts) == 0 { return fmt.Errorf("invalid EDITOR command") } - + // Append the path to the command args := append(parts[1:], path) - + cmd := exec.Command(parts[0], args...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - + if err := cmd.Start(); err != nil { return fmt.Errorf("failed to open editor: %w", err) } - + // Don't wait for the editor to close return nil -} \ No newline at end of file +} diff --git a/internal/git/repository.go b/internal/git/repository.go index 5a104b0..58b6e17 100644 --- a/internal/git/repository.go +++ b/internal/git/repository.go @@ -23,9 +23,9 @@ func GetRepositoryName() (string, error) { if err != nil { return "", fmt.Errorf("not in a git repository") } - + gitCommonDir := strings.TrimSpace(string(output)) - + // Convert to absolute path if relative if !filepath.IsAbs(gitCommonDir) { // Get the current working directory to resolve relative path @@ -37,14 +37,14 @@ func GetRepositoryName() (string, error) { topLevelPath := strings.TrimSpace(string(topLevel)) gitCommonDir = filepath.Join(topLevelPath, gitCommonDir) } - + // If the common dir ends with .git, we're in a worktree or the main repo if strings.HasSuffix(gitCommonDir, ".git") { // Get the parent directory of .git to find the main repository path mainRepoPath := filepath.Dir(gitCommonDir) return filepath.Base(mainRepoPath), nil } - + // Fallback: shouldn't normally reach here return "", fmt.Errorf("unable to determine repository name") } @@ -53,10 +53,10 @@ func GetWorktreeBaseDir() string { if baseDir := os.Getenv("WORKTREE_BASE_DIR"); baseDir != "" { return baseDir } - + return config.GetWorktreesLocation() } func GetWorktreeDir(repoName string) string { return filepath.Join(GetWorktreeBaseDir(), repoName) -} \ No newline at end of file +} diff --git a/main.go b/main.go index 6757f7a..8748473 100644 --- a/main.go +++ b/main.go @@ -6,4 +6,4 @@ import ( func main() { worktree.Execute() -} \ No newline at end of file +}