256 lines
7.3 KiB
Go
256 lines
7.3 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"kmanage/pkg/git"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var gitCmd = &cobra.Command{
|
|
Use: "git",
|
|
Short: "Git repository management (Multi-Account: Gitea / Forgejo / GitHub)",
|
|
Long: `Manage Git repositories, accounts, webhooks, and PR settings across multiple Git providers.`,
|
|
}
|
|
|
|
var gitConfigureCmd = &cobra.Command{
|
|
Use: "configure [flags]",
|
|
Short: "Configure a Git provider account",
|
|
Long: `Saves Git account metadata to ~/.kmanage.yaml and securely stores the Personal Access Token in the OS keyring.
|
|
|
|
Examples:
|
|
# Configure a Gitea account:
|
|
kmanage git configure --name gitea --url https://git.example.com --token my-access-token
|
|
|
|
# Configure a GitHub account:
|
|
kmanage git configure --name github --type github --token ghp_myGitHubToken`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
name, _ := cmd.Flags().GetString("name")
|
|
accType, _ := cmd.Flags().GetString("type")
|
|
url, _ := cmd.Flags().GetString("url")
|
|
token, _ := cmd.Flags().GetString("token")
|
|
username, _ := cmd.Flags().GetString("user")
|
|
|
|
if token == "" {
|
|
_ = cmd.Help()
|
|
fmt.Fprintln(os.Stderr)
|
|
return fmt.Errorf("❌ Error: The flag '--token' (or '-t') is required")
|
|
}
|
|
|
|
if name == "" {
|
|
if strings.Contains(url, "github.com") || accType == "github" {
|
|
name = "github"
|
|
} else {
|
|
name = "gitea"
|
|
}
|
|
}
|
|
|
|
if url == "" && (accType == "github" || name == "github") {
|
|
url = "https://api.github.com"
|
|
if accType == "" {
|
|
accType = "github"
|
|
}
|
|
}
|
|
|
|
if url == "" {
|
|
_ = cmd.Help()
|
|
fmt.Fprintln(os.Stderr)
|
|
return fmt.Errorf("❌ Error: The flag '--url' (or '-u') is required")
|
|
}
|
|
|
|
if err := SaveGitAccount(name, accType, url, token, username); err != nil {
|
|
return fmt.Errorf("failed to save Git account: %w", err)
|
|
}
|
|
|
|
fmt.Printf("✅ Git account '%s' (%s) successfully saved (token stored in OS keyring).\n", name, url)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var gitAccountsCmd = &cobra.Command{
|
|
Use: "accounts",
|
|
Aliases: []string{"account-list", "list-accounts"},
|
|
Short: "List all configured Git accounts",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
cfg, err := LoadAppConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(cfg.GitAccounts) == 0 {
|
|
fmt.Println("No Git accounts configured. Run 'kmanage git configure --name <name> --url <url> --token <token>'")
|
|
return nil
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
|
noHeader, _ := cmd.Flags().GetBool("no-header")
|
|
if !noHeader {
|
|
fmt.Fprintln(w, "ACCOUNT\tTYPE\tURL\tDEFAULT")
|
|
fmt.Fprintln(w, "-------\t----\t---\t-------")
|
|
}
|
|
|
|
for name, acc := range cfg.GitAccounts {
|
|
isDefault := "no"
|
|
if name == cfg.DefaultGitAccount {
|
|
isDefault = "yes (*)"
|
|
}
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, acc.Type, acc.URL, isDefault)
|
|
}
|
|
|
|
return w.Flush()
|
|
},
|
|
}
|
|
|
|
var gitSetDefaultCmd = &cobra.Command{
|
|
Use: "set-default <account_name>",
|
|
Short: "Set the default active Git account",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
accountName := args[0]
|
|
if err := SetDefaultGitAccount(accountName); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("✅ Default Git account set to '%s'.\n", accountName)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var gitDeleteAccountCmd = &cobra.Command{
|
|
Use: "delete-account <account_name>",
|
|
Aliases: []string{"rm-account", "del-account"},
|
|
Short: "Delete a configured Git account",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
accountName := args[0]
|
|
if err := DeleteGitAccount(accountName); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("✅ Git account '%s' successfully deleted.\n", accountName)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var gitListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List Git resources (e.g. repos)",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return runListRepos(cmd, args)
|
|
},
|
|
}
|
|
|
|
var gitListReposCmd = &cobra.Command{
|
|
Use: "repos",
|
|
Short: "List all repositories along with their webhooks",
|
|
RunE: runListRepos,
|
|
}
|
|
|
|
func runListRepos(cmd *cobra.Command, args []string) error {
|
|
accountFlag, _ := cmd.Flags().GetString("account")
|
|
allAccountsFlag, _ := cmd.Flags().GetBool("all-accounts")
|
|
|
|
clients, err := getSelectedGitClients(accountFlag, allAccountsFlag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var allReposWithHooks []git.RepositoryWithHooks
|
|
for _, client := range clients {
|
|
reposWithHooks, err := client.ListRepositoriesWithHooks()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "⚠️ Warning retrieving account '%s': %v\n", client.AccountName, err)
|
|
continue
|
|
}
|
|
allReposWithHooks = append(allReposWithHooks, reposWithHooks...)
|
|
}
|
|
|
|
if len(allReposWithHooks) == 0 {
|
|
fmt.Println("No repositories found.")
|
|
return nil
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
|
|
|
noHeader, _ := cmd.Flags().GetBool("no-header")
|
|
if !noHeader {
|
|
if allAccountsFlag || len(clients) > 1 {
|
|
fmt.Fprintln(w, "ACCOUNT\tREPOSITORY\tSTATUS\tBRANCH\tPR AUTO-DEL\tWEBHOOKS")
|
|
fmt.Fprintln(w, "-------\t----------\t------\t------\t-----------\t--------")
|
|
} else {
|
|
fmt.Fprintln(w, "REPOSITORY\tSTATUS\tBRANCH\tPR AUTO-DEL\tWEBHOOKS")
|
|
fmt.Fprintln(w, "----------\t------\t------\t-----------\t--------")
|
|
}
|
|
}
|
|
|
|
for _, item := range allReposWithHooks {
|
|
r := item.Repository
|
|
visibility := "public"
|
|
if r.Private {
|
|
visibility = "private"
|
|
}
|
|
if r.Fork {
|
|
visibility += " (fork)"
|
|
}
|
|
|
|
branch := r.DefaultBranch
|
|
if branch == "" {
|
|
branch = "-"
|
|
}
|
|
|
|
prAutoDel := "no"
|
|
if r.IsAutoDeleteBranch() {
|
|
prAutoDel = "yes"
|
|
}
|
|
|
|
var hookStrings []string
|
|
for _, h := range item.Hooks {
|
|
target := h.TargetURL()
|
|
statusStr := "active"
|
|
if !h.Active {
|
|
statusStr = "inactive"
|
|
}
|
|
hookStrings = append(hookStrings, fmt.Sprintf("%s (%s)", target, statusStr))
|
|
}
|
|
|
|
webhooksDisplay := "-"
|
|
if len(hookStrings) > 0 {
|
|
webhooksDisplay = strings.Join(hookStrings, ", ")
|
|
}
|
|
|
|
if allAccountsFlag || len(clients) > 1 {
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", r.AccountName, r.FullName, visibility, branch, prAutoDel, webhooksDisplay)
|
|
} else {
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", r.FullName, visibility, branch, prAutoDel, webhooksDisplay)
|
|
}
|
|
}
|
|
|
|
return w.Flush()
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(gitCmd)
|
|
gitCmd.AddCommand(gitConfigureCmd)
|
|
gitCmd.AddCommand(gitAccountsCmd)
|
|
gitCmd.AddCommand(gitSetDefaultCmd)
|
|
gitCmd.AddCommand(gitDeleteAccountCmd)
|
|
gitCmd.AddCommand(gitListCmd)
|
|
gitListCmd.AddCommand(gitListReposCmd)
|
|
|
|
gitConfigureCmd.Flags().StringP("name", "n", "", "Unique name for the Git account (e.g. 'gitea', 'github', 'work')")
|
|
gitConfigureCmd.Flags().StringP("type", "", "", "Git provider type: 'gitea', 'forgejo', or 'github' (optional, auto-detected)")
|
|
gitConfigureCmd.Flags().StringP("url", "u", "", "Git API Base URL (e.g. https://git.example.com or https://api.github.com)")
|
|
gitConfigureCmd.Flags().StringP("token", "t", "", "Git Personal Access Token (required)")
|
|
gitConfigureCmd.Flags().StringP("user", "", "", "Optional username")
|
|
|
|
for _, c := range []*cobra.Command{gitListCmd, gitListReposCmd} {
|
|
c.Flags().StringP("account", "A", "", "Select a specific Git account (default: configured default account)")
|
|
c.Flags().BoolP("all-accounts", "", false, "List repositories across ALL configured Git accounts")
|
|
_ = c.RegisterFlagCompletionFunc("account", completeGitAccountNames)
|
|
}
|
|
|
|
_ = gitSetDefaultCmd.RegisterFlagCompletionFunc("default", completeGitAccountNames)
|
|
}
|