gitlink-cli/shortcuts/branch/batch.go

162 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package branch
import (
"fmt"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)
func newBatchDeleteShortcut() *common.Shortcut {
return &common.Shortcut{
// +batch-delete批量删除多个分支
Name: "batch-delete",
Description: "批量删除多个分支",
Long: `Delete multiple branches from the current repository in a single operation.
Provide branch names via --names (comma-separated) or --from (plain-text file
with one branch name per line).
This action is irreversible. A confirmation prompt is shown before deletion
unless --yes is set.
Use --dry-run to preview which branches would be deleted without making any
changes.`,
Example: ` # Delete two branches
gitlink branch +batch-delete --names old-feature,deprecated-api
# Dry-run to preview deletions
gitlink branch +batch-delete --names old-feature,deprecated-api --dry-run
# Delete branches listed in a file
gitlink branch +batch-delete --from branches.txt`,
Flags: []common.Flag{
{Name: "names", Short: "n", Usage: "Comma-separated branch names"},
{Name: "from", Usage: "File path (one branch name per line)"},
{Name: "dry-run", Usage: "Preview changes without applying them", Bool: true, Default: "false"},
},
Run: runBatchDelete,
}
}
func newBatchProtectShortcut() *common.Shortcut {
return &common.Shortcut{
// +batch-protect批量为多个分支开启保护
Name: "batch-protect",
Description: "批量为多个分支开启保护",
Long: `Enable branch protection for multiple branches in a single operation.
Provide branch names via --names (comma-separated) or --from (plain-text file
with one branch name per line).
Protected branches restrict force-pushes and deletion.
Use --dry-run to preview which branches would be protected without making any
changes.`,
Example: ` # Protect two branches
gitlink branch +batch-protect --names main,develop
# Dry-run to preview protections
gitlink branch +batch-protect --names main,develop --dry-run
# Protect branches listed in a file
gitlink branch +batch-protect --from branches.txt`,
Flags: []common.Flag{
{Name: "names", Short: "n", Usage: "Comma-separated branch names"},
{Name: "from", Usage: "File path (one branch name per line)"},
{Name: "dry-run", Usage: "Preview changes without applying them", Bool: true, Default: "false"},
},
Run: runBatchProtect,
}
}
// --- batch-delete ---
// [B 类批量] 逐个删除分支——POST /branches/delete不可逆
// 使用 CollectStrings 而非 CollectNumbers分支名是字符串非数字
func runBatchDelete(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
names, err := common.CollectStrings(ctx.Arg("names"), ctx.Arg("from"))
if err != nil {
return err
}
if len(names) == 0 {
return fmt.Errorf("no branch names provided; use --names branch1,branch2 or --from branches.txt")
}
dryRun := common.ParseBool(ctx.Arg("dry-run"))
if !dryRun {
if err := common.ConfirmAction(
fmt.Sprintf("delete %d branch(es) from %s/%s", len(names), ctx.Owner, ctx.Repo),
); err != nil {
return err
}
}
summary := common.ProcessBatch(names, dryRun, "delete", func(name string) error {
payload := map[string]interface{}{
"branch_name": name,
}
_, err := ctx.CallAPI("POST", "/v1"+ctx.RepoPath()+"/branches/delete", payload)
return err
})
summary.Repository = fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo)
if err := ctx.OutputData(summary); err != nil {
return err
}
if summary.Failed > 0 {
return fmt.Errorf("%d of %d branch(es) failed to delete", summary.Failed, summary.Total)
}
return nil
}
// --- batch-protect ---
// [B 类批量] 逐个设置分支保护——POST /protected_branches
// 保护后的分支限制 force-push 和删除操作
func runBatchProtect(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
names, err := common.CollectStrings(ctx.Arg("names"), ctx.Arg("from"))
if err != nil {
return err
}
if len(names) == 0 {
return fmt.Errorf("no branch names provided; use --names branch1,branch2 or --from branches.txt")
}
dryRun := common.ParseBool(ctx.Arg("dry-run"))
summary := common.ProcessBatch(names, dryRun, "protect", func(name string) error {
payload := map[string]interface{}{
"branch_name": name,
}
_, err := ctx.CallAPI("POST", protectedBranchesPath(ctx.RepoPath()), payload)
return err
})
summary.Repository = fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo)
if err := ctx.OutputData(summary); err != nil {
return err
}
if summary.Failed > 0 {
return fmt.Errorf("%d of %d branch(es) failed to protect", summary.Failed, summary.Total)
}
return nil
}
// protectedBranchesPath returns the API path for a repo's protected-branches
// collection. Unlike most repository endpoints, protected_branches is served
// under the legacy /api route with NO /v1 prefix — forgeplus defines it as
// /api/:owner/:repo/protected_branches/:branch_name — so RepoPath() is used
// directly. (Empirically the /v1 form returns 404.)
func protectedBranchesPath(repoPath string) string {
return repoPath + "/protected_branches"
}