216 lines
6.4 KiB
Go
216 lines
6.4 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"kmanage/pkg/git"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var gitPRAutoDelCmd = &cobra.Command{
|
|
Use: "pr-autodel",
|
|
Aliases: []string{"set-pr-autodel", "pr-branch-del", "autodel"},
|
|
Short: "Enable or disable automatic PR branch deletion after merge",
|
|
Long: `Controls the repository setting to automatically delete head branches upon pull request merge.
|
|
Compatible with Gitea, Forgejo, and GitHub (delete_branch_on_merge).
|
|
Can be applied to a single repository, all repositories of an owner/organization, or all accessible repositories.
|
|
|
|
Examples:
|
|
# Enable or disable for a single repository:
|
|
kmanage git pr-autodel enable Docker/authentik
|
|
kmanage git pr-autodel disable Docker/authentik
|
|
|
|
# For all repositories of an owner/organization (e.g. 'Docker'):
|
|
kmanage git pr-autodel enable --owner Docker
|
|
kmanage git pr-autodel disable --owner Docker -y
|
|
|
|
# On a specific Git account (e.g. GitHub):
|
|
kmanage git pr-autodel enable --account github --owner my-org
|
|
|
|
# Globally for ALL accessible repositories:
|
|
kmanage git pr-autodel enable --all`,
|
|
}
|
|
|
|
var gitPREnableCmd = &cobra.Command{
|
|
Use: "enable [owner/repo]",
|
|
Aliases: []string{"on", "1", "true"},
|
|
Short: "Enable automatic deletion of PR branches after merge",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return handleSetPRAutoDel(cmd, args, true)
|
|
},
|
|
}
|
|
|
|
var gitPRDisableCmd = &cobra.Command{
|
|
Use: "disable [owner/repo]",
|
|
Aliases: []string{"off", "0", "false"},
|
|
Short: "Disable automatic deletion of PR branches after merge",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return handleSetPRAutoDel(cmd, args, false)
|
|
},
|
|
}
|
|
|
|
func handleSetPRAutoDel(cmd *cobra.Command, args []string, enable bool) error {
|
|
ownerFlag, _ := cmd.Flags().GetString("owner")
|
|
allFlag, _ := cmd.Flags().GetBool("all")
|
|
autoYes, _ := cmd.Flags().GetBool("yes")
|
|
accountFlag, _ := cmd.Flags().GetString("account")
|
|
allAccountsFlag, _ := cmd.Flags().GetBool("all-accounts")
|
|
|
|
actionName := "enable"
|
|
stateName := "enabled"
|
|
if !enable {
|
|
actionName = "disable"
|
|
stateName = "disabled"
|
|
}
|
|
|
|
// Case 1: Single repository specified as argument
|
|
if len(args) > 0 {
|
|
owner, repo, err := parseRepoArg(args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
client, err := getGitClientForAccount(accountFlag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
updatedRepo, err := client.SetAutoDeleteBranch(owner, repo, enable)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update %s/%s (%s): %w", owner, repo, client.AccountName, err)
|
|
}
|
|
|
|
fmt.Printf("✅ PR Auto-Delete for '%s' (%s) successfully %s (Current value: %v).\n",
|
|
updatedRepo.FullName, client.AccountName, stateName, updatedRepo.IsAutoDeleteBranch())
|
|
return nil
|
|
}
|
|
|
|
// Case 2: Multiple repositories via --owner or --all
|
|
if ownerFlag == "" && !allFlag {
|
|
_ = cmd.Help()
|
|
fmt.Fprintln(os.Stderr)
|
|
return fmt.Errorf("❌ Error: Specify a repository (e.g. 'Docker/authentik') or use '--owner <name>' / '--all'")
|
|
}
|
|
|
|
clients, err := getSelectedGitClients(accountFlag, allAccountsFlag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
type targetRepo struct {
|
|
Repo git.Repository
|
|
Client *git.Client
|
|
}
|
|
|
|
var targets []targetRepo
|
|
for _, client := range clients {
|
|
repos, err := client.ListRepositories()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "⚠️ Warning retrieving account '%s': %v\n", client.AccountName, err)
|
|
continue
|
|
}
|
|
|
|
for _, r := range repos {
|
|
if allFlag {
|
|
targets = append(targets, targetRepo{Repo: r, Client: client})
|
|
} else if ownerFlag != "" {
|
|
ownerName := r.Owner.UserName
|
|
if ownerName == "" && strings.Contains(r.FullName, "/") {
|
|
ownerName = strings.SplitN(r.FullName, "/", 2)[0]
|
|
}
|
|
if strings.EqualFold(ownerName, ownerFlag) {
|
|
targets = append(targets, targetRepo{Repo: r, Client: client})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(targets) == 0 {
|
|
if ownerFlag != "" {
|
|
fmt.Printf("No repositories found for owner '%s'.\n", ownerFlag)
|
|
} else {
|
|
fmt.Println("No repositories found.")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
scopeDescription := fmt.Sprintf("all %d repositories", len(targets))
|
|
if ownerFlag != "" {
|
|
scopeDescription = fmt.Sprintf("all %d repositories of owner '%s'", len(targets), ownerFlag)
|
|
}
|
|
|
|
if !autoYes {
|
|
prompt := fmt.Sprintf("Do you really want to %s PR Auto-Delete for %s?", actionName, scopeDescription)
|
|
if !confirmAction(prompt) {
|
|
fmt.Println("Cancelled.")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
fmt.Printf("\nUpdating %d repositories...\n", len(targets))
|
|
|
|
var wg sync.WaitGroup
|
|
semaphore := make(chan struct{}, 8)
|
|
var successCount int
|
|
var countMu sync.Mutex
|
|
|
|
for _, t := range targets {
|
|
wg.Add(1)
|
|
go func(item targetRepo) {
|
|
defer wg.Done()
|
|
semaphore <- struct{}{}
|
|
defer func() { <-semaphore }()
|
|
|
|
ownerName := item.Repo.Owner.UserName
|
|
if ownerName == "" && strings.Contains(item.Repo.FullName, "/") {
|
|
ownerName = strings.SplitN(item.Repo.FullName, "/", 2)[0]
|
|
}
|
|
repoName := item.Repo.Name
|
|
if repoName == "" && strings.Contains(item.Repo.FullName, "/") {
|
|
repoName = strings.SplitN(item.Repo.FullName, "/", 2)[1]
|
|
}
|
|
|
|
_, err := item.Client.SetAutoDeleteBranch(ownerName, repoName, enable)
|
|
|
|
countMu.Lock()
|
|
defer countMu.Unlock()
|
|
|
|
if err != nil {
|
|
fmt.Printf("❌ Error updating [%s] %s: %v\n", item.Client.AccountName, item.Repo.FullName, err)
|
|
} else {
|
|
fmt.Printf("✅ [%s] %-30s -> PR Auto-Delete %s\n", item.Client.AccountName, item.Repo.FullName, stateName)
|
|
successCount++
|
|
}
|
|
}(t)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
fmt.Printf("\nDone: %d of %d repositories successfully updated.\n", successCount, len(targets))
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
gitCmd.AddCommand(gitPRAutoDelCmd)
|
|
gitPRAutoDelCmd.AddCommand(gitPREnableCmd)
|
|
gitPRAutoDelCmd.AddCommand(gitPRDisableCmd)
|
|
|
|
gitPREnableCmd.ValidArgsFunction = completeRepoNames
|
|
gitPRDisableCmd.ValidArgsFunction = completeRepoNames
|
|
|
|
for _, c := range []*cobra.Command{gitPREnableCmd, gitPRDisableCmd} {
|
|
c.Flags().StringP("owner", "o", "", "Apply to all repositories belonging to this owner/organization")
|
|
c.Flags().BoolP("all", "a", false, "Apply to all accessible repositories")
|
|
c.Flags().BoolP("yes", "y", false, "Skip interactive confirmation prompt")
|
|
c.Flags().StringP("account", "A", "", "Select specific Git account (default: configured default account)")
|
|
c.Flags().Bool("all-accounts", false, "Apply across all configured Git accounts")
|
|
|
|
_ = c.RegisterFlagCompletionFunc("owner", completeOwnerNames)
|
|
_ = c.RegisterFlagCompletionFunc("account", completeGitAccountNames)
|
|
}
|
|
}
|