Skip to content

Commit e0dbf37

Browse files
Merge pull request cli#960 from cli/prêt-à-réviser
Add `gh pr ready`
2 parents 06e43ac + d209c0b commit e0dbf37

File tree

3 files changed

+157
-1
lines changed

3 files changed

+157
-1
lines changed

api/queries_pr.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,27 @@ func PullRequestMerge(client *Client, repo ghrepo.Interface, pr *PullRequest, m
953953
return err
954954
}
955955

956+
func PullRequestReady(client *Client, repo ghrepo.Interface, pr *PullRequest) error {
957+
var mutation struct {
958+
MarkPullRequestReadyForReviewInput struct {
959+
PullRequest struct {
960+
ID githubv4.ID
961+
}
962+
} `graphql:"markPullRequestReadyForReview(input: $input)"`
963+
}
964+
965+
type MarkPullRequestReadyForReviewInput struct {
966+
PullRequestID githubv4.ID `json:"pullRequestId"`
967+
}
968+
969+
input := MarkPullRequestReadyForReviewInput{PullRequestID: pr.ID}
970+
971+
v4 := githubv4.NewClient(client.http)
972+
err := v4.Mutate(context.Background(), &mutation, input, nil)
973+
974+
return err
975+
}
976+
956977
func min(a, b int) int {
957978
if a < b {
958979
return a

command/pr.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func init() {
2929
prMergeCmd.Flags().BoolP("merge", "m", true, "Merge the commits with the base branch")
3030
prMergeCmd.Flags().BoolP("rebase", "r", false, "Rebase the commits onto the base branch")
3131
prMergeCmd.Flags().BoolP("squash", "s", false, "Squash the commits into one commit and merge it into the base branch")
32+
prCmd.AddCommand(prReadyCmd)
3233

3334
prCmd.AddCommand(prListCmd)
3435
prListCmd.Flags().IntP("limit", "L", 30, "Maximum number of items to fetch")
@@ -84,13 +85,18 @@ var prReopenCmd = &cobra.Command{
8485
Args: cobra.ExactArgs(1),
8586
RunE: prReopen,
8687
}
87-
8888
var prMergeCmd = &cobra.Command{
8989
Use: "merge [<number> | <url> | <branch>]",
9090
Short: "Merge a pull request",
9191
Args: cobra.MaximumNArgs(1),
9292
RunE: prMerge,
9393
}
94+
var prReadyCmd = &cobra.Command{
95+
Use: "ready [<number> | <url> | <branch>]",
96+
Short: "Make a pull request as ready for review",
97+
Args: cobra.MaximumNArgs(1),
98+
RunE: prReady,
99+
}
94100

95101
func prStatus(cmd *cobra.Command, args []string) error {
96102
ctx := contextForCommand(cmd)
@@ -551,6 +557,66 @@ func printPrPreview(out io.Writer, pr *api.PullRequest) error {
551557
return nil
552558
}
553559

560+
func prReady(cmd *cobra.Command, args []string) error {
561+
ctx := contextForCommand(cmd)
562+
apiClient, err := apiClientForContext(ctx)
563+
if err != nil {
564+
return err
565+
}
566+
567+
baseRepo, err := determineBaseRepo(apiClient, cmd, ctx)
568+
if err != nil {
569+
return err
570+
}
571+
572+
var pr *api.PullRequest
573+
if len(args) > 0 {
574+
var prNumber string
575+
n, _ := prFromURL(args[0])
576+
if n != "" {
577+
prNumber = n
578+
} else {
579+
prNumber = args[0]
580+
}
581+
582+
pr, err = prFromArg(apiClient, baseRepo, prNumber)
583+
if err != nil {
584+
return err
585+
}
586+
} else {
587+
prNumber, branchWithOwner, err := prSelectorForCurrentBranch(ctx, baseRepo)
588+
if err != nil {
589+
return err
590+
}
591+
592+
if prNumber != 0 {
593+
pr, err = api.PullRequestByNumber(apiClient, baseRepo, prNumber)
594+
} else {
595+
pr, err = api.PullRequestForBranch(apiClient, baseRepo, "", branchWithOwner)
596+
}
597+
if err != nil {
598+
return err
599+
}
600+
}
601+
602+
if pr.Closed {
603+
err := fmt.Errorf("%s Pull request #%d is closed. Only draft pull requests can be marked as \"ready for review\"", utils.Red("!"), pr.Number)
604+
return err
605+
} else if !pr.IsDraft {
606+
fmt.Fprintf(colorableErr(cmd), "%s Pull request #%d is already \"ready for review\"\n", utils.Yellow("!"), pr.Number)
607+
return nil
608+
}
609+
610+
err = api.PullRequestReady(apiClient, baseRepo, pr)
611+
if err != nil {
612+
return fmt.Errorf("API call failed: %w", err)
613+
}
614+
615+
fmt.Fprintf(colorableErr(cmd), "%s Pull request #%d is marked as \"ready for review\"\n", utils.Green("✔"), pr.Number)
616+
617+
return nil
618+
}
619+
554620
// Ref. https://developer.github.com/v4/enum/pullrequestreviewstate/
555621
const (
556622
requestedReviewState = "REQUESTED" // This is our own state for review request

command/pr_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,3 +1093,72 @@ func TestPrMerge_alreadyMerged(t *testing.T) {
10931093
t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr())
10941094
}
10951095
}
1096+
1097+
func TestPRReady(t *testing.T) {
1098+
initBlankContext("", "OWNER/REPO", "master")
1099+
http := initFakeHTTP()
1100+
http.StubRepoResponse("OWNER", "REPO")
1101+
http.StubResponse(200, bytes.NewBufferString(`
1102+
{ "data": { "repository": {
1103+
"pullRequest": { "number": 444, "closed": false, "isDraft": true}
1104+
} } }
1105+
`))
1106+
http.StubResponse(200, bytes.NewBufferString(`{"id": "THE-ID"}`))
1107+
1108+
output, err := RunCommand("pr ready 444")
1109+
if err != nil {
1110+
t.Fatalf("error running command `pr ready`: %v", err)
1111+
}
1112+
1113+
r := regexp.MustCompile(`Pull request #444 is marked as "ready for review"`)
1114+
1115+
if !r.MatchString(output.Stderr()) {
1116+
t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr())
1117+
}
1118+
}
1119+
1120+
func TestPRReady_alreadyReady(t *testing.T) {
1121+
initBlankContext("", "OWNER/REPO", "master")
1122+
http := initFakeHTTP()
1123+
http.StubRepoResponse("OWNER", "REPO")
1124+
http.StubResponse(200, bytes.NewBufferString(`
1125+
{ "data": { "repository": {
1126+
"pullRequest": { "number": 445, "closed": false, "isDraft": false}
1127+
} } }
1128+
`))
1129+
http.StubResponse(200, bytes.NewBufferString(`{"id": "THE-ID"}`))
1130+
1131+
output, err := RunCommand("pr ready 445")
1132+
if err != nil {
1133+
t.Fatalf("error running command `pr ready`: %v", err)
1134+
}
1135+
1136+
r := regexp.MustCompile(`Pull request #445 is already "ready for review"`)
1137+
1138+
if !r.MatchString(output.Stderr()) {
1139+
t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr())
1140+
}
1141+
}
1142+
1143+
func TestPRReady_closed(t *testing.T) {
1144+
initBlankContext("", "OWNER/REPO", "master")
1145+
http := initFakeHTTP()
1146+
http.StubRepoResponse("OWNER", "REPO")
1147+
http.StubResponse(200, bytes.NewBufferString(`
1148+
{ "data": { "repository": {
1149+
"pullRequest": { "number": 446, "closed": true, "isDraft": true}
1150+
} } }
1151+
`))
1152+
http.StubResponse(200, bytes.NewBufferString(`{"id": "THE-ID"}`))
1153+
1154+
_, err := RunCommand("pr ready 446")
1155+
if err == nil {
1156+
t.Fatalf("expected an error running command `pr ready` on a review that is closed!: %v", err)
1157+
}
1158+
1159+
r := regexp.MustCompile(`Pull request #446 is closed. Only draft pull requests can be marked as "ready for review"`)
1160+
1161+
if !r.MatchString(err.Error()) {
1162+
t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, err.Error())
1163+
}
1164+
}

0 commit comments

Comments
 (0)