forked from Gitlink/gitlink-cli
162 lines
5.1 KiB
Go
162 lines
5.1 KiB
Go
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"
|
||
}
|