Skip to content

Commit d81e622

Browse files
committed
lightest weight reviewing
this commit add very basic non-interactive PR reviewing. You can either review the "current" or a passed PR (number or URL) as approved, changes requested, or commented via CLI flags.
1 parent a7242f4 commit d81e622

File tree

4 files changed

+396
-1
lines changed

4 files changed

+396
-1
lines changed

api/queries_pr.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,24 @@ import (
66
"strings"
77
"time"
88

9-
"github.com/cli/cli/internal/ghrepo"
109
"github.com/shurcooL/githubv4"
10+
11+
"github.com/cli/cli/internal/ghrepo"
12+
)
13+
14+
type PullRequestReviewState int
15+
16+
const (
17+
ReviewApprove PullRequestReviewState = iota
18+
ReviewRequestChanges
19+
ReviewComment
1120
)
1221

22+
type PullRequestReviewInput struct {
23+
Body string
24+
State PullRequestReviewState
25+
}
26+
1327
type PullRequestsPayload struct {
1428
ViewerCreated PullRequestAndTotalCount
1529
ReviewRequested PullRequestAndTotalCount
@@ -593,6 +607,34 @@ func CreatePullRequest(client *Client, repo *Repository, params map[string]inter
593607
return &result.CreatePullRequest.PullRequest, nil
594608
}
595609

610+
func AddReview(client *Client, pr *PullRequest, input *PullRequestReviewInput) error {
611+
var mutation struct {
612+
AddPullRequestReview struct {
613+
ClientMutationID string
614+
} `graphql:"addPullRequestReview(input:$input)"`
615+
}
616+
617+
state := githubv4.PullRequestReviewEventComment
618+
switch input.State {
619+
case ReviewApprove:
620+
state = githubv4.PullRequestReviewEventApprove
621+
case ReviewRequestChanges:
622+
state = githubv4.PullRequestReviewEventRequestChanges
623+
}
624+
625+
body := githubv4.String(input.Body)
626+
627+
gqlInput := githubv4.AddPullRequestReviewInput{
628+
PullRequestID: pr.ID,
629+
Event: &state,
630+
Body: &body,
631+
}
632+
633+
v4 := githubv4.NewClient(client.http)
634+
635+
return v4.Mutate(context.Background(), &mutation, gqlInput, nil)
636+
}
637+
596638
func PullRequestList(client *Client, vars map[string]interface{}, limit int) (*PullRequestAndTotalCount, error) {
597639
type prBlock struct {
598640
Edges []struct {

command/pr_review.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package command
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/cli/cli/api"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func init() {
14+
prCmd.AddCommand(prReviewCmd)
15+
16+
prReviewCmd.Flags().StringP("approve", "a", "", "Approve pull request")
17+
prReviewCmd.Flags().StringP("request-changes", "r", "", "Request changes on a pull request")
18+
prReviewCmd.Flags().StringP("comment", "c", "", "Comment on a pull request")
19+
20+
// this is required; without it pflag complains that you must pass a string to string flags.
21+
prReviewCmd.Flags().Lookup("approve").NoOptDefVal = " "
22+
prReviewCmd.Flags().Lookup("request-changes").NoOptDefVal = " "
23+
prReviewCmd.Flags().Lookup("comment").NoOptDefVal = " "
24+
}
25+
26+
var prReviewCmd = &cobra.Command{
27+
Use: "review",
28+
Short: "TODO",
29+
Args: cobra.MaximumNArgs(1),
30+
Long: "TODO",
31+
RunE: prReview,
32+
}
33+
34+
func processReviewOpt(cmd *cobra.Command) (*api.PullRequestReviewInput, error) {
35+
found := 0
36+
flag := ""
37+
var state api.PullRequestReviewState
38+
39+
if cmd.Flags().Changed("approve") {
40+
found++
41+
flag = "approve"
42+
state = api.ReviewApprove
43+
}
44+
if cmd.Flags().Changed("request-changes") {
45+
found++
46+
flag = "request-changes"
47+
state = api.ReviewRequestChanges
48+
}
49+
if cmd.Flags().Changed("comment") {
50+
found++
51+
flag = "comment"
52+
state = api.ReviewComment
53+
}
54+
55+
if found != 1 {
56+
return nil, errors.New("need exactly one of approve, request-changes, or comment")
57+
}
58+
59+
val, err := cmd.Flags().GetString(flag)
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
body := ""
65+
if val != " " {
66+
body = val
67+
}
68+
69+
if flag == "comment" && (body == " " || len(body) == 0) {
70+
return nil, errors.New("cannot leave blank comment")
71+
}
72+
73+
return &api.PullRequestReviewInput{
74+
Body: body,
75+
State: state,
76+
}, nil
77+
}
78+
79+
func prReview(cmd *cobra.Command, args []string) error {
80+
ctx := contextForCommand(cmd)
81+
baseRepo, err := determineBaseRepo(cmd, ctx)
82+
if err != nil {
83+
return fmt.Errorf("could not determine base repo: %w", err)
84+
}
85+
86+
apiClient, err := apiClientForContext(ctx)
87+
if err != nil {
88+
return err
89+
}
90+
91+
var prNum int
92+
93+
if len(args) == 0 {
94+
prNum, _, err = prSelectorForCurrentBranch(ctx, baseRepo)
95+
if err != nil {
96+
return fmt.Errorf("could not query for pull request for current branch: %w", err)
97+
}
98+
} else {
99+
prArg, repo := prFromURL(args[0])
100+
if repo != nil {
101+
baseRepo = repo
102+
// TODO handle malformed URL; it falls through to Atoi
103+
} else {
104+
prArg = strings.TrimPrefix(args[0], "#")
105+
}
106+
prNum, err = strconv.Atoi(prArg)
107+
if err != nil {
108+
return fmt.Errorf("could not parse pull request number: %w", err)
109+
}
110+
}
111+
112+
input, err := processReviewOpt(cmd)
113+
if err != nil {
114+
return fmt.Errorf("did not understand desired review action: %w", err)
115+
}
116+
117+
pr, err := api.PullRequestByNumber(apiClient, baseRepo, prNum)
118+
if err != nil {
119+
return fmt.Errorf("could not find pull request: %w", err)
120+
}
121+
122+
err = api.AddReview(apiClient, pr, input)
123+
if err != nil {
124+
return fmt.Errorf("failed to create review: %w", err)
125+
}
126+
127+
return nil
128+
}

0 commit comments

Comments
 (0)