gitea-tea/modules/print/pull.go

259 lines
6.0 KiB
Go
Raw Permalink Normal View History

// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package print
import (
"fmt"
"strings"
"code.gitea.io/sdk/gitea"
)
var ciStatusSymbols = map[gitea.StatusState]string{
gitea.StatusSuccess: "✓ ",
gitea.StatusPending: "⭮ ",
gitea.StatusWarning: "⚠ ",
gitea.StatusError: "✘ ",
gitea.StatusFailure: "❌ ",
}
// PullDetails print an pull rendered to stdout
func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview, ciStatus *gitea.CombinedStatus) {
base := pr.Base.Name
head := formatPRHead(pr)
state := formatPRState(pr)
out := fmt.Sprintf(
"# #%d %s (%s)\n@%s created %s\t**%s** <- **%s**\n\n%s\n\n",
pr.Index,
pr.Title,
state,
pr.Poster.UserName,
FormatTime(*pr.Created, false),
base,
head,
pr.Body,
)
if ciStatus != nil || len(reviews) != 0 || pr.State == gitea.StateOpen {
out += "---\n"
}
out += formatReviews(pr, reviews)
if ciStatus != nil {
var summary, errors string
for _, s := range ciStatus.Statuses {
summary += ciStatusSymbols[s.State]
if s.State != gitea.StatusSuccess {
errors += fmt.Sprintf(" - [**%s**:\t%s](%s)\n", s.Context, s.Description, s.TargetURL)
}
}
if len(ciStatus.Statuses) != 0 {
out += fmt.Sprintf("- CI: %s\n%s", summary, errors)
}
}
if pr.State == gitea.StateOpen {
if pr.Mergeable {
out += "- No Conflicts\n"
} else {
out += "- **Conflicting files**\n"
}
}
if pr.AllowMaintainerEdit {
out += "- Maintainers are allowed to edit\n"
}
outputMarkdown(out, getRepoURL(pr.HTMLURL))
}
func formatPRHead(pr *gitea.PullRequest) string {
head := pr.Head.Name
if pr.Head.RepoID != pr.Base.RepoID {
if pr.Head.Repository != nil {
head = pr.Head.Repository.Owner.UserName + ":" + head
} else {
head = "delete:" + head
}
}
return head
}
func formatPRState(pr *gitea.PullRequest) string {
if pr.Merged != nil {
return "merged"
}
return string(pr.State)
}
func formatReviews(pr *gitea.PullRequest, reviews []*gitea.PullReview) string {
result := ""
if len(reviews) == 0 {
return result
}
// deduplicate reviews by user (via review time & userID),
reviewByUserOrTeam := make(map[string]*gitea.PullReview)
for _, review := range reviews {
switch review.State {
case gitea.ReviewStateApproved,
gitea.ReviewStateRequestChanges,
gitea.ReviewStateRequestReview:
if review.Reviewer != nil {
if r, ok := reviewByUserOrTeam[fmt.Sprintf("user_%d", review.Reviewer.ID)]; !ok || review.Submitted.After(r.Submitted) {
reviewByUserOrTeam[fmt.Sprintf("user_%d", review.Reviewer.ID)] = review
}
} else if review.ReviewerTeam != nil {
if r, ok := reviewByUserOrTeam[fmt.Sprintf("team_%d", review.ReviewerTeam.ID)]; !ok || review.Submitted.After(r.Submitted) {
reviewByUserOrTeam[fmt.Sprintf("team_%d", review.ReviewerTeam.ID)] = review
}
}
}
}
// group reviews by type
reviewByState := make(map[gitea.ReviewStateType][]string)
for _, r := range reviewByUserOrTeam {
if r.Reviewer != nil {
reviewByState[r.State] = append(reviewByState[r.State],
r.Reviewer.UserName,
)
} else if r.ReviewerTeam != nil {
// only pulls to orgs can have team reviews
org := pr.Base.Repository.Owner
reviewByState[r.State] = append(reviewByState[r.State],
fmt.Sprintf("%s/%s", org.UserName, r.ReviewerTeam.Name),
)
}
}
// stringify
for state, user := range reviewByState {
result += fmt.Sprintf("- %s by @%s\n", state, strings.Join(user, ", @"))
}
return result
}
// PullsList prints a listing of pulls
func PullsList(prs []*gitea.PullRequest, output string, fields []string) {
printPulls(prs, output, fields)
}
// PullFields are all available fields to print with PullsList()
var PullFields = []string{
"index",
"state",
"author",
"author-id",
"url",
"title",
"body",
"mergeable",
"base",
"base-commit",
"head",
"diff",
"patch",
"created",
"updated",
"deadline",
"assignees",
"milestone",
"labels",
"comments",
}
func printPulls(pulls []*gitea.PullRequest, output string, fields []string) {
labelMap := map[int64]string{}
var printables = make([]printable, len(pulls))
machineReadable := isMachineReadable(output)
for i, x := range pulls {
// pre-serialize labels for performance
for _, label := range x.Labels {
if _, ok := labelMap[label.ID]; !ok {
labelMap[label.ID] = formatLabel(label, !machineReadable, "")
}
}
// store items with printable interface
printables[i] = &printablePull{x, &labelMap}
}
t := tableFromItems(fields, printables, machineReadable)
t.print(output)
}
type printablePull struct {
*gitea.PullRequest
formattedLabels *map[int64]string
}
func (x printablePull) FormatField(field string, machineReadable bool) string {
switch field {
case "index":
return fmt.Sprintf("%d", x.Index)
case "state":
return formatPRState(x.PullRequest)
case "author":
return formatUserName(x.Poster)
case "author-id":
return x.Poster.UserName
case "url":
return x.HTMLURL
case "title":
return x.Title
case "body":
return x.Body
case "created":
return FormatTime(*x.Created, machineReadable)
case "updated":
return FormatTime(*x.Updated, machineReadable)
case "deadline":
if x.Deadline == nil {
return ""
}
return FormatTime(*x.Deadline, machineReadable)
case "milestone":
if x.Milestone != nil {
return x.Milestone.Title
}
return ""
case "labels":
var labels = make([]string, len(x.Labels))
for i, l := range x.Labels {
labels[i] = (*x.formattedLabels)[l.ID]
}
return strings.Join(labels, " ")
case "assignees":
var assignees = make([]string, len(x.Assignees))
for i, a := range x.Assignees {
assignees[i] = formatUserName(a)
}
return strings.Join(assignees, " ")
case "comments":
return fmt.Sprintf("%d", x.Comments)
case "mergeable":
isMergeable := x.Mergeable && x.State == gitea.StateOpen
return formatBoolean(isMergeable, !machineReadable)
case "base":
return x.Base.Ref
case "base-commit":
return x.MergeBase
case "head":
return formatPRHead(x.PullRequest)
case "diff":
return x.DiffURL
case "patch":
return x.PatchURL
}
return ""
}