From bdf15a57bea15901bcd149aa152da68c2117db03 Mon Sep 17 00:00:00 2001 From: Alain Thiffault Date: Thu, 19 Feb 2026 15:16:21 +0000 Subject: [PATCH] feat(pulls): add JSON output support for single PR view (#864) Reviewed-on: https://gitea.com/gitea/tea/pulls/864 Reviewed-by: Lunny Xiao Co-authored-by: Alain Thiffault Co-committed-by: Alain Thiffault --- cmd/pulls.go | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/cmd/pulls.go b/cmd/pulls.go index e051928..7318246 100644 --- a/cmd/pulls.go +++ b/cmd/pulls.go @@ -5,18 +5,67 @@ package cmd import ( stdctx "context" + "encoding/json" "fmt" + "time" + "code.gitea.io/sdk/gitea" + "code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/pulls" "code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/interact" "code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/utils" - "code.gitea.io/sdk/gitea" "github.com/urfave/cli/v3" ) +type pullLabelData struct { + Name string `json:"name"` + Color string `json:"color"` + Description string `json:"description"` +} + +type pullReviewData struct { + ID int64 `json:"id"` + Reviewer string `json:"reviewer"` + State gitea.ReviewStateType `json:"state"` + Body string `json:"body"` + Created time.Time `json:"created"` +} + +type pullCommentData struct { + ID int64 `json:"id"` + Author string `json:"author"` + Created time.Time `json:"created"` + Body string `json:"body"` +} + +type pullData struct { + ID int64 `json:"id"` + Index int64 `json:"index"` + Title string `json:"title"` + State gitea.StateType `json:"state"` + Created *time.Time `json:"created"` + Updated *time.Time `json:"updated"` + Labels []pullLabelData `json:"labels"` + User string `json:"user"` + Body string `json:"body"` + Assignees []string `json:"assignees"` + URL string `json:"url"` + Base string `json:"base"` + Head string `json:"head"` + HeadSha string `json:"headSha"` + DiffURL string `json:"diffUrl"` + Mergeable bool `json:"mergeable"` + HasMerged bool `json:"hasMerged"` + MergedAt *time.Time `json:"mergedAt"` + MergedBy string `json:"mergedBy,omitempty"` + ClosedAt *time.Time `json:"closedAt"` + Reviews []pullReviewData `json:"reviews"` + Comments []pullCommentData `json:"comments"` +} + // CmdPulls is the main command to operate on PRs var CmdPulls = cli.Command{ Name: "pulls", @@ -74,6 +123,13 @@ func runPullDetail(_ stdctx.Context, cmd *cli.Command, index string) error { fmt.Printf("error while loading reviews: %v\n", err) } + if ctx.IsSet("output") { + switch ctx.String("output") { + case "json": + return runPullDetailAsJSON(ctx, pr, reviews) + } + } + ci, _, err := client.GetCombinedStatus(ctx.Owner, ctx.Repo, pr.Head.Sha) if err != nil { fmt.Printf("error while loading CI: %v\n", err) @@ -90,3 +146,85 @@ func runPullDetail(_ stdctx.Context, cmd *cli.Command, index string) error { return nil } + +func runPullDetailAsJSON(ctx *context.TeaContext, pr *gitea.PullRequest, reviews []*gitea.PullReview) error { + c := ctx.Login.Client() + opts := gitea.ListIssueCommentOptions{ListOptions: flags.GetListOptions()} + + labelSlice := make([]pullLabelData, 0, len(pr.Labels)) + for _, label := range pr.Labels { + labelSlice = append(labelSlice, pullLabelData{label.Name, label.Color, label.Description}) + } + + assigneesSlice := make([]string, 0, len(pr.Assignees)) + for _, assignee := range pr.Assignees { + assigneesSlice = append(assigneesSlice, assignee.UserName) + } + + reviewsSlice := make([]pullReviewData, 0, len(reviews)) + for _, review := range reviews { + reviewsSlice = append(reviewsSlice, pullReviewData{ + ID: review.ID, + Reviewer: review.Reviewer.UserName, + State: review.State, + Body: review.Body, + Created: review.Submitted, + }) + } + + mergedBy := "" + if pr.MergedBy != nil { + mergedBy = pr.MergedBy.UserName + } + + pullSlice := pullData{ + ID: pr.ID, + Index: pr.Index, + Title: pr.Title, + State: pr.State, + Created: pr.Created, + Updated: pr.Updated, + User: pr.Poster.UserName, + Body: pr.Body, + Labels: labelSlice, + Assignees: assigneesSlice, + URL: pr.HTMLURL, + Base: pr.Base.Ref, + Head: pr.Head.Ref, + HeadSha: pr.Head.Sha, + DiffURL: pr.DiffURL, + Mergeable: pr.Mergeable, + HasMerged: pr.HasMerged, + MergedAt: pr.Merged, + MergedBy: mergedBy, + ClosedAt: pr.Closed, + Reviews: reviewsSlice, + Comments: make([]pullCommentData, 0), + } + + if ctx.Bool("comments") { + comments, _, err := c.ListIssueComments(ctx.Owner, ctx.Repo, pr.Index, opts) + if err != nil { + return err + } + + pullSlice.Comments = make([]pullCommentData, 0, len(comments)) + for _, comment := range comments { + pullSlice.Comments = append(pullSlice.Comments, pullCommentData{ + ID: comment.ID, + Author: comment.Poster.UserName, + Body: comment.Body, + Created: comment.Created, + }) + } + } + + jsonData, err := json.MarshalIndent(pullSlice, "", "\t") + if err != nil { + return err + } + + _, err = fmt.Fprintf(ctx.Writer, "%s\n", jsonData) + + return err +}