Skip to content
31 changes: 31 additions & 0 deletions api/queries_pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,37 @@ func PullRequestReady(client *Client, repo ghrepo.Interface, pr *PullRequest) er
return client.Mutate(repo.RepoHost(), "PullRequestReadyForReview", &mutation, variables)
}

func PullRequestRevert(client *Client, repo ghrepo.Interface, params githubv4.RevertPullRequestInput) (*PullRequest, error) {
var mutation struct {
RevertPullRequest struct {
PullRequest struct {
ID githubv4.ID
}
RevertPullRequest struct {
ID string
Number int
URL string
}
} `graphql:"revertPullRequest(input: $input)"`
}

variables := map[string]interface{}{
"input": params,
}
err := client.Mutate(repo.RepoHost(), "PullRequestRevert", &mutation, variables)
if err != nil {
return nil, err
}
pr := &mutation.RevertPullRequest.RevertPullRequest
revertPR := &PullRequest{
ID: pr.ID,
Number: pr.Number,
URL: pr.URL,
}

return revertPR, nil
}

func ConvertPullRequestToDraft(client *Client, repo ghrepo.Interface, pr *PullRequest) error {
var mutation struct {
ConvertPullRequestToDraft struct {
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/pr/pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
cmdMerge "github.com/cli/cli/v2/pkg/cmd/pr/merge"
cmdReady "github.com/cli/cli/v2/pkg/cmd/pr/ready"
cmdReopen "github.com/cli/cli/v2/pkg/cmd/pr/reopen"
cmdRevert "github.com/cli/cli/v2/pkg/cmd/pr/revert"
cmdReview "github.com/cli/cli/v2/pkg/cmd/pr/review"
cmdStatus "github.com/cli/cli/v2/pkg/cmd/pr/status"
cmdUpdateBranch "github.com/cli/cli/v2/pkg/cmd/pr/update-branch"
Expand Down Expand Up @@ -63,6 +64,7 @@ func NewCmdPR(f *cmdutil.Factory) *cobra.Command {
cmdComment.NewCmdComment(f, nil),
cmdClose.NewCmdClose(f, nil),
cmdReopen.NewCmdReopen(f, nil),
cmdRevert.NewCmdRevert(f, nil),
cmdEdit.NewCmdEdit(f, nil),
cmdLock.NewCmdLock(f, cmd.Name(), nil),
cmdLock.NewCmdUnlock(f, cmd.Name(), nil),
Expand Down
132 changes: 132 additions & 0 deletions pkg/cmd/pr/revert/revert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package revert

import (
"fmt"
"net/http"

"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/shurcooL/githubv4"
"github.com/spf13/cobra"
)

type RevertOptions struct {
HttpClient func() (*http.Client, error)
IO *iostreams.IOStreams

Finder shared.PRFinder

SelectorArg string

Body string
BodySet bool
Title string
IsDraft bool
}

func NewCmdRevert(f *cmdutil.Factory, runF func(*RevertOptions) error) *cobra.Command {
opts := &RevertOptions{
IO: f.IOStreams,
HttpClient: f.HttpClient,
}

var bodyFile string

cmd := &cobra.Command{
Use: "revert {<number> | <url> | <branch>}",
Short: "Revert a pull request",
Args: cmdutil.ExactArgs(1, "cannot revert pull request: number, url, or branch required"),
RunE: func(cmd *cobra.Command, args []string) error {
opts.Finder = shared.NewFinder(f)

if len(args) > 0 {
opts.SelectorArg = args[0]
}

bodyProvided := cmd.Flags().Changed("body")
bodyFileProvided := bodyFile != ""

if err := cmdutil.MutuallyExclusive(
"specify only one of `--body` or `--body-file`",
bodyProvided,
bodyFileProvided,
); err != nil {
return err
}

if bodyProvided || bodyFileProvided {
opts.BodySet = true
if bodyFileProvided {
b, err := cmdutil.ReadFile(bodyFile, opts.IO.In)
if err != nil {
return err
}
opts.Body = string(b)
}
}

if runF != nil {
return runF(opts)
}
return revertRun(opts)
},
}

cmd.Flags().BoolVarP(&opts.IsDraft, "draft", "d", false, "Mark revert pull request as a draft")
cmd.Flags().StringVarP(&opts.Title, "title", "t", "", "Title for the revert pull request")
cmd.Flags().StringVarP(&opts.Body, "body", "b", "", "Body for the revert pull request")
cmd.Flags().StringVarP(&bodyFile, "body-file", "F", "", "Read body text from `file` (use \"-\" to read from standard input)")
return cmd
}

func revertRun(opts *RevertOptions) error {
cs := opts.IO.ColorScheme()

findOptions := shared.FindOptions{
Selector: opts.SelectorArg,
Fields: []string{"id", "number", "state", "title"},
}
pr, baseRepo, err := opts.Finder.Find(findOptions)
if err != nil {
return err
}
if pr.State != "MERGED" {
fmt.Fprintf(opts.IO.ErrOut, "%s Pull request %s#%d (%s) can't be reverted because it has not been merged\n", cs.FailureIcon(), ghrepo.FullName(baseRepo), pr.Number, pr.Title)
return cmdutil.SilentError
}

httpClient, err := opts.HttpClient()
if err != nil {
return err
}
apiClient := api.NewClientFromHTTP(httpClient)

params := githubv4.RevertPullRequestInput{
PullRequestID: pr.ID,
Draft: githubv4.NewBoolean(githubv4.Boolean(opts.IsDraft)),
}
// Only set the Body field when opts.BodySet is true to avoid overriding
// GitHub's default revert body generation.
if opts.BodySet {
params.Body = githubv4.NewString(githubv4.String(opts.Body))
}
// Only set the Title field when opts.Title is not empty to avoid overriding
// GitHub's default revert title generation.
if opts.Title != "" {
params.Title = githubv4.NewString(githubv4.String(opts.Title))
}

revertPR, err := api.PullRequestRevert(apiClient, baseRepo, params)
if err != nil {
fmt.Fprintf(opts.IO.ErrOut, "%s %s\n", cs.FailureIcon(), err)
return fmt.Errorf("API call failed: %w", err)
}

if revertPR != nil {
fmt.Fprintln(opts.IO.Out, revertPR.URL)
}
return nil
}
Loading