Files
gitea-tea/internal/app/milestone_issues.go
T
bircni 1035ff551b fix(milestone): address milestones by title or ID everywhere
milestone view v2.0 failed with "invalid resource number" although its
usage promised <id-or-name>, and issue create --milestone v1.0 failed
with a strconv error. Every milestone argument (view, edit, delete,
close, reopen, milestone issue add|remove|list) and issue create|edit
-m/--milestone now accept a title or an ID: a number is an ID first,
then a title; --by-name matches numeric titles only. --milestone-name
stays as a hidden name of --milestone, and issue edit gains
--remove-milestone (as does --milestone 0).

Signed-off-by: bircni <bircni@icloud.com>
2026-10-12 00:14:30 +02:00

87 lines
3.2 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package app
import (
"errors"
"strconv"
"strings"
"gitea.dev/tea/internal/clierr"
"gitea.dev/tea/internal/services"
"github.com/spf13/cobra"
)
// findMilestone resolves a milestone argument, a title or an ID, in the target repository.
func findMilestone(cmd *cobra.Command, t target, raw string, byName bool) (services.Milestone, error) {
if strings.TrimSpace(raw) == "" {
return services.Milestone{}, clierr.Wrap(clierr.Usage, errors.New("milestone title or ID cannot be empty"))
}
return (services.Milestones{Client: t.client, Repository: t.Repo}).FindOne(cmd.Context(), raw, byName)
}
func (a *application) milestoneIssueCommand() *cobra.Command {
group := &cobra.Command{Use: "issue", Short: "Manage milestone issues and pull requests"}
opts := services.ListOptions{}
var byName bool
list := &cobra.Command{Use: "list <milestone>", Short: "List issues and pull requests in a milestone", Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
if err := oneOf("state", opts.State, "open", "closed", "all"); err != nil {
return err
}
if err := oneOf("kind", opts.Kind, "all", "issues", "pulls"); err != nil {
return err
}
target, err := a.resolve(cmd.Context(), true)
if err != nil {
return err
}
milestone, err := findMilestone(cmd, target, args[0], byName)
if err != nil {
return err
}
opts.Milestones = strconv.FormatInt(milestone.ID, 10)
items, more, err := (services.Issues{Client: target.client, Repository: target.Repo}).List(cmd.Context(), opts)
if err != nil {
return err
}
if err := writeList(a, cmd, items, issueRow); err != nil {
return err
}
return a.morePagesHint(cmd, more, opts.Page)
}}
pageFlags(list, &opts.Limit, &opts.Page)
list.Flags().StringVar(&opts.State, "state", "open", "open, closed, or all")
list.Flags().StringVar(&opts.Kind, "kind", "all", "all, issues, or pulls")
list.Flags().BoolVar(&byName, "by-name", false, "Match a numeric argument as a milestone title rather than an ID")
group.AddCommand(list)
for _, verb := range []string{"add", "remove"} {
var byName bool
command := &cobra.Command{Use: verb + " <milestone> <number>...", Short: titleCase(verb) + " issues or pull requests in a milestone", Args: usageArgs(cobra.MinimumNArgs(2)), RunE: func(cmd *cobra.Command, args []string) error {
numbers, err := resourceNumbers(args[1:])
if err != nil {
return err
}
target, err := a.resolve(cmd.Context(), true)
if err != nil {
return err
}
milestone, err := findMilestone(cmd, target, args[0], byName)
if err != nil {
return err
}
id := milestone.ID
service := services.Issues{Client: target.client, Repository: target.Repo}
return batch(a, cmd, numbers, issueLabel, func(n int64) (services.Issue, error) {
if verb == "remove" {
return service.RemoveMilestone(cmd.Context(), n, id)
}
return service.Edit(cmd.Context(), n, services.IssueEdit{Milestone: &id})
}, issueRow)
}}
command.Flags().BoolVar(&byName, "by-name", false, "Match a numeric argument as a milestone title rather than an ID")
group.AddCommand(command)
}
return group
}