mirror of
https://gitea.com/gitea/tea.git
synced 2024-11-25 03:51:36 +01:00
c1c7870ceb
at the moment we crash with an nil exeption if there exist team reviews this fix it and add support to display them Reviewed-on: https://gitea.com/gitea/tea/pulls/515 Reviewed-by: Norwin <noerw@noreply.gitea.io> Reviewed-by: wxiaoguang <wxiaoguang@noreply.gitea.io>
260 lines
6.1 KiB
Go
260 lines
6.1 KiB
Go
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
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 ""
|
|
}
|