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
7 changes: 4 additions & 3 deletions cmd/branch/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import (
)

var BranchCmd = &cobra.Command{
Use: "branch",
Short: "Manage branches in main repositories",
Long: `Manage branches in main repositories, including listing and cleaning up orphaned branches.`,
Use: "branch",
Aliases: []string{"b"},
Short: "Manage branches in main repositories",
Long: `Manage branches in main repositories, including listing and cleaning up orphaned branches.`,
}

func init() {
Expand Down
39 changes: 22 additions & 17 deletions cmd/branch/cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import (
)

var cleanupCmd = &cobra.Command{
Use: "cleanup",
Short: "Delete orphaned branches",
Long: `Delete branches that don't have an associated workspace (orphaned branches).`,
Use: "cleanup",
Short: "Delete orphaned branches",
Long: `Delete branches that don't have an associated workspace (orphaned branches).`,
Example: ` workspace branch cleanup`,
Run: func(_ *cobra.Command, _ []string) {
cleanupBranches()
},
Expand All @@ -28,25 +29,29 @@ func cleanupBranches() {

plan, err := svc.PlanCleanup()
if err != nil {
commands.PrintError(fmt.Sprintf("Failed to plan cleanup: %v", err))
commands.PrintErrorf("Failed to plan cleanup: %v", err)
return
}

if len(plan.OrphanedBranches) == 0 {
if plan.SkippedIgnored > 0 {
commands.PrintSuccess(fmt.Sprintf("No orphaned branches found! (%d ignored branches skipped)", plan.SkippedIgnored))
commands.PrintSuccessf("No orphaned branches found! (%d ignored branches skipped)", plan.SkippedIgnored)
} else {
commands.PrintSuccess("No orphaned branches found!")
}
return
}

commands.PrintWarning(fmt.Sprintf("Found %d orphaned branch(es):", len(plan.OrphanedBranches)))
commands.PrintWarningf("Found %d orphaned branch(es):", len(plan.OrphanedBranches))
if plan.SkippedIgnored > 0 {
commands.PrintInfo(fmt.Sprintf("(%d ignored branches skipped)", plan.SkippedIgnored))
commands.PrintInfof("(%d ignored branches skipped)", plan.SkippedIgnored)
}
for _, ob := range plan.OrphanedBranches {
fmt.Printf(" - %s: %s\n", ob.RepoName, ob.BranchName)
if ob.HasUnpushed {
fmt.Printf(" - %s: %s %s\n", ob.RepoName, ob.BranchName, commands.ColorWarning(fmt.Sprintf("(%d unpushed)", ob.UnpushedCount)))
} else {
fmt.Printf(" - %s: %s\n", ob.RepoName, ob.BranchName)
}
}

if !commands.PromptYesNo("\nDo you want to delete these branches? (y/n): ") {
Expand All @@ -58,21 +63,21 @@ func cleanupBranches() {

result, err := svc.ExecuteCleanup(plan, skipBranches)
if err != nil {
commands.PrintError(fmt.Sprintf("Failed to execute cleanup: %v", err))
commands.PrintErrorf("Failed to execute cleanup: %v", err)
return
}

displayCleanupResult(result)
}

func promptForUnpushedBranches(plan *branch.CleanupPlan) []string {
func promptForUnpushedBranches(plan branch.CleanupPlan) []string {
var skipBranches []string

for _, ob := range plan.OrphanedBranches {
if ob.HasUnpushed {
commands.PrintWarning(fmt.Sprintf("Branch '%s' in %s has %d unpushed commit(s)", ob.BranchName, ob.RepoName, ob.UnpushedCount))
commands.PrintWarningf("Branch '%s' in %s has %d unpushed commit(s)", ob.BranchName, ob.RepoName, ob.UnpushedCount)
if !commands.PromptYesNo("Delete anyway? (y/n): ") {
commands.PrintInfo(fmt.Sprintf("Skipping branch '%s'", ob.BranchName))
commands.PrintInfof("Skipping branch '%s'", ob.BranchName)
skipBranches = append(skipBranches, fmt.Sprintf("%s:%s", ob.RepoName, ob.BranchName))
}
}
Expand All @@ -81,21 +86,21 @@ func promptForUnpushedBranches(plan *branch.CleanupPlan) []string {
return skipBranches
}

func displayCleanupResult(result *branch.CleanupResult) {
func displayCleanupResult(result branch.CleanupResult) {
fmt.Printf("\n")

for _, d := range result.Deleted {
commands.PrintSuccess(fmt.Sprintf("Deleted %s in %s", d.BranchName, d.RepoName))
commands.PrintSuccessf("Deleted %s in %s", d.BranchName, d.RepoName)
}

for _, f := range result.Failed {
commands.PrintError(fmt.Sprintf("Failed to delete %s in %s: %v", f.BranchName, f.RepoName, f.Error))
commands.PrintErrorf("Failed to delete %s in %s: %v", f.BranchName, f.RepoName, f.Error)
}

if len(result.Deleted) > 0 {
commands.PrintSuccess(fmt.Sprintf("Deleted %d branch(es)", len(result.Deleted)))
commands.PrintSuccessf("Deleted %d branch(es)", len(result.Deleted))
}
if len(result.Failed) > 0 {
commands.PrintWarning(fmt.Sprintf("Failed to delete %d branch(es)", len(result.Failed)))
commands.PrintWarningf("Failed to delete %d branch(es)", len(result.Failed))
}
}
10 changes: 5 additions & 5 deletions cmd/branch/ignore.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ var ignoreAddCmd = &cobra.Command{
Run: func(_ *cobra.Command, args []string) {
pattern := args[0]
if err := cmd.ConfigManager.AddIgnoredBranch(pattern); err != nil {
commands.PrintError(fmt.Sprintf("Failed to add pattern: %v", err))
commands.PrintErrorf("Failed to add pattern: %v", err)
return
}
commands.PrintSuccess(fmt.Sprintf("Added pattern '%s' to ignore list", pattern))
commands.PrintSuccessf("Added pattern '%s' to ignore list", pattern)
},
}

Expand All @@ -36,10 +36,10 @@ var ignoreRemoveCmd = &cobra.Command{
Run: func(_ *cobra.Command, args []string) {
pattern := args[0]
if err := cmd.ConfigManager.RemoveIgnoredBranch(pattern); err != nil {
commands.PrintError(fmt.Sprintf("Failed to remove pattern: %v", err))
commands.PrintErrorf("Failed to remove pattern: %v", err)
return
}
commands.PrintSuccess(fmt.Sprintf("Removed pattern '%s' from ignore list", pattern))
commands.PrintSuccessf("Removed pattern '%s' from ignore list", pattern)
},
}

Expand All @@ -65,7 +65,7 @@ var ignoreClearCmd = &cobra.Command{
Short: "Clear all ignored branch patterns",
Run: func(_ *cobra.Command, _ []string) {
if err := cmd.ConfigManager.ClearIgnoredBranches(); err != nil {
commands.PrintError(fmt.Sprintf("Failed to clear ignored patterns: %v", err))
commands.PrintErrorf("Failed to clear ignored patterns: %v", err)
return
}
commands.PrintSuccess("Cleared all ignored branch patterns")
Expand Down
13 changes: 8 additions & 5 deletions cmd/branch/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ import (
)

var listCmd = &cobra.Command{
Use: "list",
Short: "List all branches and their associated workspaces",
Long: `List all branches in main repositories, showing which workspaces they belong to and if they have unpushed commits.`,
Use: "list",
Aliases: []string{"ls"},
Short: "List all branches and their associated workspaces",
Long: `List all branches in main repositories, showing which workspaces they belong to and if they have unpushed commits.`,
Example: ` workspace branch list
workspace b ls`,
Run: func(_ *cobra.Command, _ []string) {
listBranches()
},
Expand All @@ -29,7 +32,7 @@ func listBranches() {

output, err := svc.List()
if err != nil {
commands.PrintError(fmt.Sprintf("Failed to list branches: %v", err))
commands.PrintErrorf("Failed to list branches: %v", err)
return
}

Expand All @@ -41,7 +44,7 @@ func listBranches() {
displayBranchList(output)
}

func displayBranchList(output *branch.ListOutput) {
func displayBranchList(output branch.ListOutput) {
for _, repo := range output.Repositories {
fmt.Printf("\n%s\n", commands.ColorInfo(fmt.Sprintf("=== Repository: %s ===", repo.RepoName)))

Expand Down
10 changes: 5 additions & 5 deletions cmd/config/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ func initShellIntegration() {
functionContent = shell.GenerateFishFunction()

if err := os.MkdirAll(fishConfigDir, 0o755); err != nil {
commands.PrintError(fmt.Sprintf("Failed to create fish config directory: %v", err))
commands.PrintErrorf("Failed to create fish config directory: %v", err)
return
}
default:
commands.PrintError(fmt.Sprintf("Unsupported shell: %s", shellName))
commands.PrintErrorf("Unsupported shell: %s", shellName)
fmt.Println("Supported shells: bash, zsh, fish")
return
}
Expand All @@ -61,16 +61,16 @@ func initShellIntegration() {

if shellName == "fish" {
if err := os.WriteFile(rcFile, []byte(functionContent), 0o644); err != nil {
commands.PrintError(fmt.Sprintf("Failed to write fish function: %v", err))
commands.PrintErrorf("Failed to write fish function: %v", err)
return
}
commands.PrintSuccess(fmt.Sprintf("Fish function written to: %s", rcFile))
commands.PrintSuccessf("Fish function written to: %s", rcFile)
commands.PrintInfo("Restart your shell or run 'source ~/.config/fish/config.fish' to use the 'w' command")
} else {
fmt.Printf("Add this function to your %s:\n\n", commands.InfoStyle.Render(rcFile))
fmt.Println(functionContent)
fmt.Println()
commands.PrintInfo("After adding, restart your shell or run 'source " + rcFile + "' to use the 'w' command")
commands.PrintInfof("After adding, restart your shell or run 'source %s' to use the 'w' command", rcFile)
}

fmt.Println()
Expand Down
19 changes: 11 additions & 8 deletions cmd/config/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ var setCmd = &cobra.Command{
Use: "set <key> <value>",
Short: "Set configuration value",
Long: `Set a configuration value. Available keys: workspaces-dir, repos-dir, claude-dir`,
Args: cobra.ExactArgs(2),
Example: ` workspace config set repos-dir ~/Projects/repos
workspace config set workspaces-dir ~/dev/workspaces
workspace config set claude-dir ~/shared/.claude`,
Args: cobra.ExactArgs(2),
Run: func(_ *cobra.Command, args []string) {
setConfigValue(args[0], args[1])
},
Expand All @@ -28,33 +31,33 @@ func setConfigValue(key, value string) {
switch key {
case "workspaces-dir":
if err := cmd.ConfigManager.SetWorkspacesDir(value); err != nil {
commands.PrintError(fmt.Sprintf("Failed to set workspaces directory: %v", err))
commands.PrintErrorf("Failed to set workspaces directory: %v", err)
return
}
cfg := cmd.ConfigManager.GetConfig()
cmd.WorkspaceManager = workspace.NewManager(cfg.WorkspacesDir, cfg.ReposDir, cfg.ClaudeDir)
commands.PrintSuccess(fmt.Sprintf("Workspaces directory set to: %s", cfg.WorkspacesDir))
commands.PrintSuccessf("Workspaces directory set to: %s", cfg.WorkspacesDir)

case "repos-dir":
if err := cmd.ConfigManager.SetReposDir(value); err != nil {
commands.PrintError(fmt.Sprintf("Failed to set repos directory: %v", err))
commands.PrintErrorf("Failed to set repos directory: %v", err)
return
}
cfg := cmd.ConfigManager.GetConfig()
cmd.WorkspaceManager = workspace.NewManager(cfg.WorkspacesDir, cfg.ReposDir, cfg.ClaudeDir)
commands.PrintSuccess(fmt.Sprintf("Repos directory set to: %s", cfg.ReposDir))
commands.PrintSuccessf("Repos directory set to: %s", cfg.ReposDir)

case "claude-dir":
if err := cmd.ConfigManager.SetClaudeDir(value); err != nil {
commands.PrintError(fmt.Sprintf("Failed to set claude directory: %v", err))
commands.PrintErrorf("Failed to set claude directory: %v", err)
return
}
cfg := cmd.ConfigManager.GetConfig()
cmd.WorkspaceManager = workspace.NewManager(cfg.WorkspacesDir, cfg.ReposDir, cfg.ClaudeDir)
commands.PrintSuccess(fmt.Sprintf("Claude directory set to: %s", cfg.ClaudeDir))
commands.PrintSuccessf("Claude directory set to: %s", cfg.ClaudeDir)

default:
commands.PrintError(fmt.Sprintf("Unknown configuration key: %s", key))
commands.PrintErrorf("Unknown configuration key: %s", key)
fmt.Println("Available keys: workspaces-dir, repos-dir, claude-dir")
}
}
2 changes: 1 addition & 1 deletion cmd/config/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func runInteractiveSetup() {
cfg.ClaudeDir,
)
if err != nil {
commands.PrintError(fmt.Sprintf("Setup wizard failed: %v", err))
commands.PrintErrorf("Setup wizard failed: %v", err)
os.Exit(1)
}

Expand Down
51 changes: 47 additions & 4 deletions cmd/config/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"fmt"
"os"

"github.com/spf13/cobra"

Expand All @@ -12,7 +13,7 @@ import (
var showCmd = &cobra.Command{
Use: "show",
Short: "Show current configuration",
Long: `Display the current workspace configuration.`,
Long: `Display the current workspace configuration with health check information.`,
Run: func(_ *cobra.Command, _ []string) {
showConfig()
},
Expand All @@ -32,8 +33,50 @@ func showConfig() {
fmt.Printf("Config file: %s\n", commands.InfoStyle.Render(cmd.ConfigManager.GetConfigPath()))
fmt.Println()

fmt.Printf("Workspaces directory: %s\n", commands.SuccessStyle.Render(config.WorkspacesDir))
fmt.Printf("Repos directory: %s\n", commands.SuccessStyle.Render(config.ReposDir))
fmt.Printf("Claude directory: %s\n", commands.SuccessStyle.Render(config.ClaudeDir))
workspacesStatus := checkDirStatus(config.WorkspacesDir, "workspaces")
reposStatus := checkDirStatus(config.ReposDir, "repos")
claudeStatus := checkDirStatus(config.ClaudeDir, "")

fmt.Printf("Workspaces directory: %s %s\n", commands.SuccessStyle.Render(config.WorkspacesDir), workspacesStatus)
fmt.Printf("Repos directory: %s %s\n", commands.SuccessStyle.Render(config.ReposDir), reposStatus)
fmt.Printf("Claude directory: %s %s\n", commands.SuccessStyle.Render(config.ClaudeDir), claudeStatus)
fmt.Println()
}

func checkDirStatus(path, dirType string) string {
info, err := os.Stat(path)
if os.IsNotExist(err) {
return commands.ColorWarning("(missing)")
}
if err != nil {
return commands.ColorWarning("(error)")
}
if !info.IsDir() {
return commands.ColorWarning("(not a directory)")
}

entries, err := os.ReadDir(path)
if err != nil {
return commands.ColorSuccess("(exists)")
}

count := 0
for _, e := range entries {
if e.IsDir() && e.Name() != ".claude" && !isHiddenDir(e.Name()) {
count++
}
}

switch dirType {
case "workspaces":
return commands.ColorSuccess(fmt.Sprintf("(exists, %d workspace(s))", count))
case "repos":
return commands.ColorSuccess(fmt.Sprintf("(exists, %d repo(s))", count))
default:
return commands.ColorSuccess("(exists)")
}
}

func isHiddenDir(name string) bool {
return name != "" && name[0] == '.'
}
Loading