From 2826726557f3d42a33623afecec5654d8a2d2606 Mon Sep 17 00:00:00 2001 From: bircni Date: Mon, 12 Oct 2026 20:45:02 +0200 Subject: [PATCH] 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 --- internal/app/checkout.go | 9 ++--- internal/app/comments.go | 56 +++++++++---------------------- internal/app/discussion_search.go | 1 - internal/app/discussion_view.go | 5 +-- internal/app/issue_attachments.go | 32 ++++++++---------- internal/app/issue_edit.go | 8 ++--- internal/app/issues.go | 17 +++------- internal/app/labels.go | 27 ++++----------- internal/app/metadata.go | 12 ++----- internal/app/pull_create.go | 10 +++--- internal/app/pull_mutations.go | 2 -- internal/app/pull_review.go | 17 ++-------- internal/app/pulls.go | 8 +---- 13 files changed, 57 insertions(+), 147 deletions(-) diff --git a/internal/app/checkout.go b/internal/app/checkout.go index 28584326..f89db34e 100644 --- a/internal/app/checkout.go +++ b/internal/app/checkout.go @@ -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 { | | }", 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 { | | }", 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 } diff --git a/internal/app/comments.go b/internal/app/comments.go index 4fd5e924..3e23eed5 100644 --- a/internal/app/comments.go +++ b/internal/app/comments.go @@ -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 ", 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 ", 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")) diff --git a/internal/app/discussion_search.go b/internal/app/discussion_search.go index 08487051..da769c22 100644 --- a/internal/app/discussion_search.go +++ b/internal/app/discussion_search.go @@ -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 { diff --git a/internal/app/discussion_view.go b/internal/app/discussion_view.go index 78f674b0..a65a09f0 100644 --- a/internal/app/discussion_view.go +++ b/internal/app/discussion_view.go @@ -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) }, } } diff --git a/internal/app/issue_attachments.go b/internal/app/issue_attachments.go index 3c274c06..c2821db7 100644 --- a/internal/app/issue_attachments.go +++ b/internal/app/issue_attachments.go @@ -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 { | } ", 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 { | } ", 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 -} diff --git a/internal/app/issue_edit.go b/internal/app/issue_edit.go index 5eaae88a..96875e3e 100644 --- a/internal/app/issue_edit.go +++ b/internal/app/issue_edit.go @@ -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) diff --git a/internal/app/issues.go b/internal/app/issues.go index 70261653..cd34cf3e 100644 --- a/internal/app/issues.go +++ b/internal/app/issues.go @@ -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()) diff --git a/internal/app/labels.go b/internal/app/labels.go index cb8e9453..e77509e9 100644 --- a/internal/app/labels.go +++ b/internal/app/labels.go @@ -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 diff --git a/internal/app/metadata.go b/internal/app/metadata.go index 0e3dcc30..547d49b8 100644 --- a/internal/app/metadata.go +++ b/internal/app/metadata.go @@ -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 []", 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")) diff --git a/internal/app/pull_create.go b/internal/app/pull_create.go index 37861f0d..cf74284c 100644 --- a/internal/app/pull_create.go +++ b/internal/app/pull_create.go @@ -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 } diff --git a/internal/app/pull_mutations.go b/internal/app/pull_mutations.go index 599de686..79be53c0 100644 --- a/internal/app/pull_mutations.go +++ b/internal/app/pull_mutations.go @@ -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 { diff --git a/internal/app/pull_review.go b/internal/app/pull_review.go index c40db618..7ce6049e 100644 --- a/internal/app/pull_review.go +++ b/internal/app/pull_review.go @@ -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 { diff --git a/internal/app/pulls.go b/internal/app/pulls.go index 0a0990c3..8cf81061 100644 --- a/internal/app/pulls.go +++ b/internal/app/pulls.go @@ -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 }