mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
refactor(issue,pr): trim help, restating comments and single-use helpers
Cut issue, pr, label, milestone and comment examples to at most three, drop doc comments that only restate the name, fold the comment and attachment argument lookups into one closure each, and inline label helpers with one caller. Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
13 files changed
+57
-147
No files matched your search
@@ -75,12 +75,8 @@ var errNoMatchingRemote = errors.New("no Git remote matches the selected reposit
|
||||
func (a *application) pullCheckoutCommand() *cobra.Command {
|
||||
var branch string
|
||||
var detached, update bool
|
||||
cmd := &cobra.Command{Use: "checkout {<number> | <url> | <branch>}", Aliases: []string{"co"}, Short: "Check out a pull request", Long: `Check out the head of a pull request in a local branch.
|
||||
|
||||
The branch takes the name of the pull request's head branch when that name is free or
|
||||
already points at the head; otherwise it is OWNER/BRANCH for a fork, or pr/NUMBER. tea
|
||||
records the pull request in the branch's Git configuration, so that pr commands without an
|
||||
argument select it, and the branch tracks the head branch, so that git push updates it.`, Annotations: pullArgumentsAnnotation(nil), Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cmd := &cobra.Command{Use: "checkout {<number> | <url> | <branch>}", Aliases: []string{"co"}, Short: "Check out a pull request", Long: `Check out the head of a pull request in a local branch that tracks the head branch, so
|
||||
that git push updates it and pr commands without an argument select the pull request.`, Annotations: pullArgumentsAnnotation(nil), Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if detached && (cmd.Flags().Changed("branch") || update) {
|
||||
return clierr.Wrap(clierr.Usage, errors.New("--detach and --branch/--update are mutually exclusive"))
|
||||
}
|
||||
@@ -156,7 +152,6 @@ argument select it, and the branch tracks the head branch, so that git push upda
|
||||
cmd.ValidArgsFunction = a.completePulls("open")
|
||||
cmd.Example = `$ tea pr checkout 13
|
||||
$ tea pr co feature/login
|
||||
$ tea pr checkout 13 --branch review-13
|
||||
$ tea pr checkout 13 --update`
|
||||
return cmd
|
||||
}
|
||||
|
||||
+16
-40
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// commentTitle is the prompt for a comment body.
|
||||
const commentTitle = "Comment (markdown):"
|
||||
|
||||
// commentNouns are the words for the commands of an issue or pull request comment group.
|
||||
@@ -65,7 +64,6 @@ func commentName(t target, comment services.Comment) string {
|
||||
return fmt.Sprintf("comment %d on %s#%d", comment.ID, t.Repo, comment.IssueNumber)
|
||||
}
|
||||
|
||||
// commentFlags are the body flags of commands that write a comment.
|
||||
type commentFlags struct {
|
||||
body, file string
|
||||
useEditor bool
|
||||
@@ -142,7 +140,6 @@ func (a *application) commentOn(cmd *cobra.Command, t target, n int64, kind refK
|
||||
return a.created(cmd, comment, comment.URL)
|
||||
}
|
||||
|
||||
// lastComment returns the newest comment on n by the account tea acts as.
|
||||
func lastComment(cmd *cobra.Command, t target, n int64) (services.Comment, error) {
|
||||
items, err := (services.Comments{Client: t.client, Repository: t.Repo}).List(cmd.Context(), n)
|
||||
if err != nil {
|
||||
@@ -156,14 +153,22 @@ func lastComment(cmd *cobra.Command, t target, n int64) (services.Comment, error
|
||||
return services.Comment{}, fmt.Errorf("%s has no comments on %s#%d", t.login(), t.Repo, n)
|
||||
}
|
||||
|
||||
// comment returns comment id, which a command of kind may change: pr commands refuse comments on
|
||||
// issues.
|
||||
func (a *application) comment(cmd *cobra.Command, t target, id int64, kind refKind) (services.Comment, error) {
|
||||
// comment returns the comment that the ID argument names, which a command of kind may change: pr
|
||||
// commands refuse comments on issues.
|
||||
func (a *application) comment(cmd *cobra.Command, arg string, kind refKind) (target, services.Comment, error) {
|
||||
id, err := number(arg)
|
||||
if err != nil {
|
||||
return target{}, services.Comment{}, err
|
||||
}
|
||||
t, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return t, services.Comment{}, err
|
||||
}
|
||||
current, err := (services.Comments{Client: t.client, Repository: t.Repo}).View(cmd.Context(), id)
|
||||
if err == nil && kind == pullRef && !current.Pull {
|
||||
err = fmt.Errorf("comment %d in %s is on an issue, not a pull request; use %s %d", id, t.Repo, issueCommandPath(cmd), id)
|
||||
}
|
||||
return current, err
|
||||
return t, current, err
|
||||
}
|
||||
|
||||
// editComment replaces the text of comment, prefilling the prompt or editor with it.
|
||||
@@ -184,8 +189,7 @@ func (a *application) editComment(cmd *cobra.Command, t target, comment services
|
||||
|
||||
// deleteComment deletes comment after a confirmation that defaults to no.
|
||||
func (a *application) deleteComment(cmd *cobra.Command, t target, comment services.Comment) error {
|
||||
name := commentName(t, comment)
|
||||
return a.deleteConfirmed(cmd, name, func() error {
|
||||
return a.deleteConfirmed(cmd, commentName(t, comment), func() error {
|
||||
return (services.Comments{Client: t.client, Repository: t.Repo}).Delete(cmd.Context(), comment.ID)
|
||||
})
|
||||
}
|
||||
@@ -262,10 +266,7 @@ func (a *application) commentCommand(kind refKind) *cobra.Command {
|
||||
|
||||
Without --body, --body-file or --editor, the configured editor opens, or a multi-line prompt
|
||||
asks for the comment. The subcommands list, view, edit and delete comments by their ID.`, kind.article()), Example: fmt.Sprintf(`$ tea %[1]s comment %[2]s --body "Fixed in #14"
|
||||
$ tea %[1]s comment %[2]s --body-file notes.md
|
||||
# Change or remove your last comment
|
||||
$ tea %[1]s comment %[2]s --edit-last --body "Fixed in #15"
|
||||
$ tea %[1]s comment %[2]s --delete-last --yes
|
||||
$ tea %[1]s comment list %[2]s`, words.group, words.example), Annotations: map[string]string{"tea.arguments": words.arguments, "tea.confirm": "true"}, Args: commentArgs(kind), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
if localFlagsChanged(cmd) {
|
||||
@@ -329,15 +330,7 @@ $ tea %[1]s comment list %[2]s --json id,author,body`, words.group, words.exampl
|
||||
list.ValidArgsFunction = a.completeDiscussions(kind)
|
||||
|
||||
view := &cobra.Command{Use: "view <comment-id>", Short: "View a comment", Example: fmt.Sprintf("$ tea %s comment view 81", words.group), Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
id, err := number(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
comment, err := a.comment(cmd, t, id, kind)
|
||||
t, comment, err := a.comment(cmd, args[0], kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -365,15 +358,7 @@ $ tea %[1]s comment edit 81`, words.group), Args: argsExact(1), RunE: func(cmd *
|
||||
if err := editFlags.validate(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := number(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
comment, err := a.comment(cmd, t, id, kind)
|
||||
t, comment, err := a.comment(cmd, args[0], kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -382,15 +367,7 @@ $ tea %[1]s comment edit 81`, words.group), Args: argsExact(1), RunE: func(cmd *
|
||||
editFlags.register(edit)
|
||||
|
||||
remove := &cobra.Command{Use: "delete <comment-id>", Short: "Delete a comment", Example: fmt.Sprintf("$ tea %s comment delete 81 --yes", words.group), Annotations: map[string]string{"tea.text": "comment deletion does not return a resource", "tea.confirm": "true"}, Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
id, err := number(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
comment, err := a.comment(cmd, t, id, kind)
|
||||
t, comment, err := a.comment(cmd, args[0], kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -415,7 +392,6 @@ func commentArgs(kind refKind) cobra.PositionalArgs {
|
||||
}
|
||||
}
|
||||
|
||||
// completeDiscussions completes the open issues or pull requests that a command of kind takes.
|
||||
func (a *application) completeDiscussions(kind refKind) cobra.CompletionFunc {
|
||||
if kind == pullRef {
|
||||
return firstArg(a.completePulls("open"))
|
||||
|
||||
@@ -24,7 +24,6 @@ func (a *application) discussionSearchCommand(pulls bool) *cobra.Command {
|
||||
var updated timeRange
|
||||
opts.Pulls = pulls
|
||||
example := fmt.Sprintf(`$ tea search %[1]s "login error"
|
||||
$ tea search %[1]s --author @me --state all
|
||||
# Search one repository; its labels and milestones are checked
|
||||
$ tea search %[1]s crash -R alice/project --label bug --milestone v1.0`, noun)
|
||||
if pulls {
|
||||
|
||||
@@ -37,12 +37,10 @@ type discussionComment struct {
|
||||
meta [2]string
|
||||
}
|
||||
|
||||
// stateBadge shows a discussion state, such as Open or Merged, in its color.
|
||||
func stateBadge(state string) terminal.Cell {
|
||||
return terminal.Cell{Text: titleCase(state), Color: stateColors[state]}
|
||||
}
|
||||
|
||||
// labelCells shows labels in their colors.
|
||||
func labelCells(labels []services.Label) []terminal.Cell {
|
||||
cells := make([]terminal.Cell, len(labels))
|
||||
for i, label := range labels {
|
||||
@@ -151,7 +149,6 @@ func pullView(item services.Pull, repo string, details pullDetails) discussionVi
|
||||
if checks != "" {
|
||||
checksHint = fmt.Sprintf("%s (tea pr checks %d)", checks, item.Number)
|
||||
}
|
||||
reviews := details.reviews
|
||||
return discussionView{
|
||||
number: item.Number, comments: item.Comments, summary: summary,
|
||||
view: terminal.View{
|
||||
@@ -176,7 +173,7 @@ func pullView(item services.Pull, repo string, details pullDetails) discussionVi
|
||||
}
|
||||
},
|
||||
extraComments: func(cmd *cobra.Command, t target, n int64) ([]discussionComment, error) {
|
||||
return pullReviewComments(cmd, t, n, reviews)
|
||||
return pullReviewComments(cmd, t, n, details.reviews)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ func (a *application) issueAttachmentCommand(kind refKind) *cobra.Command {
|
||||
group := &cobra.Command{Use: "attachment", Short: "Manage the attachments of " + kind.noun() + "s", Long: fmt.Sprintf(`List, view, upload, download, rename and delete the files attached to %s. Attachments
|
||||
are addressed by the %s number and the attachment ID that attachment list shows.`, kind.article(), kind.noun())}
|
||||
annotations := map[string]string{"tea.arguments": words.arguments}
|
||||
// discussionTarget resolves the issue or pull request argument of an attachment command.
|
||||
discussionTarget := func(cmd *cobra.Command, raw string) (target, int64, error) {
|
||||
t, n, err := a.resolveRef(cmd, raw, kind)
|
||||
if err == nil && kind == pullRef {
|
||||
@@ -56,6 +55,15 @@ are addressed by the %s number and the attachment ID that attachment list shows.
|
||||
}
|
||||
return t, n, err
|
||||
}
|
||||
// attachmentArgs checks the attachment ID before any request.
|
||||
attachmentArgs := func(cmd *cobra.Command, args []string) (target, int64, int64, error) {
|
||||
id, err := number(args[1])
|
||||
if err != nil {
|
||||
return target{}, 0, 0, err
|
||||
}
|
||||
t, n, err := discussionTarget(cmd, args[0])
|
||||
return t, n, id, err
|
||||
}
|
||||
completeFirst := a.completeDiscussions(kind)
|
||||
|
||||
var limit, page int
|
||||
@@ -77,7 +85,7 @@ are addressed by the %s number and the attachment ID that attachment list shows.
|
||||
pageFlags(list, &limit, &page)
|
||||
|
||||
view := &cobra.Command{Use: "view {<number> | <url>} <attachment-id>", Short: "View an attachment", Example: fmt.Sprintf("$ tea %s attachment view %s 9", words.group, words.example), Annotations: annotations, Args: argsExact(2), ValidArgsFunction: completeFirst, RunE: func(cmd *cobra.Command, args []string) error {
|
||||
t, n, id, err := a.attachmentArgs(cmd, args, discussionTarget)
|
||||
t, n, id, err := attachmentArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -121,7 +129,7 @@ $ tea %[1]s attachment upload %[2]s screenshot.png --name before.png`, words.gro
|
||||
if strings.TrimSpace(newName) == "" {
|
||||
return clierr.Wrap(clierr.Usage, errors.New("supply a nonempty --name"))
|
||||
}
|
||||
t, n, id, err := a.attachmentArgs(cmd, args, discussionTarget)
|
||||
t, n, id, err := attachmentArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -135,7 +143,7 @@ $ tea %[1]s attachment upload %[2]s screenshot.png --name before.png`, words.gro
|
||||
edit.Flags().StringVar(&newName, "name", "", "New file `name`")
|
||||
|
||||
remove := &cobra.Command{Use: "delete {<number> | <url>} <attachment-id>", Short: "Delete an attachment", Example: fmt.Sprintf("$ tea %s attachment delete %s 9 --yes", words.group, words.example), Annotations: map[string]string{"tea.arguments": words.arguments, "tea.confirm": "true", "tea.text": "attachment deletion does not return a resource"}, Args: argsExact(2), ValidArgsFunction: completeFirst, RunE: func(cmd *cobra.Command, args []string) error {
|
||||
t, n, id, err := a.attachmentArgs(cmd, args, discussionTarget)
|
||||
t, n, id, err := attachmentArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -144,8 +152,7 @@ $ tea %[1]s attachment upload %[2]s screenshot.png --name before.png`, words.gro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name := attachmentName(t, n, item)
|
||||
return a.deleteConfirmed(cmd, name, func() error { return service.DeleteAttachment(cmd.Context(), n, id) })
|
||||
return a.deleteConfirmed(cmd, attachmentName(t, n, item), func() error { return service.DeleteAttachment(cmd.Context(), n, id) })
|
||||
}}
|
||||
|
||||
var destination downloadFlags
|
||||
@@ -155,7 +162,7 @@ skips the download.`, Example: fmt.Sprintf(`$ tea %[1]s attachment download %[2]
|
||||
$ tea %[1]s attachment download %[2]s 9 --output crash.log --clobber
|
||||
# Write the file to standard output
|
||||
$ tea %[1]s attachment download %[2]s 9 -O - | less`, words.group, words.example), Annotations: map[string]string{"tea.arguments": words.arguments, "tea.text": "attachment downloads are binary"}, Args: argsExact(2), ValidArgsFunction: completeFirst, RunE: func(cmd *cobra.Command, args []string) error {
|
||||
t, n, id, err := a.attachmentArgs(cmd, args, discussionTarget)
|
||||
t, n, id, err := attachmentArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -184,14 +191,3 @@ $ tea %[1]s attachment download %[2]s 9 -O - | less`, words.group, words.example
|
||||
group.AddCommand(list, view, upload, edit, remove, download)
|
||||
return group
|
||||
}
|
||||
|
||||
// attachmentArgs resolves the issue or pull request and attachment ID arguments of an attachment
|
||||
// command, checking the ID before any request.
|
||||
func (a *application) attachmentArgs(cmd *cobra.Command, args []string, resolve func(*cobra.Command, string) (target, int64, error)) (target, int64, int64, error) {
|
||||
id, err := number(args[1])
|
||||
if err != nil {
|
||||
return target{}, 0, 0, err
|
||||
}
|
||||
t, n, err := resolve(cmd, args[0])
|
||||
return t, n, id, err
|
||||
}
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// errNothingToEdit is the shared answer to edit commands without anything to change.
|
||||
var errNothingToEdit = clierr.Wrap(clierr.Usage, errors.New("nothing to edit; pass a flag such as --title (see --help)"))
|
||||
|
||||
func (a *application) issueEditCommand() *cobra.Command {
|
||||
@@ -25,16 +24,14 @@ func (a *application) issueEditCommand() *cobra.Command {
|
||||
starting from its current values. Label and assignee flags add to or remove from each issue's
|
||||
current values; the --set flags replace them.`, Example: `$ tea issue edit 12 --title "Crash on start" --add-label bug
|
||||
$ tea issue edit 12 13 --add-assignee @me --milestone v1.0
|
||||
$ tea issue edit 12 --remove-label wontfix --remove-milestone
|
||||
$ tea issue edit 12 --body-file notes.md`, Annotations: map[string]string{"tea.arguments": issueArguments}, Args: usageArgs(cobra.MinimumNArgs(1)), RunE: func(cmd *cobra.Command, args []string) (runErr error) {
|
||||
$ tea issue edit 12 --remove-label wontfix --remove-milestone`, Annotations: map[string]string{"tea.arguments": issueArguments}, Args: usageArgs(cobra.MinimumNArgs(1)), RunE: func(cmd *cobra.Command, args []string) (runErr error) {
|
||||
var draft terminal.Draft
|
||||
defer finishDraft(&draft, &runErr)
|
||||
if err := validateBodySources(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
prompt := a.canPrompt()
|
||||
if !localFlagsChanged(cmd) {
|
||||
if !prompt || a.deps.IssueEditForm == nil {
|
||||
if !a.canPrompt() || a.deps.IssueEditForm == nil {
|
||||
return errNothingToEdit
|
||||
}
|
||||
target, numbers, err := a.resolveRefs(cmd, args, issueRef)
|
||||
@@ -128,7 +125,6 @@ $ tea issue edit 12 --body-file notes.md`, Annotations: map[string]string{"tea.a
|
||||
return cmd
|
||||
}
|
||||
|
||||
// editedIssue words the done line of an issue edit.
|
||||
func editedIssue(t target) func(services.Issue) string {
|
||||
return func(item services.Issue) string {
|
||||
return "Edited " + discussionNoun(item.Kind) + " " + refLabel(t.Repo, item.Number, item.Title)
|
||||
|
||||
+4
-13
@@ -20,7 +20,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// issueArguments is the ARGUMENTS help of commands that take issue references.
|
||||
const issueArguments = `An issue can be supplied as argument in any of the following formats:
|
||||
- by number, e.g. "123" or "#123"
|
||||
- by URL, e.g. "https://gitea.com/OWNER/REPO/issues/123"
|
||||
@@ -44,8 +43,7 @@ func (a *application) issueListCommand() *cobra.Command {
|
||||
Labels and milestones must exist in the repository.`, Example: `$ tea issue list --label bug --state all
|
||||
$ tea issue list --assignee @me --json number,title
|
||||
# Issues and pull requests in a milestone, as tab-separated values
|
||||
$ tea issue list --kind all --milestone v1.0 | cut -f1,2
|
||||
$ tea issue list --search "login" --web`, Args: argsExact(0), RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
$ tea issue list --kind all --milestone v1.0 | cut -f1,2`, Args: argsExact(0), RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
var err error
|
||||
if opts.Since, opts.Before, err = updated.parse(a.deps.Now()); err != nil {
|
||||
return err
|
||||
@@ -194,13 +192,9 @@ func (a *application) issueCreateCommand() *cobra.Command {
|
||||
var useEditor, web bool
|
||||
var assignees, labelNames, attachmentPaths []string
|
||||
cmd := &cobra.Command{Use: "create", Aliases: []string{"new"}, Short: "Create an issue", Long: `Create an issue in a repository. Without flags on a terminal, a form asks for the title and
|
||||
body, then optionally for assignees, a milestone, labels and a due date. The new issue's URL
|
||||
is printed, so scripts can capture it.`, Example: `$ tea issue create
|
||||
body, then optionally for assignees, a milestone, labels and a due date.`, Example: `$ tea issue create
|
||||
$ tea issue create --title "Crash on start" --body "Steps to reproduce…" --label bug --milestone v1.0
|
||||
$ tea issue create -t "Update docs" -F notes.md -a @me
|
||||
# Start from a repository issue template, or continue in the browser
|
||||
$ tea issue create --template "Bug report"
|
||||
$ tea issue create --title "Idea" --web`, Args: argsExact(0), RunE: func(cmd *cobra.Command, _ []string) (runErr error) {
|
||||
$ tea issue create --template "Bug report"`, Args: argsExact(0), RunE: func(cmd *cobra.Command, _ []string) (runErr error) {
|
||||
var draft terminal.Draft
|
||||
defer finishDraft(&draft, &runErr)
|
||||
if err := validateBodySources(cmd); err != nil {
|
||||
@@ -230,8 +224,7 @@ $ tea issue create --title "Idea" --web`, Args: argsExact(0), RunE: func(cmd *co
|
||||
return err
|
||||
}
|
||||
}
|
||||
prompt := a.canPrompt()
|
||||
if !localFlagsChanged(cmd) && prompt && a.deps.IssueForm != nil {
|
||||
if !localFlagsChanged(cmd) && a.canPrompt() && a.deps.IssueForm != nil {
|
||||
return a.createIssueInteractively(cmd)
|
||||
}
|
||||
var target target
|
||||
@@ -461,7 +454,6 @@ func milestoneFlag(cmd *cobra.Command, value string) (selected string, remove bo
|
||||
return strings.TrimSpace(value), false, nil
|
||||
}
|
||||
|
||||
// milestoneID resolves a milestone title or ID in the target repository.
|
||||
func milestoneID(cmd *cobra.Command, t target, raw string) (int64, error) {
|
||||
milestone, err := (services.Milestones{Client: t.client, Repository: t.Repo}).FindOne(cmd.Context(), raw, false)
|
||||
return milestone.ID, err
|
||||
@@ -539,7 +531,6 @@ func (f *metadataFlags) register(cmd *cobra.Command) {
|
||||
flags.StringSliceVar(&f.setAssignees, "set-assignees", nil, "Replace all assignees; an empty value clears them")
|
||||
}
|
||||
|
||||
// completeMetadataFlags completes the label and assignee flags of metadataFlags.
|
||||
func (a *application) completeMetadataFlags(cmd *cobra.Command) {
|
||||
for _, name := range []string{"add-label", "remove-label", "set-labels"} {
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, a.completeLabels())
|
||||
|
||||
+7
-20
@@ -23,7 +23,6 @@ type labelScope func(*cobra.Command) (services.Labels, error)
|
||||
// labelColors are the colors of new labels created without --color, from Gitea's default labels.
|
||||
var labelColors = []string{"ee0701", "fbca04", "84b6eb", "cc317c", "c2e0c6", "0e8a16", "1d76db", "5319e7", "d4c5f9", "e99695", "bfd4f2", "006b75"}
|
||||
|
||||
// labelArguments is the ARGUMENTS help of commands that take labels.
|
||||
const labelArguments = `A label can be supplied by its name, such as "bug" or "kind/feature", or by its ID. A
|
||||
number is an ID first, then a name.`
|
||||
|
||||
@@ -90,7 +89,6 @@ func labelName(s services.Labels, item services.Label) string {
|
||||
return fmt.Sprintf("label %q in %s", item.Name, s.Scope())
|
||||
}
|
||||
|
||||
// labelFields are the fields of label create and edit.
|
||||
type labelFields struct {
|
||||
name, color, description string
|
||||
exclusive, archived bool
|
||||
@@ -184,7 +182,12 @@ $ tea label create bug -c ee0701 --force`, Args: usageArgs(cobra.MaximumNArgs(1)
|
||||
if err != nil {
|
||||
return forceHint(err)
|
||||
}
|
||||
return a.reportLabel(cmd, s, item, updated)
|
||||
verb := "Created"
|
||||
if updated {
|
||||
verb = "Updated"
|
||||
}
|
||||
a.done(cmd, "%s %s", verb, labelName(s, item))
|
||||
return a.jsonResult(cmd, item)
|
||||
}}
|
||||
fields.register(cmd, "Label `name`")
|
||||
_ = cmd.Flags().MarkHidden("name")
|
||||
@@ -228,16 +231,6 @@ func forceHint(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// reportLabel reports a created or updated label as JSON or with a ✓ line.
|
||||
func (a *application) reportLabel(cmd *cobra.Command, s services.Labels, item services.Label, updated bool) error {
|
||||
verb := "Created"
|
||||
if updated {
|
||||
verb = "Updated"
|
||||
}
|
||||
a.done(cmd, "%s %s", verb, labelName(s, item))
|
||||
return a.jsonResult(cmd, item)
|
||||
}
|
||||
|
||||
func (a *application) labelEditCommand(scope labelScope, complete cobra.CompletionFunc) *cobra.Command {
|
||||
var fields labelFields
|
||||
var byName bool
|
||||
@@ -317,7 +310,6 @@ $ tea label export --repo alice/old | tea label import - --repo alice/new`, Args
|
||||
return cmd
|
||||
}
|
||||
|
||||
// importLabels creates the labels of a label file, or of stdin for "-".
|
||||
func (a *application) importLabels(cmd *cobra.Command, scope labelScope, file string, force bool) error {
|
||||
data, err := readFileOrStdin(cmd.InOrStdin(), file)
|
||||
if err != nil {
|
||||
@@ -331,12 +323,7 @@ func (a *application) importLabels(cmd *cobra.Command, scope labelScope, file st
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.createLabels(cmd, s, rows, force)
|
||||
}
|
||||
|
||||
// createLabels creates each label in rows, continuing past failures. A label that exists already
|
||||
// is updated with force and fails otherwise.
|
||||
func (a *application) createLabels(cmd *cobra.Command, s services.Labels, rows []services.LabelOptions, force bool) error {
|
||||
// Each row is created, or updated with force, continuing past failures.
|
||||
existing, err := s.All(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// milestoneArguments is the ARGUMENTS help of commands that take milestones.
|
||||
const milestoneArguments = `A milestone can be supplied by its title, such as "v1.0", or by its ID. A number is an ID
|
||||
first, then a title; --by-name matches it as a title only.`
|
||||
|
||||
@@ -164,7 +163,6 @@ $ tea milestone view 3 --web`, Annotations: map[string]string{"tea.arguments": m
|
||||
return cmd
|
||||
}
|
||||
|
||||
// milestoneFields are the fields of milestone create and edit.
|
||||
type milestoneFields struct {
|
||||
title, description, descriptionFile, deadline string
|
||||
useEditor bool
|
||||
@@ -211,7 +209,6 @@ func (a *application) milestoneOptions(cmd *cobra.Command, f *milestoneFields) (
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// formOptions turns milestone form values into options.
|
||||
func formOptions(values terminal.MilestoneFormValues) services.MilestoneOptions {
|
||||
deadline := time.Time{}
|
||||
if values.Deadline != nil {
|
||||
@@ -225,7 +222,7 @@ func (a *application) milestoneCreateCommand(service milestoneService) *cobra.Co
|
||||
var fields milestoneFields
|
||||
var state string
|
||||
cmd := &cobra.Command{Use: "create [<title>]", Aliases: []string{"new"}, Short: "Create a milestone", Long: `Create a milestone. Without flags on a terminal, a form asks for the title, description
|
||||
and due date. The new milestone's URL is printed, so scripts can capture it.`, Example: `$ tea milestone create v1.0 --deadline 2026-12-31 --description "First release"
|
||||
and due date.`, Example: `$ tea milestone create v1.0 --deadline 2026-12-31 --description "First release"
|
||||
$ tea milestone create --title v2.0 --editor`, Args: usageArgs(cobra.MaximumNArgs(1)), RunE: func(cmd *cobra.Command, args []string) (runErr error) {
|
||||
if err := validateTextSources(cmd, "description", "description-file"); err != nil {
|
||||
return err
|
||||
@@ -238,12 +235,11 @@ $ tea milestone create --title v2.0 --editor`, Args: usageArgs(cobra.MaximumNArg
|
||||
return err
|
||||
}
|
||||
}
|
||||
prompt := a.canPrompt()
|
||||
var opts services.MilestoneOptions
|
||||
var draft terminal.Draft
|
||||
var err error
|
||||
defer finishDraft(&draft, &runErr)
|
||||
if !localFlagsChanged(cmd) && prompt && a.deps.MilestoneForm != nil {
|
||||
if !localFlagsChanged(cmd) && a.canPrompt() && a.deps.MilestoneForm != nil {
|
||||
values, err := a.deps.MilestoneForm.EditMilestone(cmd.Context(), "", terminal.MilestoneFormValues{})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -291,8 +287,7 @@ $ tea milestone edit 3 --editor`, Annotations: map[string]string{"tea.arguments"
|
||||
if err := validateTextSources(cmd, "description", "description-file"); err != nil {
|
||||
return err
|
||||
}
|
||||
prompt := a.canPrompt()
|
||||
form := !localFlagsChanged(cmd) && prompt && a.deps.MilestoneForm != nil
|
||||
form := !localFlagsChanged(cmd) && a.canPrompt() && a.deps.MilestoneForm != nil
|
||||
if !form && !localFlagsChanged(cmd) {
|
||||
return errNothingToEdit
|
||||
}
|
||||
@@ -370,7 +365,6 @@ $ tea milestone delete 7 --yes`, Annotations: map[string]string{"tea.confirm": "
|
||||
return cmd
|
||||
}
|
||||
|
||||
// findMilestone resolves a milestone argument, a title or an ID.
|
||||
func findMilestone(cmd *cobra.Command, s services.Milestones, 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"))
|
||||
|
||||
@@ -220,8 +220,7 @@ $ tea pr create
|
||||
# Take the title and body from the commits, without questions
|
||||
$ tea pr create --fill
|
||||
|
||||
$ tea pr create -t "Add login" -b "Closes #1" -B develop -r carol -l enhancement
|
||||
$ tea pr create --draft --fill`
|
||||
$ tea pr create -t "Add login" -b "Closes #1" -B develop -r carol -l enhancement`
|
||||
return create
|
||||
}
|
||||
|
||||
@@ -316,13 +315,14 @@ func (a *application) createPullInteractively(cmd *cobra.Command, snapshot setti
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
selectables, err := (services.Pulls{Client: target.client, Repository: target.Repo}).CreateSelectables(cmd.Context())
|
||||
service := services.Pulls{Client: target.client, Repository: target.Repo}
|
||||
selectables, err := service.CreateSelectables(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
baseOwner, _, _ := strings.Cut(target.Repo, "/")
|
||||
headOwner, headBranch := a.defaultPullHead(cmd.Context())
|
||||
template, err := (services.Pulls{Client: target.client, Repository: target.Repo}).Template(cmd.Context(), repo.DefaultBranch)
|
||||
template, err := service.Template(cmd.Context(), repo.DefaultBranch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -361,7 +361,7 @@ func (a *application) createPullInteractively(cmd *cobra.Command, snapshot setti
|
||||
if err := a.ensureHeadPublished(cmd, target, snapshot, options.Head); err != nil {
|
||||
return err
|
||||
}
|
||||
item, err := (services.Pulls{Client: target.client, Repository: target.Repo}).Create(cmd.Context(), options)
|
||||
item, err := service.Create(cmd.Context(), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@ func (a *application) addPullMutations(group *cobra.Command) {
|
||||
group.AddCommand(a.pullCreateCommand(), a.pullStateCommand(true), a.pullStateCommand(false), a.pullDiffCommand(), a.pullReviewCommand(), a.pullMergeCommand())
|
||||
}
|
||||
|
||||
// pullStateCommand builds tea pr close, or tea pr reopen when closing is false.
|
||||
func (a *application) pullStateCommand(closing bool) *cobra.Command {
|
||||
verb, done, state := "reopen", "Reopened", "open"
|
||||
if closing {
|
||||
@@ -252,7 +251,6 @@ var mergeVerbs = map[string]string{
|
||||
"merge": "Merged", "rebase": "Rebased and merged", "rebase-merge": "Rebased and merged", "squash": "Squashed and merged", "fast-forward-only": "Merged",
|
||||
}
|
||||
|
||||
// mergeMethods are the merge styles in Gitea's order.
|
||||
var mergeMethods = []string{"merge", "rebase", "rebase-merge", "squash", "fast-forward-only"}
|
||||
|
||||
func (a *application) pullMergeCommand() *cobra.Command {
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// pullArguments documents the pull request argument of pr commands in their ARGUMENTS section.
|
||||
const pullArguments = `A pull request can be supplied as argument in any of the following formats:
|
||||
- by number, such as "13" or "#13"
|
||||
- by URL, such as "https://gitea.com/OWNER/REPO/pulls/13"
|
||||
@@ -27,7 +26,6 @@ const pullArguments = `A pull request can be supplied as argument in any of the
|
||||
|
||||
Without an argument, the pull request of the current branch is selected.`
|
||||
|
||||
// pullArgumentsAnnotation adds the ARGUMENTS section to a pr command's help.
|
||||
func pullArgumentsAnnotation(annotations map[string]string) map[string]string {
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
@@ -43,7 +41,6 @@ var reviewStates = map[string]string{
|
||||
"comment": "COMMENT", "pending": "PENDING",
|
||||
}
|
||||
|
||||
// reviewType holds the flags that choose the type of a review.
|
||||
type reviewType struct {
|
||||
approve, requestChanges, comment, pending bool
|
||||
// event is the hidden spelling of the type that tea 0.x and scripts use.
|
||||
@@ -138,12 +135,6 @@ $ tea pr review --approve
|
||||
# Request changes on #13
|
||||
$ tea pr review 13 -r -b "Please hash the password"
|
||||
|
||||
# Comment with a body read from a file
|
||||
$ tea pr review 13 -c -F notes.md
|
||||
|
||||
# Review interactively: pick the type, write the body, optionally annotate the diff
|
||||
$ tea pr review 13
|
||||
|
||||
# Start a pending review with inline comments, then submit it
|
||||
$ tea pr review 13 --pending --comments-file inline.json
|
||||
$ tea pr review submit 13 --approve`
|
||||
@@ -152,9 +143,8 @@ func (a *application) pullReviewCommand() *cobra.Command {
|
||||
review := a.reviewCreateCommand("review [<number> | <url> | <branch>]", "Review a pull request or manage its reviews")
|
||||
review.Long = `Submit a review on a pull request, or manage existing reviews with the commands below.
|
||||
|
||||
Without an argument, the pull request of the current branch is reviewed. Choose the review
|
||||
type with --approve, --request-changes or --comment. On a terminal, leaving the type out
|
||||
opens an interactive review that also offers to annotate the diff in your editor.`
|
||||
Choose the review type with --approve, --request-changes or --comment. On a terminal, leaving
|
||||
the type out opens an interactive review that also offers to annotate the diff in your editor.`
|
||||
review.Example = reviewExamples
|
||||
review.SuggestionsMinimumDistance = 2
|
||||
create := a.reviewCreateCommand("create [<number> | <url> | <branch>]", `Submit a review (same as "tea pr review")`)
|
||||
@@ -169,7 +159,6 @@ $ tea pr review create --comment -b "Looks good overall"`
|
||||
return review
|
||||
}
|
||||
|
||||
// reviewCreateCommand builds tea pr review and its explicit form tea pr review create.
|
||||
func (a *application) reviewCreateCommand(use, short string) *cobra.Command {
|
||||
var kind reviewType
|
||||
var body, file, commentsFile, commit string
|
||||
@@ -263,7 +252,6 @@ func (a *application) reportReview(cmd *cobra.Command, t target, pull services.P
|
||||
return a.created(cmd, item, item.URL)
|
||||
}
|
||||
|
||||
// readInlineComments reads the inline comments of --comments-file, a JSON array.
|
||||
func readInlineComments(path string) ([]services.InlineComment, error) {
|
||||
if path == "" {
|
||||
return nil, nil
|
||||
@@ -296,7 +284,6 @@ func readInlineComments(path string) ([]services.InlineComment, error) {
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
// reviewInteractively asks for the review type, body and inline comments, then submits the review.
|
||||
func (a *application) reviewInteractively(cmd *cobra.Command, args []string) error {
|
||||
t, n, err := a.pullSelector(cmd, args)
|
||||
if err != nil {
|
||||
|
||||
@@ -23,8 +23,6 @@ one select the pull request of the current branch.`}
|
||||
group.Annotations = pullArgumentsAnnotation(nil)
|
||||
group.Example = `$ tea pr list --author @me
|
||||
$ tea pr create --fill
|
||||
$ tea pr checkout 13
|
||||
$ tea pr review --approve
|
||||
$ tea pr merge --squash --delete-branch`
|
||||
group.AddCommand(a.pullChecksCommand(), a.pullCheckoutCommand(), a.pullCleanCommand(), a.statusCommand(true))
|
||||
group.AddCommand(a.pullListCommand(), a.pullViewCommand())
|
||||
@@ -88,9 +86,8 @@ each pull request, which tea only makes when --json names them.`, Args: argsExac
|
||||
if localFlagsChanged(cmd, "state", "limit", "page", "sort") {
|
||||
empty = "no " + noun + " match your filters in " + t.Repo
|
||||
}
|
||||
columns := []terminal.Column{{Header: "ID", Prefix: "#"}, {Header: "Title", Flex: true}, {Header: "Branch", Flex: true, Min: 12}, {Header: "State", PipedOnly: state == "open"}, {Header: "Author"}, {Header: "Created"}}
|
||||
return writeTable(a, cmd, items, listSpec[services.Pull]{
|
||||
Columns: columns,
|
||||
Columns: []terminal.Column{{Header: "ID", Prefix: "#"}, {Header: "Title", Flex: true}, {Header: "Branch", Flex: true, Min: 12}, {Header: "State", PipedOnly: state == "open"}, {Header: "Author"}, {Header: "Created"}},
|
||||
Row: func(item services.Pull) []terminal.Cell {
|
||||
state := pullState(item)
|
||||
return []terminal.Cell{
|
||||
@@ -125,8 +122,6 @@ each pull request, which tea only makes when --json names them.`, Args: argsExac
|
||||
_ = cmd.RegisterFlagCompletionFunc("milestone", a.completeMilestones())
|
||||
cmd.Example = `$ tea pr list
|
||||
$ tea pr list --author @me --state all
|
||||
$ tea pr list -l bug -B main --draft
|
||||
$ tea pr list --state merged -L 10
|
||||
$ tea pr list --json number,title,head --jq '.[] | select(.head == "feature/login")'`
|
||||
return cmd
|
||||
}
|
||||
@@ -180,7 +175,6 @@ $ tea pr view
|
||||
# View #13 with its comments and reviews
|
||||
$ tea pr view 13 --comments
|
||||
|
||||
$ tea pr view https://gitea.com/owner/repo/pulls/13 --web
|
||||
$ tea pr view feature/login --json number,state,review_decision`
|
||||
return cmd
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user