From 662e339bf9eac80700206c4fa5ec44686c139c5b Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 8 Apr 2026 03:36:09 +0000 Subject: [PATCH] feat(pulls): add resolve, unresolve and review-comments subcommands (#948) ## Summary - Add `tea pulls review-comments ` subcommand to list PR review comments with configurable fields (supports table/json/csv/yaml output) - Add `tea pulls resolve ` subcommand to mark a review comment as resolved - Add `tea pulls unresolve ` subcommand to unmark a review comment as resolved - Follow existing approve/reject pattern with shared `runResolveComment` helper in `review_helpers.go` ## Usage ```bash # List review comments for PR #42 tea pulls review-comments 42 # Resolve comment #789 tea pulls resolve 789 # Unresolve comment #789 tea pulls unresolve 789 # Custom output fields tea pulls review-comments 42 --fields id,path,body,resolver --output json ``` ## New Files | File | Description | |------|-------------| | `cmd/pulls/review_comments.go` | `review-comments` subcommand | | `cmd/pulls/resolve.go` | `resolve` subcommand | | `cmd/pulls/unresolve.go` | `unresolve` subcommand | | `modules/task/pull_review_comment.go` | Task layer: list, resolve, unresolve via SDK | | `modules/print/pull_review_comment.go` | Print formatting with `printable` interface | ## Modified Files | File | Description | |------|-------------| | `cmd/pulls.go` | Register 3 new commands | | `cmd/pulls/review_helpers.go` | Add shared `runResolveComment` helper | ## Test Plan - [x] `go build ./...` passes - [x] `go vet ./...` passes - [x] `tea pulls review-comments ` lists comments with IDs - [x] `tea pulls resolve ` resolves successfully - [x] `tea pulls unresolve ` unresolves successfully - [x] `--output json` produces valid JSON output Reviewed-on: https://gitea.com/gitea/tea/pulls/948 Reviewed-by: Lunny Xiao Co-authored-by: Bo-Yi Wu Co-committed-by: Bo-Yi Wu --- cmd/pulls.go | 3 ++ cmd/pulls/resolve.go | 30 ++++++++++++ cmd/pulls/review_comments.go | 63 ++++++++++++++++++++++++ cmd/pulls/review_helpers.go | 18 +++++++ cmd/pulls/unresolve.go | 30 ++++++++++++ docs/CLI.md | 40 +++++++++++++++ modules/print/pull_review_comment.go | 73 ++++++++++++++++++++++++++++ modules/task/pull_review_comment.go | 60 +++++++++++++++++++++++ 8 files changed, 317 insertions(+) create mode 100644 cmd/pulls/resolve.go create mode 100644 cmd/pulls/review_comments.go create mode 100644 cmd/pulls/unresolve.go create mode 100644 modules/print/pull_review_comment.go create mode 100644 modules/task/pull_review_comment.go diff --git a/cmd/pulls.go b/cmd/pulls.go index 58d6c57..cb49bb3 100644 --- a/cmd/pulls.go +++ b/cmd/pulls.go @@ -77,6 +77,9 @@ var CmdPulls = cli.Command{ &pulls.CmdPullsApprove, &pulls.CmdPullsReject, &pulls.CmdPullsMerge, + &pulls.CmdPullsReviewComments, + &pulls.CmdPullsResolve, + &pulls.CmdPullsUnresolve, }, } diff --git a/cmd/pulls/resolve.go b/cmd/pulls/resolve.go new file mode 100644 index 0000000..6be0028 --- /dev/null +++ b/cmd/pulls/resolve.go @@ -0,0 +1,30 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package pulls + +import ( + stdctx "context" + + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/task" + + "github.com/urfave/cli/v3" +) + +// CmdPullsResolve resolves a review comment on a pull request +var CmdPullsResolve = cli.Command{ + Name: "resolve", + Usage: "Resolve a review comment on a pull request", + Description: "Resolve a review comment on a pull request", + ArgsUsage: "", + Action: func(_ stdctx.Context, cmd *cli.Command) error { + ctx, err := context.InitCommand(cmd) + if err != nil { + return err + } + return runResolveComment(ctx, task.ResolvePullReviewComment) + }, + Flags: flags.AllDefaultFlags, +} diff --git a/cmd/pulls/review_comments.go b/cmd/pulls/review_comments.go new file mode 100644 index 0000000..63b19f2 --- /dev/null +++ b/cmd/pulls/review_comments.go @@ -0,0 +1,63 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package pulls + +import ( + stdctx "context" + "fmt" + + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/print" + "code.gitea.io/tea/modules/task" + "code.gitea.io/tea/modules/utils" + + "github.com/urfave/cli/v3" +) + +var reviewCommentFieldsFlag = flags.FieldsFlag(print.PullReviewCommentFields, []string{ + "id", "path", "line", "body", "reviewer", "resolver", +}) + +// CmdPullsReviewComments lists review comments on a pull request +var CmdPullsReviewComments = cli.Command{ + Name: "review-comments", + Aliases: []string{"rc"}, + Usage: "List review comments on a pull request", + Description: "List review comments on a pull request", + ArgsUsage: "", + Action: runPullsReviewComments, + Flags: append([]cli.Flag{reviewCommentFieldsFlag}, flags.AllDefaultFlags...), +} + +func runPullsReviewComments(_ stdctx.Context, cmd *cli.Command) error { + ctx, err := context.InitCommand(cmd) + if err != nil { + return err + } + if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil { + return err + } + + if ctx.Args().Len() < 1 { + return fmt.Errorf("pull request index is required") + } + + idx, err := utils.ArgToIndex(ctx.Args().First()) + if err != nil { + return err + } + + comments, err := task.ListPullReviewComments(ctx, idx) + if err != nil { + return err + } + + fields, err := reviewCommentFieldsFlag.GetValues(cmd) + if err != nil { + return err + } + + return print.PullReviewCommentsList(comments, ctx.Output, fields) +} diff --git a/cmd/pulls/review_helpers.go b/cmd/pulls/review_helpers.go index c21f84c..ba844a6 100644 --- a/cmd/pulls/review_helpers.go +++ b/cmd/pulls/review_helpers.go @@ -40,3 +40,21 @@ func runPullReview(ctx *context.TeaContext, state gitea.ReviewStateType, require return task.CreatePullReview(ctx, idx, state, comment, nil) } + +// runResolveComment handles the common logic for resolving/unresolving review comments +func runResolveComment(ctx *context.TeaContext, action func(*context.TeaContext, int64) error) error { + if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil { + return err + } + + if ctx.Args().Len() < 1 { + return fmt.Errorf("comment ID is required") + } + + commentID, err := utils.ArgToIndex(ctx.Args().First()) + if err != nil { + return err + } + + return action(ctx, commentID) +} diff --git a/cmd/pulls/unresolve.go b/cmd/pulls/unresolve.go new file mode 100644 index 0000000..1dea389 --- /dev/null +++ b/cmd/pulls/unresolve.go @@ -0,0 +1,30 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package pulls + +import ( + stdctx "context" + + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/task" + + "github.com/urfave/cli/v3" +) + +// CmdPullsUnresolve unresolves a review comment on a pull request +var CmdPullsUnresolve = cli.Command{ + Name: "unresolve", + Usage: "Unresolve a review comment on a pull request", + Description: "Unresolve a review comment on a pull request", + ArgsUsage: "", + Action: func(_ stdctx.Context, cmd *cli.Command) error { + ctx, err := context.InitCommand(cmd) + if err != nil { + return err + } + return runResolveComment(ctx, task.UnresolvePullReviewComment) + }, + Flags: flags.AllDefaultFlags, +} diff --git a/docs/CLI.md b/docs/CLI.md index 8d40cd1..bbfe584 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -483,6 +483,46 @@ Merge a pull request **--title, -t**="": Merge commit title +### review-comments, rc + +List review comments on a pull request + +**--fields, -f**="": Comma-separated list of fields to print. Available values: + id,body,reviewer,path,line,resolver,created,updated,url + (default: "id,path,line,body,reviewer,resolver") + +**--login, -l**="": Use a different Gitea Login. Optional + +**--output, -o**="": Output format. (simple, table, csv, tsv, yaml, json) + +**--remote, -R**="": Discover Gitea login from remote. Optional + +**--repo, -r**="": Override local repository path or gitea repository slug to interact with. Optional + +### resolve + +Resolve a review comment on a pull request + +**--login, -l**="": Use a different Gitea Login. Optional + +**--output, -o**="": Output format. (simple, table, csv, tsv, yaml, json) + +**--remote, -R**="": Discover Gitea login from remote. Optional + +**--repo, -r**="": Override local repository path or gitea repository slug to interact with. Optional + +### unresolve + +Unresolve a review comment on a pull request + +**--login, -l**="": Use a different Gitea Login. Optional + +**--output, -o**="": Output format. (simple, table, csv, tsv, yaml, json) + +**--remote, -R**="": Discover Gitea login from remote. Optional + +**--repo, -r**="": Override local repository path or gitea repository slug to interact with. Optional + ## labels, label Manage issue labels diff --git a/modules/print/pull_review_comment.go b/modules/print/pull_review_comment.go new file mode 100644 index 0000000..738d7ee --- /dev/null +++ b/modules/print/pull_review_comment.go @@ -0,0 +1,73 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package print + +import ( + "fmt" + + "code.gitea.io/sdk/gitea" +) + +// PullReviewCommentFields are all available fields to print with PullReviewCommentsList() +var PullReviewCommentFields = []string{ + "id", + "body", + "reviewer", + "path", + "line", + "resolver", + "created", + "updated", + "url", +} + +// PullReviewCommentsList prints a listing of pull review comments +func PullReviewCommentsList(comments []*gitea.PullReviewComment, output string, fields []string) error { + printables := make([]printable, len(comments)) + for i, c := range comments { + printables[i] = &printablePullReviewComment{c} + } + t := tableFromItems(fields, printables, isMachineReadable(output)) + return t.print(output) +} + +type printablePullReviewComment struct { + *gitea.PullReviewComment +} + +func (x printablePullReviewComment) FormatField(field string, machineReadable bool) string { + switch field { + case "id": + return fmt.Sprintf("%d", x.ID) + case "body": + return x.Body + case "reviewer": + if x.Reviewer != nil { + return formatUserName(x.Reviewer) + } + return "" + case "path": + return x.Path + case "line": + if x.LineNum != 0 { + return fmt.Sprintf("%d", x.LineNum) + } + if x.OldLineNum != 0 { + return fmt.Sprintf("%d", x.OldLineNum) + } + return "" + case "resolver": + if x.Resolver != nil { + return formatUserName(x.Resolver) + } + return "" + case "created": + return FormatTime(x.Created, machineReadable) + case "updated": + return FormatTime(x.Updated, machineReadable) + case "url": + return x.HTMLURL + } + return "" +} diff --git a/modules/task/pull_review_comment.go b/modules/task/pull_review_comment.go new file mode 100644 index 0000000..68eaf78 --- /dev/null +++ b/modules/task/pull_review_comment.go @@ -0,0 +1,60 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package task + +import ( + "fmt" + + "code.gitea.io/sdk/gitea" + "code.gitea.io/tea/modules/context" +) + +// ListPullReviewComments lists all review comments across all reviews for a PR +func ListPullReviewComments(ctx *context.TeaContext, idx int64) ([]*gitea.PullReviewComment, error) { + c := ctx.Login.Client() + + reviews, _, err := c.ListPullReviews(ctx.Owner, ctx.Repo, idx, gitea.ListPullReviewsOptions{ + ListOptions: gitea.ListOptions{Page: -1}, + }) + if err != nil { + return nil, err + } + + var allComments []*gitea.PullReviewComment + for _, review := range reviews { + comments, _, err := c.ListPullReviewComments(ctx.Owner, ctx.Repo, idx, review.ID) + if err != nil { + return nil, err + } + allComments = append(allComments, comments...) + } + + return allComments, nil +} + +// ResolvePullReviewComment resolves a review comment +func ResolvePullReviewComment(ctx *context.TeaContext, commentID int64) error { + c := ctx.Login.Client() + + _, err := c.ResolvePullReviewComment(ctx.Owner, ctx.Repo, commentID) + if err != nil { + return err + } + + fmt.Printf("Comment %d resolved\n", commentID) + return nil +} + +// UnresolvePullReviewComment unresolves a review comment +func UnresolvePullReviewComment(ctx *context.TeaContext, commentID int64) error { + c := ctx.Login.Client() + + _, err := c.UnresolvePullReviewComment(ctx.Owner, ctx.Repo, commentID) + if err != nil { + return err + } + + fmt.Printf("Comment %d unresolved\n", commentID) + return nil +}