Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into issue-metadata-reso…
Browse files Browse the repository at this point in the history
…lve-ids
  • Loading branch information
mislav committed May 13, 2020
2 parents 1ff37be + 3552bca commit 57e60ab
Show file tree
Hide file tree
Showing 9 changed files with 617 additions and 36 deletions.
38 changes: 38 additions & 0 deletions api/queries_pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ type PullRequestReviewStatus struct {
ReviewRequired bool
}

type PullRequestMergeMethod int

const (
PullRequestMergeMethodMerge PullRequestMergeMethod = iota
PullRequestMergeMethodRebase
PullRequestMergeMethodSquash
)

func (pr *PullRequest) ReviewStatus() PullRequestReviewStatus {
var status PullRequestReviewStatus
switch pr.ReviewDecision {
Expand Down Expand Up @@ -466,6 +474,7 @@ func PullRequestForBranch(client *Client, repo ghrepo.Interface, baseBranch, hea
type response struct {
Repository struct {
PullRequests struct {
ID githubv4.ID
Nodes []PullRequest
}
}
Expand Down Expand Up @@ -658,6 +667,7 @@ func CreatePullRequest(client *Client, repo *Repository, params map[string]inter
requestReviews(input: $input) { clientMutationId }
}`
reviewParams["pullRequestId"] = pr.ID
reviewParams["union"] = true
variables := map[string]interface{}{
"input": reviewParams,
}
Expand Down Expand Up @@ -915,6 +925,34 @@ func PullRequestReopen(client *Client, repo ghrepo.Interface, pr *PullRequest) e
return err
}

func PullRequestMerge(client *Client, repo ghrepo.Interface, pr *PullRequest, m PullRequestMergeMethod) error {
mergeMethod := githubv4.PullRequestMergeMethodMerge
switch m {
case PullRequestMergeMethodRebase:
mergeMethod = githubv4.PullRequestMergeMethodRebase
case PullRequestMergeMethodSquash:
mergeMethod = githubv4.PullRequestMergeMethodSquash
}

var mutation struct {
MergePullRequest struct {
PullRequest struct {
ID githubv4.ID
}
} `graphql:"mergePullRequest(input: $input)"`
}

input := githubv4.MergePullRequestInput{
PullRequestID: pr.ID,
MergeMethod: &mergeMethod,
}

v4 := githubv4.NewClient(client.http)
err := v4.Mutate(context.Background(), &mutation, input, nil)

return err
}

func min(a, b int) int {
if a < b {
return a
Expand Down
83 changes: 82 additions & 1 deletion command/pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func init() {
prCmd.AddCommand(prStatusCmd)
prCmd.AddCommand(prCloseCmd)
prCmd.AddCommand(prReopenCmd)
prCmd.AddCommand(prMergeCmd)
prMergeCmd.Flags().BoolP("merge", "m", true, "Merge the commits with the base branch")
prMergeCmd.Flags().BoolP("rebase", "r", false, "Rebase the commits onto the base branch")
prMergeCmd.Flags().BoolP("squash", "s", false, "Squash the commits into one commit and merge it into the base branch")

prCmd.AddCommand(prListCmd)
prListCmd.Flags().IntP("limit", "L", 30, "Maximum number of items to fetch")
Expand Down Expand Up @@ -58,7 +62,7 @@ var prStatusCmd = &cobra.Command{
RunE: prStatus,
}
var prViewCmd = &cobra.Command{
Use: "view [{<number> | <url> | <branch>}]",
Use: "view [<number> | <url> | <branch>]",
Short: "View a pull request",
Long: `Display the title, body, and other information about a pull request.
Expand All @@ -81,6 +85,13 @@ var prReopenCmd = &cobra.Command{
RunE: prReopen,
}

var prMergeCmd = &cobra.Command{
Use: "merge [<number> | <url> | <branch>]",
Short: "Merge a pull request",
Args: cobra.MaximumNArgs(1),
RunE: prMerge,
}

func prStatus(cmd *cobra.Command, args []string) error {
ctx := contextForCommand(cmd)
apiClient, err := apiClientForContext(ctx)
Expand All @@ -100,6 +111,7 @@ func prStatus(cmd *cobra.Command, args []string) error {

repoOverride, _ := cmd.Flags().GetString("repo")
currentPRNumber, currentPRHeadRef, err := prSelectorForCurrentBranch(ctx, baseRepo)

if err != nil && repoOverride == "" && err.Error() != "git: not on any branch" {
return fmt.Errorf("could not query for pull request for current branch: %w", err)
}
Expand Down Expand Up @@ -419,6 +431,75 @@ func prReopen(cmd *cobra.Command, args []string) error {
return nil
}

func prMerge(cmd *cobra.Command, args []string) error {
ctx := contextForCommand(cmd)
apiClient, err := apiClientForContext(ctx)
if err != nil {
return err
}

baseRepo, err := determineBaseRepo(cmd, ctx)
if err != nil {
return err
}

var pr *api.PullRequest
if len(args) > 0 {
pr, err = prFromArg(apiClient, baseRepo, args[0])
if err != nil {
return err
}
} else {
prNumber, branchWithOwner, err := prSelectorForCurrentBranch(ctx, baseRepo)
if err != nil {
return err
}

if prNumber != 0 {
pr, err = api.PullRequestByNumber(apiClient, baseRepo, prNumber)
} else {
pr, err = api.PullRequestForBranch(apiClient, baseRepo, "", branchWithOwner)
}
if err != nil {
return err
}
}

if pr.State == "MERGED" {
err := fmt.Errorf("%s Pull request #%d was already merged", utils.Red("!"), pr.Number)
return err
}

rebase, err := cmd.Flags().GetBool("rebase")
if err != nil {
return err
}
squash, err := cmd.Flags().GetBool("squash")
if err != nil {
return err
}

var output string
if rebase {
output = fmt.Sprintf("%s Rebased and merged pull request #%d\n", utils.Green("✔"), pr.Number)
err = api.PullRequestMerge(apiClient, baseRepo, pr, api.PullRequestMergeMethodRebase)
} else if squash {
output = fmt.Sprintf("%s Squashed and merged pull request #%d\n", utils.Green("✔"), pr.Number)
err = api.PullRequestMerge(apiClient, baseRepo, pr, api.PullRequestMergeMethodSquash)
} else {
output = fmt.Sprintf("%s Merged pull request #%d\n", utils.Green("✔"), pr.Number)
err = api.PullRequestMerge(apiClient, baseRepo, pr, api.PullRequestMergeMethodMerge)
}

if err != nil {
return fmt.Errorf("API call failed: %w", err)
}

fmt.Fprint(colorableOut(cmd), output)

return nil
}

func printPrPreview(out io.Writer, pr *api.PullRequest) error {
// Header (Title and State)
fmt.Fprintln(out, utils.Bold(pr.Title))
Expand Down
1 change: 1 addition & 0 deletions command/pr_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ func TestPRCreate_metadata(t *testing.T) {
eq(t, inputs["pullRequestId"], "NEWPULLID")
eq(t, inputs["userIds"], []interface{}{"HUBOTID", "MONAID"})
eq(t, inputs["teamIds"], []interface{}{"COREID", "ROBOTID"})
eq(t, inputs["union"], true)
}))

cs, cmdTeardown := test.InitCmdStubber()
Expand Down
159 changes: 150 additions & 9 deletions command/pr_review.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import (
"strconv"
"strings"

"github.com/cli/cli/api"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"

"github.com/cli/cli/api"
"github.com/cli/cli/pkg/surveyext"
"github.com/cli/cli/utils"
)

func init() {
// TODO re-register post release
// prCmd.AddCommand(prReviewCmd)
prCmd.AddCommand(prReviewCmd)

prReviewCmd.Flags().BoolP("approve", "a", false, "Approve pull request")
prReviewCmd.Flags().BoolP("request-changes", "r", false, "Request changes on a pull request")
Expand All @@ -28,6 +31,8 @@ var prReviewCmd = &cobra.Command{
Examples:
gh pr review # add a review for the current branch's pull request
gh pr review 123 # add a review for pull request 123
gh pr review -a # mark the current branch's pull request as approved
gh pr review -c -b "interesting" # comment on the current branch's pull request
gh pr review 123 -r -b "needs more ascii art" # request changes on pull request 123
Expand Down Expand Up @@ -56,15 +61,19 @@ func processReviewOpt(cmd *cobra.Command) (*api.PullRequestReviewInput, error) {
state = api.ReviewComment
}

if found != 1 {
return nil, errors.New("need exactly one of --approve, --request-changes, or --comment")
}

body, err := cmd.Flags().GetString("body")
if err != nil {
return nil, err
}

if found == 0 && body == "" {
return nil, nil // signal interactive mode
} else if found == 0 && body != "" {
return nil, errors.New("--body unsupported without --approve, --request-changes, or --comment")
} else if found > 1 {
return nil, errors.New("need exactly one of --approve, --request-changes, or --comment")
}

if (flag == "request-changes" || flag == "comment") && body == "" {
return nil, fmt.Errorf("body cannot be blank for %s review", flag)
}
Expand Down Expand Up @@ -108,7 +117,7 @@ func prReview(cmd *cobra.Command, args []string) error {
}
}

input, err := processReviewOpt(cmd)
reviewData, err := processReviewOpt(cmd)
if err != nil {
return fmt.Errorf("did not understand desired review action: %w", err)
}
Expand All @@ -124,12 +133,144 @@ func prReview(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("could not find pull request: %w", err)
}
prNum = pr.Number
}

out := colorableOut(cmd)

if reviewData == nil {
reviewData, err = reviewSurvey(cmd)
if err != nil {
return err
}
if reviewData == nil && err == nil {
fmt.Fprint(out, "Discarding.\n")
return nil
}
}

err = api.AddReview(apiClient, pr, input)
err = api.AddReview(apiClient, pr, reviewData)
if err != nil {
return fmt.Errorf("failed to create review: %w", err)
}

switch reviewData.State {
case api.ReviewComment:
fmt.Fprintf(out, "%s Reviewed pull request #%d\n", utils.Gray("-"), prNum)
case api.ReviewApprove:
fmt.Fprintf(out, "%s Approved pull request #%d\n", utils.Green("✓"), prNum)
case api.ReviewRequestChanges:
fmt.Fprintf(out, "%s Requested changes to pull request #%d\n", utils.Red("+"), prNum)
}

return nil
}

func reviewSurvey(cmd *cobra.Command) (*api.PullRequestReviewInput, error) {
editorCommand, err := determineEditor(cmd)
if err != nil {
return nil, err
}

typeAnswers := struct {
ReviewType string
}{}
typeQs := []*survey.Question{
{
Name: "reviewType",
Prompt: &survey.Select{
Message: "What kind of review do you want to give?",
Options: []string{
"Comment",
"Approve",
"Request changes",
},
},
},
}

err = SurveyAsk(typeQs, &typeAnswers)
if err != nil {
return nil, err
}

var reviewState api.PullRequestReviewState

switch typeAnswers.ReviewType {
case "Approve":
reviewState = api.ReviewApprove
case "Request changes":
reviewState = api.ReviewRequestChanges
case "Comment":
reviewState = api.ReviewComment
default:
panic("unreachable state")
}

bodyAnswers := struct {
Body string
}{}

blankAllowed := false
if reviewState == api.ReviewApprove {
blankAllowed = true
}

bodyQs := []*survey.Question{
&survey.Question{
Name: "body",
Prompt: &surveyext.GhEditor{
BlankAllowed: blankAllowed,
EditorCommand: editorCommand,
Editor: &survey.Editor{
Message: "Review body",
FileName: "*.md",
},
},
},
}

err = SurveyAsk(bodyQs, &bodyAnswers)
if err != nil {
return nil, err
}

if bodyAnswers.Body == "" && (reviewState == api.ReviewComment || reviewState == api.ReviewRequestChanges) {
return nil, errors.New("this type of review cannot be blank")
}

if len(bodyAnswers.Body) > 0 {
out := colorableOut(cmd)
renderedBody, err := utils.RenderMarkdown(bodyAnswers.Body)
if err != nil {
return nil, err
}

fmt.Fprintf(out, "Got:\n%s", renderedBody)
}

confirm := false
confirmQs := []*survey.Question{
{
Name: "confirm",
Prompt: &survey.Confirm{
Message: "Submit?",
Default: true,
},
},
}

err = SurveyAsk(confirmQs, &confirm)
if err != nil {
return nil, err
}

if !confirm {
return nil, nil
}

return &api.PullRequestReviewInput{
Body: bodyAnswers.Body,
State: reviewState,
}, nil
}
Loading

0 comments on commit 57e60ab

Please sign in to comment.