mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
FormPrompter is the only production prompter and implements the rich prompt API, so Ask, AskText and ConfirmDefault become the Prompter interface and the plain Input/Confirm fallbacks go away. Confirmations that default to no derive their non-terminal wording from the prompt, and single deletes share deleteConfirmed for the prompt, the request and the done line. Signed-off-by: bircni <bircni@icloud.com>
214 lines
7.3 KiB
Go
214 lines
7.3 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package app
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gitea.dev/tea/internal/clierr"
|
|
"gitea.dev/tea/internal/services"
|
|
"gitea.dev/tea/internal/terminal"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func (a *application) pullReviewSubmitCommand() *cobra.Command {
|
|
var kind reviewType
|
|
var body, file string
|
|
var useEditor bool
|
|
cmd := &cobra.Command{Use: "submit [<number> | <url> | <branch>] [<review-id>]", Short: "Submit your pending review", Annotations: pullArgumentsAnnotation(nil), Args: usageArgs(cobra.MaximumNArgs(2)), 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
|
|
}
|
|
state, err := kind.state()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if state == "" {
|
|
return errReviewType(cmd)
|
|
}
|
|
missingBody := clierr.Wrap(clierr.Usage, errors.New("a change request needs a body; pass --body, --body-file or --editor"))
|
|
var supplied *string
|
|
if cmd.Flags().Changed("body") || cmd.Flags().Changed("body-file") {
|
|
if body, err = a.readBody(cmd, body, file); err != nil {
|
|
return err
|
|
}
|
|
if state == "REQUEST_CHANGES" && strings.TrimSpace(body) == "" {
|
|
return missingBody
|
|
}
|
|
supplied = &body
|
|
}
|
|
var id int64
|
|
if len(args) == 2 {
|
|
if id, err = number(args[1]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
t, n, err := a.pullSelector(cmd, args[:min(len(args), 1)])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pull, err := (services.Pulls{Client: t.client, Repository: t.Repo}).View(cmd.Context(), n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := ownReview(pull, t, state); err != nil {
|
|
return err
|
|
}
|
|
service := services.Reviews{Client: t.client, Repository: t.Repo}
|
|
if id == 0 {
|
|
if id, err = a.pendingReview(cmd, service, t, n, state); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if useEditor || (supplied == nil && state == "REQUEST_CHANGES") {
|
|
current, err := service.View(cmd.Context(), n, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
supplied = ¤t.Body
|
|
if useEditor {
|
|
if draft, err = a.editDraft(cmd, current.Body); err != nil {
|
|
return err
|
|
}
|
|
supplied = &draft.Body
|
|
}
|
|
}
|
|
if state == "REQUEST_CHANGES" && strings.TrimSpace(*supplied) == "" {
|
|
return missingBody
|
|
}
|
|
item, err := service.Submit(cmd.Context(), n, id, services.ReviewSubmitOptions{Event: state, Body: supplied})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return a.reportReview(cmd, t, pull, item)
|
|
}}
|
|
kind.register(cmd, false)
|
|
cmd.Flags().StringVarP(&body, "body", "b", "", "Review body (default: the body of the pending review)")
|
|
cmd.Flags().StringVarP(&file, "body-file", "F", "", "Read the body from `file`, or - for stdin")
|
|
cmd.Flags().BoolVarP(&useEditor, "editor", "e", false, "Edit the body of the pending review in your editor")
|
|
cmd.Example = `# Start a pending review, then submit it as an approval
|
|
$ tea pr review 13 --pending -b "First pass" --comments-file inline.json
|
|
$ tea pr review submit 13 --approve
|
|
|
|
# Submit a pending review by its ID
|
|
$ tea pr review submit 13 21 --request-changes -b "See the inline comments"`
|
|
cmd.ValidArgsFunction = a.completeReviewArgs(false)
|
|
return cmd
|
|
}
|
|
|
|
// pendingReview finds the pending review that the account started on pull request n.
|
|
func (a *application) pendingReview(cmd *cobra.Command, service services.Reviews, t target, n int64, state string) (int64, error) {
|
|
pending, err := service.Pending(cmd.Context(), n, t.login())
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
switch len(pending) {
|
|
case 0:
|
|
flag := map[string]string{"APPROVED": "--approve", "REQUEST_CHANGES": "--request-changes", "COMMENT": "--comment"}[state]
|
|
return 0, fmt.Errorf("no pending review by %s on %s#%d; to post a new review run: tea pr review %d %s", t.login(), t.Repo, n, n, flag)
|
|
case 1:
|
|
return pending[0].ID, nil
|
|
}
|
|
ids := make([]string, len(pending))
|
|
for i, review := range pending {
|
|
ids[i] = strconv.FormatInt(review.ID, 10)
|
|
}
|
|
return 0, clierr.Wrap(clierr.Usage, fmt.Errorf("%s has several pending reviews on %s#%d (%s); pass a review ID", t.login(), t.Repo, n, strings.Join(ids, ", ")))
|
|
}
|
|
|
|
func (a *application) pullReviewDeleteCommand() *cobra.Command {
|
|
cmd := &cobra.Command{Use: "delete <number> <review-id>", Short: "Delete a review", Annotations: pullArgumentsAnnotation(textOutput("review deletion does not return a resource")), Args: argsExact(2), RunE: func(cmd *cobra.Command, args []string) error {
|
|
t, n, id, err := a.pullAndID(cmd, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
service := services.Reviews{Client: t.client, Repository: t.Repo}
|
|
item, err := service.View(cmd.Context(), n, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
subject := fmt.Sprintf("review %d by %s on %s#%d", id, item.Author, t.Repo, n)
|
|
return a.deleteConfirmed(cmd, subject, func() error { return service.Delete(cmd.Context(), n, id) })
|
|
}}
|
|
cmd.Annotations["tea.confirm"] = "true"
|
|
cmd.Example = "$ tea pr review delete 13 21"
|
|
cmd.ValidArgsFunction = a.completeReviewArgs(false)
|
|
return cmd
|
|
}
|
|
|
|
func (a *application) pullReviewDismissCommand(dismiss bool) *cobra.Command {
|
|
var body, file string
|
|
var useEditor bool
|
|
use, short := "undismiss <number> <review-id>", "Restore a dismissed review"
|
|
if dismiss {
|
|
use, short = "dismiss <number> <review-id>", "Dismiss an approval or change request"
|
|
}
|
|
cmd := &cobra.Command{Use: use, Short: short, Annotations: pullArgumentsAnnotation(nil), Args: argsExact(2), RunE: func(cmd *cobra.Command, args []string) (runErr error) {
|
|
var draft terminal.Draft
|
|
defer finishDraft(&draft, &runErr)
|
|
var err error
|
|
if dismiss {
|
|
if err := validateBodySources(cmd); err != nil {
|
|
return err
|
|
}
|
|
switch {
|
|
case useEditor:
|
|
draft, err = a.editDraft(cmd, "")
|
|
body = draft.Body
|
|
case cmd.Flags().Changed("body") || cmd.Flags().Changed("body-file"):
|
|
body, err = a.readBody(cmd, body, file)
|
|
default:
|
|
_, err = a.requireInput(cmd, &body, "body", "Reason for dismissing the review", nil)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.TrimSpace(body) == "" {
|
|
return clierr.Wrap(clierr.Usage, errors.New("a dismissal needs a reason; pass --body, --body-file or --editor"))
|
|
}
|
|
}
|
|
t, n, id, err := a.pullAndID(cmd, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
service := services.Reviews{Client: t.client, Repository: t.Repo}
|
|
current, err := service.View(cmd.Context(), n, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
subject := fmt.Sprintf("%s on %s#%d", reviewPossessive(current), t.Repo, n)
|
|
if dismiss {
|
|
if err := a.confirmAction(cmd, terminal.Confirmation{Prompt: "Dismiss " + subject + "?"}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
item, err := service.Dismiss(cmd.Context(), n, id, dismiss, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if dismiss {
|
|
a.done(cmd, "Dismissed %s", subject)
|
|
} else {
|
|
a.done(cmd, "Restored %s", subject)
|
|
}
|
|
return a.jsonResult(cmd, item)
|
|
}}
|
|
if dismiss {
|
|
cmd.Annotations["tea.confirm"] = "true"
|
|
cmd.Flags().StringVarP(&body, "body", "b", "", "Reason for dismissing the review (required)")
|
|
cmd.Flags().StringVarP(&file, "body-file", "F", "", "Read the reason from `file`, or - for stdin")
|
|
cmd.Flags().BoolVarP(&useEditor, "editor", "e", false, "Write the reason in your editor")
|
|
cmd.Example = `$ tea pr review dismiss 13 6 -b "Approved an outdated revision"`
|
|
} else {
|
|
cmd.Example = "$ tea pr review undismiss 13 6"
|
|
}
|
|
cmd.ValidArgsFunction = a.completeReviewArgs(false)
|
|
return cmd
|
|
}
|