Files
kmanage/cmd/git_pr_autodel.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)
}
}