diff --git a/README.md b/README.md index 58e6f7b..39cf083 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,53 @@ git clone https://aur.archlinux.org/git-switcher.git makepkg -is ``` +## Commands + +Git Switcher offers several commands to help you manage your Git profiles: + +### `list` + +Lists all your saved git profiles. The currently active profile will be highlighted with an asterisk (*) and marked as `(current)`. + +**Usage:** + +```sh +git-switcher list +``` + +**Example Output:** + +``` +Available Git profiles: + work-profile +* personal-profile (current) + freelance-project +``` + +### `switch` + +Allows you to interactively select and switch to a different Git profile from your saved list. This is also the default behavior when running `git-switcher` without any subcommand. + +*(The GIF below demonstrates this functionality)* + +### `create` + +Guides you through the process of creating and saving a new Git profile. + +*(The GIF below demonstrates this functionality)* + +### `delete` + +Allows you to select and delete one of your saved Git profiles. + +*(The GIF below demonstrates this functionality)* + +### `rename` + +Allows you to rename an existing saved Git profile. + +*(The GIF below demonstrates this functionality)* + ## Switch Profile ![Switcher](https://user-images.githubusercontent.com/53150440/135753964-94d83bf5-597c-4983-b0cf-5da6f12e6c7c.gif) diff --git a/cmd/create.go b/cmd/create.go index 6d02abd..87360ec 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -75,6 +75,5 @@ A new configuration file will be created in the ~/.config/gitconfigs directory.` func init() { // This function is called when the package is initialized. // We are adding the createCmd to the rootCmd here. - // This will be done for all command files. - // rootCmd.AddCommand(createCmd) // Will be added in root.go's init + // rootCmd.AddCommand(createCmd) // Commands are added in root.go's init } diff --git a/cmd/delete.go b/cmd/delete.go index 7238a5a..49725af 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -142,6 +142,5 @@ If the deleted profile is the currently active one, ~/.gitconfig will also be re } func init() { - // Will be added in root.go - // rootCmd.AddCommand(deleteCmd) + // rootCmd.AddCommand(deleteCmd) // Commands are added in root.go's init } diff --git a/cmd/edit.go b/cmd/edit.go index fa5a806..dc45356 100644 --- a/cmd/edit.go +++ b/cmd/edit.go @@ -77,6 +77,5 @@ This allows you to directly modify the active git configuration.`, } func init() { - // Will be added in root.go - // rootCmd.AddCommand(editCmd) + // rootCmd.AddCommand(editCmd) // Commands are added in root.go's init } diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..d442e86 --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,97 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/theykk/git-switcher/utils" +) + +var ( + getHomeDirFnc = homedir.Dir // Function variable for easy mocking in tests + configSubPath = filepath.Join(".config", "gitconfigs") + gitConfigName = ".gitconfig" +) + +var listCmd = &cobra.Command{ + Use: "list", + Short: "Lists all available git profiles", + Long: `Lists all available git profiles and indicates the currently active one.`, + Run: func(cmd *cobra.Command, args []string) { + home, err := getHomeDirFnc() + if err != nil { + log.Fatalf("Error getting home directory: %v", err) // Use log.Fatalf for consistency + } + + configDir := filepath.Join(home, configSubPath) + if _, err := os.Stat(configDir); os.IsNotExist(err) { + // Use cmd.OutOrStdout() for output redirection in tests + fmt.Fprintln(cmd.OutOrStdout(), "Configuration directory "+filepath.Join("~", configSubPath)+" not found. Please create it first.") + return + } + + files, err := os.ReadDir(configDir) + if err != nil { + log.Fatalf("Error reading profiles directory: %v", err) // Use log.Fatalf + } + + gitconfigPath := filepath.Join(home, gitConfigName) + activeHash := "" + if _, err := os.Stat(gitconfigPath); err == nil { // Check if .gitconfig exists + activeHash = utils.Hash(gitconfigPath) + } else if !os.IsNotExist(err) { + // For errors other than "not exist", log them. + log.Printf("Warning: Error checking current .gitconfig: %v", err) // Keep log.Printf for warnings + } + + fmt.Fprintln(cmd.OutOrStdout(), "Available Git profiles:") // This title is part of the output. + foundProfiles := false + for _, fileEntry := range files { // Renamed 'file' to 'fileEntry' for clarity + if !fileEntry.IsDir() { + profileName := fileEntry.Name() + profilePath := filepath.Join(configDir, profileName) + + // Ensure the profile file itself exists before hashing + if _, err := os.Stat(profilePath); os.IsNotExist(err) { + log.Printf("Warning: profile file %s does not exist. Skipping.", profilePath) // Keep log.Printf for warnings + continue + } + + profileHash := utils.Hash(profilePath) + foundProfiles = true + + if activeHash != "" && profileHash == activeHash { + // color.Green will be used by Cobra if it detects a TTY. + // For tests, stdout is usually not a TTY, so color might be disabled. + // The test should ideally check for the presence of "*" and "(current)". + // Using fmt.Sprintf for consistent output formatting in tests. + // Actual color output can be manually verified. + fmt.Fprintf(cmd.OutOrStdout(), "* %s (current)\n", profileName) + } else { + fmt.Fprintf(cmd.OutOrStdout(), " %s\n", profileName) + } + } + } + + if !foundProfiles { + // Standardize this message slightly for easier testing. + fmt.Fprintln(cmd.OutOrStdout(), " No profiles found in "+filepath.Join("~", configSubPath)+".") + } + }, +} + +// GetListCmdForTest exposes listCmd for testing purposes if needed by other packages, +// though for same-package tests, it's directly accessible. +// func GetListCmdForTest() *cobra.Command { +// return listCmd +// } + +func init() { + // This ensures listCmd is added to rootCmd when the package is initialized. + // No changes needed here normally for testing listCmd itself via rootCmd.Execute(). + rootCmd.AddCommand(listCmd) +} diff --git a/cmd/list_test.go b/cmd/list_test.go new file mode 100644 index 0000000..eeeea14 --- /dev/null +++ b/cmd/list_test.go @@ -0,0 +1,252 @@ +package cmd + +import ( + "bytes" + // "fmt" // Not directly used in test functions + // "io" // Not directly used in test functions + "os" + "path/filepath" + "strings" + "testing" + // "github.com/theykk/git-switcher/cmd" // Removed to break import cycle + // "github.com/theykk/git-switcher/utils" // utils.Hash is used by the main code, not directly in test funcs +) + +// setupTestEnvironment creates a temporary home directory with a .config/gitconfigs subdir. +// It returns the path to the temp home, config dir, and gitconfig path, plus a cleanup function. +func setupTestEnvironment(t *testing.T) (string, string, string, func()) { + t.Helper() + + tempHome, err := os.MkdirTemp("", "test-home-") + if err != nil { + t.Fatalf("Failed to create temp home dir: %v", err) + } + + configDir := filepath.Join(tempHome, ".config", "gitconfigs") + err = os.MkdirAll(configDir, 0755) + if err != nil { + os.RemoveAll(tempHome) + t.Fatalf("Failed to create temp config dir: %v", err) + } + + gitConfigPath := filepath.Join(tempHome, ".gitconfig") + + // originalHome := os.Getenv("HOME") // No longer using os.Setenv("HOME") for this + // os.Setenv("HOME", tempHome) + + originalGetHomeDirFnc := getHomeDirFnc // Store original homedir func from cmd/list.go + getHomeDirFnc = func() (string, error) { // Mock it + return tempHome, nil + } + + cleanup := func() { + // os.Setenv("HOME", originalHome) // Restore original HOME if it was set + getHomeDirFnc = originalGetHomeDirFnc // Restore original homedir func + os.RemoveAll(tempHome) + } + + // Return tempHome as well, though it's now primarily for creating files within it. + return tempHome, configDir, gitConfigPath, cleanup +} + +func createDummyProfile(t *testing.T, dir, name, content string) string { + t.Helper() + profilePath := filepath.Join(dir, name) + err := os.WriteFile(profilePath, []byte(content), 0644) + if err != nil { + t.Fatalf("Failed to write dummy profile %s: %v", name, err) + } + return profilePath +} + +func TestListCmdOutput(t *testing.T) { // Renamed TestListCmd to TestListCmdOutput for clarity + // listCmd is an unexported variable in the same 'cmd' package (from list.go) + // rootCmd is also unexported (from root.go) + // Ensure listCmd is added to rootCmd for proper execution context if needed, + // though Execute() on listCmd itself should work for simple cases. + // The init() function in root.go should handle rootCmd.AddCommand(listCmd). + // We just need to ensure that init() has run, which it should have by the time tests are run. + + t.Run("Basic Listing - All profiles shown and none active", func(t *testing.T) { + _, configDir, gitConfigPath, cleanup := setupTestEnvironment(t) + defer cleanup() + + createDummyProfile(t, configDir, "profileA", "[user]\n name = UserA\n email = usera@example.com") + createDummyProfile(t, configDir, "profileB", "[user]\n name = UserB\n email = userb@example.com") + createDummyProfile(t, configDir, "profileC", "[user]\n name = UserC\n email = userc@example.com") + + // Ensure .gitconfig does not exist or is empty for this test case, so no profile is "active" + // Or, make its content not match any profile + _ = os.WriteFile(gitConfigPath, []byte("[user]\n name = NonExistent"), 0644) + + + output := new(bytes.Buffer) + // listCmd.SetOut(output) // Output should be set on rootCmd + // listCmd.SetErr(output) // Error output should be set on rootCmd + rootCmd.SetOut(output) + rootCmd.SetErr(output) + + rootCmd.SetArgs([]string{"list"}) + err := rootCmd.Execute() + if err != nil { + // If HOME override didn't work, homedir.Dir() might return actual home, + // leading to errors or unexpected behavior. + t.Fatalf("listCmd.Execute() failed: %v. Output: %s", err, output.String()) + } + + result := output.String() + t.Logf("Captured output for 'Basic Listing':\nSTART_OUTPUT\n%s\nEND_OUTPUT", result) // Debugging line + + // Exact string matching including leading spaces and newline + if !strings.Contains(result, " profileA\n") { + t.Errorf("Output does not contain ' profileA\\n'. Got:\n%s", result) + } + if !strings.Contains(result, " profileB\n") { + t.Errorf("Output does not contain ' profileB\\n'. Got:\n%s", result) + } + if !strings.Contains(result, " profileC\n") { + t.Errorf("Output does not contain ' profileC\\n'. Got:\n%s", result) + } + if strings.Contains(result, "*") { // Still check for asterisk for non-active + t.Errorf("Output contains '*' indicating active profile, but none should be active. Got:\n%s", result) + } + }) + + t.Run("Current Profile Indication", func(t *testing.T) { + _, configDir, gitConfigPath, cleanup := setupTestEnvironment(t) // tempHomeUsed changed to _ + defer cleanup() + + createDummyProfile(t, configDir, "profileX", "[user]\n name = UserX\n email = userx@example.com") + activeProfileContent := "[user]\n name = UserY\n email = usery@example.com" + createDummyProfile(t, configDir, "profileY", activeProfileContent) + createDummyProfile(t, configDir, "profileZ", "[user]\n name = UserZ\n email = userz@example.com") + + // Set .gitconfig to match profileY + err := os.WriteFile(gitConfigPath, []byte(activeProfileContent), 0644) + if err != nil { + t.Fatalf("Failed to write .gitconfig: %v", err) + } + + + output := new(bytes.Buffer) + rootCmd.SetOut(output) + rootCmd.SetErr(output) + + rootCmd.SetArgs([]string{"list"}) + executeErr := rootCmd.Execute() + if executeErr != nil { + t.Fatalf("listCmd.Execute() failed: %v. Output: %s", executeErr, output.String()) + } + result := output.String() + + expectedActive := "* profileY (current)\n" // Added newline + if !strings.Contains(result, expectedActive) { + t.Errorf("Output does not correctly indicate active profile 'profileY'. Expected to contain '%s'. Got:\n%s", expectedActive, result) + } + // Check for other profiles not being active + if strings.Contains(result, "* profileX") { + t.Errorf("Output incorrectly marks profileX as active. Got:\n%s", result) + } + if strings.Contains(result, "* profileZ") { + t.Errorf("Output incorrectly marks profileZ as active. Got:\n%s", result) + } + // Count check is still good + if strings.Count(result, "*") > 1 { // Ensure only one asterisk + t.Errorf("Output indicates more than one active profile. Got:\n%s", result) + } + if strings.Contains(result, "* profileX") || strings.Contains(result, "* profileZ") { + t.Errorf("Output incorrectly marks profileX or profileZ as active. Got:\n%s", result) + } + }) + + t.Run("No Profiles", func(t *testing.T) { + // _, _, _, cleanup := setupTestEnvironment(t) // First call removed + // defer cleanup() // First defer removed + + _, _, _, cleanup := setupTestEnvironment(t) // configDir is created but left empty. This is the only call needed. + defer cleanup() + + output := new(bytes.Buffer) + rootCmd.SetOut(output) + rootCmd.SetErr(output) + + rootCmd.SetArgs([]string{"list"}) + err := rootCmd.Execute() + if err != nil { + t.Fatalf("listCmd.Execute() failed: %v. Output: %s", err, output.String()) + } + result := output.String() + + // Message from list.go: fmt.Println(" No profiles found in " + filepath.Join("~", configSubPath) + ".") + expectedMsg := " No profiles found in " + filepath.Join("~", ".config", "gitconfigs") + "." + if !strings.Contains(result, expectedMsg) { + t.Errorf("Output does not indicate 'No profiles found'. Expected to contain\n'%s'. Got:\n'%s'", expectedMsg, result) + } + }) + + t.Run(".gitconfig Not Matching Any Profile", func(t *testing.T) { + _, configDir, gitConfigPath, cleanup := setupTestEnvironment(t) + defer cleanup() + + createDummyProfile(t, configDir, "profileOne", "[user]\n name = UserOne\n email = userone@example.com") + createDummyProfile(t, configDir, "profileTwo", "[user]\n name = UserTwo\n email = usertwo@example.com") + + err := os.WriteFile(gitConfigPath, []byte("[user]\n name = UnknownUser\n email = unknown@example.com"), 0644) + if err != nil { + t.Fatalf("Failed to write .gitconfig: %v", err) + } + + output := new(bytes.Buffer) + rootCmd.SetOut(output) + rootCmd.SetErr(output) + + rootCmd.SetArgs([]string{"list"}) + executeErr := rootCmd.Execute() + if executeErr != nil { + t.Fatalf("listCmd.Execute() failed: %v. Output: %s", executeErr, output.String()) + } + result := output.String() + + if strings.Contains(result, "*") { // No active profile expected + t.Errorf("Output indicates an active profile with '*', but none should be. Got:\n%s", result) + } + if !strings.Contains(result, " profileOne\n") { + t.Errorf("Output does not list ' profileOne\\n'. Got:\n%s", result) + } + if !strings.Contains(result, " profileTwo\n") { + t.Errorf("Output does not list ' profileTwo\\n'. Got:\n%s", result) + } + }) + + t.Run("Configuration directory does not exist", func(t *testing.T) { + tempHome, _, _, cleanup := setupTestEnvironment(t) + defer cleanup() + + // Remove the .config/gitconfigs directory that setupTestEnvironment creates + err := os.RemoveAll(filepath.Join(tempHome, ".config")) + if err != nil { + t.Fatalf("Failed to remove .config directory for test: %v", err) + } + + output := new(bytes.Buffer) + rootCmd.SetOut(output) + rootCmd.SetErr(output) + + rootCmd.SetArgs([]string{"list"}) + executeErr := rootCmd.Execute() // Should not panic, should print message + if executeErr != nil { + // Depending on cobra's behavior for RunE errors, Execute might return an error. + // For simple fmt.Println in Run, it might not. + // If the command calls log.Fatal, the test will exit here. + // The current listCmd.Run prints and returns, so Execute() shouldn't error out here. + t.Logf("listCmd.Execute() returned error (may be expected for some error messages): %v", executeErr) + } + result := output.String() + + // Message from list.go: fmt.Println("Configuration directory " + filepath.Join("~", configSubPath) + " not found. Please create it first.") + expectedMsg := "Configuration directory " + filepath.Join("~", ".config", "gitconfigs") + " not found. Please create it first." + if !strings.Contains(result, expectedMsg) { + t.Errorf("Output does not indicate config directory not found. Expected\n'%s'. Got:\n'%s'", expectedMsg, result) + } + }) +} diff --git a/cmd/rename.go b/cmd/rename.go index be110b8..7111a8f 100644 --- a/cmd/rename.go +++ b/cmd/rename.go @@ -25,7 +25,6 @@ import ( "github.com/manifoldco/promptui" "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" - // main_pkg "github.com/theykk/git-switcher" // Not needed if not using Hash/Write ) // renameCmd represents the rename command @@ -135,6 +134,5 @@ If the renamed profile is the currently active one, the ~/.gitconfig symlink wil } func init() { - // Will be added in root.go - // rootCmd.AddCommand(renameCmd) + // rootCmd.AddCommand(renameCmd) // Commands are added in root.go's init } diff --git a/cmd/root.go b/cmd/root.go index 97ac8c2..c24c32d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,21 +1,10 @@ -/* -Copyright © 2025 NAME HERE - -*/ package cmd import ( "fmt" - "io/fs" - "log" "os" - "path/filepath" - "github.com/fatih/color" - "github.com/manifoldco/promptui" - "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" - "github.com/theykk/git-switcher/utils" ) // rootCmd represents the base command when called without any subcommands @@ -28,159 +17,9 @@ and switch between them with a simple interactive prompt or direct commands. This is useful when you work on different projects that require different user names or email addresses for git commits.`, Run: func(cmd *cobra.Command, args []string) { - // This is the default action when no subcommand is provided. - // This logic is taken from the original main() function, after os.Args parsing. - - confPath, err := homedir.Expand("~/.config/gitconfigs") - if err != nil { - log.Panic(err) - } - - log.SetFlags(log.Lshortfile) - configs := make(map[string]string) - - if _, err := os.Stat(confPath); os.IsNotExist(err) { - err = os.MkdirAll(confPath, os.ModeDir|0o700) - if err != nil { - log.Println(err) // Log and continue, maybe it's just a read-only system - } - } - - err = filepath.WalkDir(confPath+"/", func(path string, d fs.DirEntry, e error) error { - if d.IsDir() { - return nil - } - if e != nil { // Check for errors from WalkDir itself - log.Printf("Warning: error accessing path %s: %v\n", path, e) - return e // or return nil to attempt to continue - } - configs[utils.Hash(path)] = filepath.Base(path) - return nil - }) - if err != nil { - log.Printf("Error walking directory %s: %v\n", confPath, err) - // Decide if this is fatal or if the program can continue (e.g. if it's just for listing) - } - - gitConfig, err := homedir.Expand("~/.gitconfig") - if err != nil { - log.Panic(err) - } - - if _, err := os.Stat(gitConfig); os.IsNotExist(err) { - utils.Write(gitConfig, []byte("[user]\n\tname = username")) - } - gitConfigHash := utils.Hash(gitConfig) - - // Ensure old-configs link is handled (idempotently) - // This part was originally before os.Args check. - // It makes sense to ensure the current .gitconfig is backed up if it's not a known profile. - if _, ok := configs[gitConfigHash]; !ok { - oldConfigsPath := filepath.Join(confPath, "old-configs") - // Check if .gitconfig is not already a symlink before attempting to link it - // This avoids linking a symlink itself if .gitconfig is already managed. - lstatInfo, lstatErr := os.Lstat(gitConfig) - isSymlink := false - if lstatErr == nil && (lstatInfo.Mode()&os.ModeSymlink != 0) { - isSymlink = true - } - - if !isSymlink { // Only try to link if .gitconfig is a regular file - if _, statErr := os.Stat(oldConfigsPath); os.IsNotExist(statErr) { - errLink := os.Link(gitConfig, oldConfigsPath) - if errLink != nil { - log.Printf("Warning: Failed to link current .gitconfig to %s: %v\n", oldConfigsPath, errLink) - } else { - log.Printf("Info: Current .gitconfig backed up to %s\n", oldConfigsPath) - // Add the newly backed-up config to the current session's list - configs[utils.Hash(oldConfigsPath)] = filepath.Base(oldConfigsPath) - } - } else if statErr == nil { - log.Printf("Info: %s already exists. Current .gitconfig not linked as old-configs.\n", oldConfigsPath) - } - } else { - log.Printf("Info: Current .gitconfig at %s is a symlink, not backing up to old-configs.\n", gitConfig) - } - } - - // Re-populate configs map after potential backup, to ensure `old-configs` is listed if created. - // This is a bit redundant if the backup didn't happen or already existed, but ensures consistency. - configs = make(map[string]string) // Reset before re-populating - err = filepath.WalkDir(confPath+"/", func(path string, d fs.DirEntry, e error) error { - if d.IsDir() { return nil } - if e != nil { log.Printf("Warning: error accessing path %s: %v\n", path, e); return e } - configs[utils.Hash(path)] = filepath.Base(path) - return nil - }) - if err != nil { log.Printf("Error re-walking directory %s: %v\n", confPath, err) } - - - var profiles []string - var currentConfigPos int = -1 // Initialize to -1 to indicate not found - i := 0 - currentConfigFilename := "unknown (current .gitconfig may not be a saved profile)" - - // Check if gitConfigHash is valid and present in configs - // This might happen if .gitconfig is empty or unreadable initially - _, gitConfigHashOk := configs[gitConfigHash] - if gitConfigHashOk { - currentConfigFilename = configs[gitConfigHash] - } - - - for hash, val := range configs { - if hash == gitConfigHash { - currentConfigPos = i - } - profiles = append(profiles, val) - i++ - } - - if len(profiles) == 0 { - fmt.Printf("No git configuration profiles found in %s.\n", confPath) - fmt.Println("You can create one using 'git-switcher create' (once implemented as a subcommand).") - return - } - - // If currentConfigPos remained -1, it means .gitconfig's hash wasn't in `configs`. - // In promptui, CursorPos defaults to 0 if out of bounds, which is fine. - // But the label should be accurate. - selectLabel := "Select Git Config" - if currentConfigPos != -1 { - selectLabel += " (Current: " + currentConfigFilename + ")" - } else { - selectLabel += " (Current: " + currentConfigFilename + " - not in saved profiles)" - } - - - prompt := promptui.Select{ - Label: selectLabel, - Items: profiles, - CursorPos: currentConfigPos, // promptui handles -1 by defaulting to 0 - HideSelected: true, - } - - _, result, err := prompt.Run() - if err != nil { - if err == promptui.ErrInterrupt { - fmt.Println("Operation cancelled.") - os.Exit(0) - } - fmt.Printf("Prompt failed %v\n", err) - os.Exit(1) - } - newConfig := result - - err = os.Remove(gitConfig) - if err != nil && !os.IsNotExist(err) { - log.Panic(err) - } - - err = os.Symlink(filepath.Join(confPath, newConfig), gitConfig) - if err != nil { - log.Panic(err) - } - color.HiBlue("Switched to profile %q", newConfig) + fmt.Println("Welcome to git-switcher! Use 'git-switcher help' to see available commands.") + fmt.Println("To switch profiles interactively, use 'git-switcher switch'.") + fmt.Println("To list profiles, use 'git-switcher list'.") }, } @@ -194,22 +33,13 @@ func Execute() { } func init() { - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.git-switcher.yaml)") - // Add subcommands rootCmd.AddCommand(createCmd) rootCmd.AddCommand(deleteCmd) rootCmd.AddCommand(renameCmd) rootCmd.AddCommand(editCmd) rootCmd.AddCommand(switchCmd) - - // Cobra also supports local flags, which will only run - // when this action is called directly. - // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // Example flag removed + rootCmd.AddCommand(listCmd) } diff --git a/cmd/switch.go b/cmd/switch.go index f362573..a322157 100644 --- a/cmd/switch.go +++ b/cmd/switch.go @@ -23,7 +23,6 @@ import ( "github.com/fatih/color" "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" - // main_pkg "github.com/theykk/git-switcher" // Not needed for this command ) // switchCmd represents the switch command @@ -88,6 +87,5 @@ The ~/.gitconfig file will be updated to be a symlink to the selected profile.`, } func init() { - // Will be added in root.go - // rootCmd.AddCommand(switchCmd) + // rootCmd.AddCommand(switchCmd) // Commands are added in root.go's init }