Add pr merge --auto

This commit is contained in:
Mislav Marohnić 2021-02-16 15:55:24 +01:00
parent f75bd7280f
commit 67bfedd56b
4 changed files with 173 additions and 86 deletions

View file

@ -140,14 +140,6 @@ 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 {
@ -1074,47 +1066,6 @@ func PullRequestReopen(client *Client, repo ghrepo.Interface, pr *PullRequest) e
return err
}
func PullRequestMerge(client *Client, repo ghrepo.Interface, pr *PullRequest, m PullRequestMergeMethod, body *string) 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,
}
if m == PullRequestMergeMethodSquash {
commitHeadline := githubv4.String(fmt.Sprintf("%s (#%d)", pr.Title, pr.Number))
input.CommitHeadline = &commitHeadline
}
if body != nil {
commitBody := githubv4.String(*body)
input.CommitBody = &commitBody
}
variables := map[string]interface{}{
"input": input,
}
gql := graphQLClient(client.http, repo.RepoHost())
err := gql.MutateNamed(context.Background(), "PullRequestMerge", &mutation, variables)
return err
}
func PullRequestReady(client *Client, repo ghrepo.Interface, pr *PullRequest) error {
var mutation struct {
MarkPullRequestReadyForReview struct {

100
pkg/cmd/pr/merge/http.go Normal file
View file

@ -0,0 +1,100 @@
package merge
import (
"context"
"net/http"
"github.com/cli/cli/internal/ghinstance"
"github.com/cli/cli/internal/ghrepo"
"github.com/shurcooL/githubv4"
"github.com/shurcooL/graphql"
)
type PullRequestMergeMethod int
const (
PullRequestMergeMethodMerge PullRequestMergeMethod = iota
PullRequestMergeMethodRebase
PullRequestMergeMethodSquash
)
type mergePayload struct {
repo ghrepo.Interface
pullRequestID string
method PullRequestMergeMethod
auto bool
commitSubject string
setCommitSubject bool
commitBody string
setCommitBody bool
}
// TODO: drop after githubv4 gets updated
type EnablePullRequestAutoMergeInput struct {
githubv4.MergePullRequestInput
}
func mergePullRequest(client *http.Client, payload mergePayload) error {
input := githubv4.MergePullRequestInput{
PullRequestID: githubv4.ID(payload.pullRequestID),
}
switch payload.method {
case PullRequestMergeMethodMerge:
m := githubv4.PullRequestMergeMethodMerge
input.MergeMethod = &m
case PullRequestMergeMethodRebase:
m := githubv4.PullRequestMergeMethodRebase
input.MergeMethod = &m
case PullRequestMergeMethodSquash:
m := githubv4.PullRequestMergeMethodSquash
input.MergeMethod = &m
}
if payload.setCommitSubject {
commitHeadline := githubv4.String(payload.commitSubject)
input.CommitHeadline = &commitHeadline
}
if payload.setCommitBody {
commitBody := githubv4.String(payload.commitBody)
input.CommitBody = &commitBody
}
variables := map[string]interface{}{
"input": input,
}
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(payload.repo.RepoHost()), client)
if payload.auto {
var mutation struct {
AnablePullRequestAutoMerge struct {
ClientMutationId string
} `graphql:"enablePullRequestAutoMerge(input: $input)"`
}
variables["input"] = EnablePullRequestAutoMergeInput{input}
return gql.MutateNamed(context.Background(), "PullRequestAutoMerge", &mutation, variables)
}
var mutation struct {
MergePullRequest struct {
ClientMutationId string
} `graphql:"mergePullRequest(input: $input)"`
}
return gql.MutateNamed(context.Background(), "PullRequestMerge", &mutation, variables)
}
func disableAutoMerge(client *http.Client, repo ghrepo.Interface, prID string) error {
var mutation struct {
DisablePullRequestAutoMerge struct {
ClientMutationId string
} `graphql:"disablePullRequestAutoMerge(input: {pullRequestId: $prID})"`
}
variables := map[string]interface{}{
"prID": githubv4.ID(prID),
}
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(repo.RepoHost()), client)
return gql.MutateNamed(context.Background(), "PullRequestAutoMergeDisable", &mutation, variables)
}

View file

@ -30,7 +30,10 @@ type MergeOptions struct {
SelectorArg string
DeleteBranch bool
MergeMethod api.PullRequestMergeMethod
MergeMethod PullRequestMergeMethod
AutoMerge bool
AutoMergeDisable bool
Body string
@ -75,15 +78,15 @@ func NewCmdMerge(f *cmdutil.Factory, runF func(*MergeOptions) error) *cobra.Comm
methodFlags := 0
if flagMerge {
opts.MergeMethod = api.PullRequestMergeMethodMerge
opts.MergeMethod = PullRequestMergeMethodMerge
methodFlags++
}
if flagRebase {
opts.MergeMethod = api.PullRequestMergeMethodRebase
opts.MergeMethod = PullRequestMergeMethodRebase
methodFlags++
}
if flagSquash {
opts.MergeMethod = api.PullRequestMergeMethodSquash
opts.MergeMethod = PullRequestMergeMethodSquash
methodFlags++
}
if methodFlags == 0 {
@ -110,6 +113,8 @@ func NewCmdMerge(f *cmdutil.Factory, runF func(*MergeOptions) error) *cobra.Comm
cmd.Flags().BoolVarP(&flagMerge, "merge", "m", false, "Merge the commits with the base branch")
cmd.Flags().BoolVarP(&flagRebase, "rebase", "r", false, "Rebase the commits onto the base branch")
cmd.Flags().BoolVarP(&flagSquash, "squash", "s", false, "Squash the commits into one commit and merge it into the base branch")
cmd.Flags().BoolVar(&opts.AutoMerge, "auto", false, "Automatically merge only after necessary requirements are met")
cmd.Flags().BoolVar(&opts.AutoMergeDisable, "disable-auto", false, "Disable auto-merge for this pull request")
return cmd
}
@ -127,6 +132,19 @@ func mergeRun(opts *MergeOptions) error {
return err
}
isTerminal := opts.IO.IsStdoutTTY()
if opts.AutoMergeDisable {
err := disableAutoMerge(httpClient, baseRepo, pr.ID)
if err != nil {
return err
}
if isTerminal {
fmt.Fprintf(opts.IO.ErrOut, "%s Auto-merge disabled for pull request #%d\n", cs.SuccessIconWithColor(cs.Green), pr.Number)
}
return nil
}
if opts.SelectorArg == "" {
localBranchLastCommit, err := git.LastCommit()
if err == nil {
@ -144,18 +162,26 @@ func mergeRun(opts *MergeOptions) error {
deleteBranch := opts.DeleteBranch
crossRepoPR := pr.HeadRepositoryOwner.Login != baseRepo.RepoOwner()
isTerminal := opts.IO.IsStdoutTTY()
isPRAlreadyMerged := pr.State == "MERGED"
if !isPRAlreadyMerged {
mergeMethod := opts.MergeMethod
payload := mergePayload{
repo: baseRepo,
pullRequestID: pr.ID,
method: opts.MergeMethod,
auto: opts.AutoMerge,
commitBody: opts.Body,
}
if opts.Body != "" {
payload.setCommitBody = true
}
if opts.InteractiveMode {
r, err := api.GitHubRepo(apiClient, baseRepo)
if err != nil {
return err
}
mergeMethod, err = mergeMethodSurvey(r)
payload.method, err = mergeMethodSurvey(r)
if err != nil {
return err
}
@ -164,7 +190,7 @@ func mergeRun(opts *MergeOptions) error {
return err
}
allowEditMsg := mergeMethod != api.PullRequestMergeMethodRebase
allowEditMsg := payload.method != PullRequestMergeMethodRebase
action, err := confirmSurvey(allowEditMsg)
if err != nil {
@ -178,19 +204,19 @@ func mergeRun(opts *MergeOptions) error {
return err
}
if opts.Body == "" {
if mergeMethod == api.PullRequestMergeMethodMerge {
opts.Body = pr.Title
if payload.commitBody == "" {
if payload.method == PullRequestMergeMethodMerge {
payload.commitBody = pr.Title
} else {
opts.Body = pr.ViewerMergeBodyText
payload.commitBody = pr.ViewerMergeBodyText
}
}
msg, err := commitMsgSurvey(opts.Body, editorCommand)
payload.commitBody, err = commitMsgSurvey(payload.commitBody, editorCommand)
if err != nil {
return err
}
opts.Body = msg
payload.setCommitBody = true
action, err = confirmSurvey(false)
if err != nil {
@ -203,27 +229,38 @@ func mergeRun(opts *MergeOptions) error {
}
}
var body *string
if opts.Body != "" {
body = &opts.Body
if !payload.setCommitSubject && payload.method == PullRequestMergeMethodSquash {
payload.commitSubject = fmt.Sprintf("%s (#%d)", pr.Title, pr.Number)
payload.setCommitSubject = true
}
err = api.PullRequestMerge(apiClient, baseRepo, pr, mergeMethod, body)
err = mergePullRequest(httpClient, payload)
if err != nil {
return err
}
if isTerminal {
action := "Merged"
switch mergeMethod {
case api.PullRequestMergeMethodRebase:
action = "Rebased and merged"
case api.PullRequestMergeMethodSquash:
action = "Squashed and merged"
if payload.auto {
method := ""
switch payload.method {
case PullRequestMergeMethodRebase:
method = " via rebase"
case PullRequestMergeMethodSquash:
method = " via squash"
}
fmt.Fprintf(opts.IO.ErrOut, "%s Pull request #%d will automatically get merged%s when all requiremenets are met\n", cs.SuccessIconWithColor(cs.Green), pr.Number, method)
} else {
action := "Merged"
switch payload.method {
case PullRequestMergeMethodRebase:
action = "Rebased and merged"
case PullRequestMergeMethodSquash:
action = "Squashed and merged"
}
fmt.Fprintf(opts.IO.ErrOut, "%s %s pull request #%d (%s)\n", cs.SuccessIconWithColor(cs.Magenta), action, pr.Number, pr.Title)
}
fmt.Fprintf(opts.IO.ErrOut, "%s %s pull request #%d (%s)\n", cs.SuccessIconWithColor(cs.Magenta), action, pr.Number, pr.Title)
}
} else if !opts.IsDeleteBranchIndicated && opts.InteractiveMode && !crossRepoPR {
} else if !opts.IsDeleteBranchIndicated && opts.InteractiveMode && !crossRepoPR && !opts.AutoMerge {
err := prompt.SurveyAskOne(&survey.Confirm{
Message: fmt.Sprintf("Pull request #%d was already merged. Delete the branch locally?", pr.Number),
Default: false,
@ -235,7 +272,7 @@ func mergeRun(opts *MergeOptions) error {
fmt.Fprintf(opts.IO.ErrOut, "%s Pull request #%d was already merged\n", cs.WarningIcon(), pr.Number)
}
if !deleteBranch || crossRepoPR {
if !deleteBranch || crossRepoPR || opts.AutoMerge {
return nil
}
@ -290,23 +327,23 @@ func mergeRun(opts *MergeOptions) error {
return nil
}
func mergeMethodSurvey(baseRepo *api.Repository) (api.PullRequestMergeMethod, error) {
func mergeMethodSurvey(baseRepo *api.Repository) (PullRequestMergeMethod, error) {
type mergeOption struct {
title string
method api.PullRequestMergeMethod
method PullRequestMergeMethod
}
var mergeOpts []mergeOption
if baseRepo.MergeCommitAllowed {
opt := mergeOption{title: "Create a merge commit", method: api.PullRequestMergeMethodMerge}
opt := mergeOption{title: "Create a merge commit", method: PullRequestMergeMethodMerge}
mergeOpts = append(mergeOpts, opt)
}
if baseRepo.RebaseMergeAllowed {
opt := mergeOption{title: "Rebase and merge", method: api.PullRequestMergeMethodRebase}
opt := mergeOption{title: "Rebase and merge", method: PullRequestMergeMethodRebase}
mergeOpts = append(mergeOpts, opt)
}
if baseRepo.SquashMergeAllowed {
opt := mergeOption{title: "Squash and merge", method: api.PullRequestMergeMethodSquash}
opt := mergeOption{title: "Squash and merge", method: PullRequestMergeMethodSquash}
mergeOpts = append(mergeOpts, opt)
}
@ -318,7 +355,6 @@ func mergeMethodSurvey(baseRepo *api.Repository) (api.PullRequestMergeMethod, er
mergeQuestion := &survey.Select{
Message: "What merge method would you like to use?",
Options: surveyOpts,
Default: "Create a merge commit",
}
var result int

View file

@ -42,7 +42,7 @@ func Test_NewCmdMerge(t *testing.T) {
DeleteBranch: false,
IsDeleteBranchIndicated: false,
CanDeleteLocalBranch: true,
MergeMethod: api.PullRequestMergeMethodMerge,
MergeMethod: PullRequestMergeMethodMerge,
InteractiveMode: true,
},
},
@ -55,7 +55,7 @@ func Test_NewCmdMerge(t *testing.T) {
DeleteBranch: false,
IsDeleteBranchIndicated: true,
CanDeleteLocalBranch: true,
MergeMethod: api.PullRequestMergeMethodMerge,
MergeMethod: PullRequestMergeMethodMerge,
InteractiveMode: true,
},
},
@ -68,7 +68,7 @@ func Test_NewCmdMerge(t *testing.T) {
DeleteBranch: false,
IsDeleteBranchIndicated: false,
CanDeleteLocalBranch: true,
MergeMethod: api.PullRequestMergeMethodMerge,
MergeMethod: PullRequestMergeMethodMerge,
InteractiveMode: true,
Body: "cool",
},
@ -787,5 +787,5 @@ func Test_mergeMethodSurvey(t *testing.T) {
as.StubOne(0) // Select first option which is rebase merge
method, err := mergeMethodSurvey(repo)
assert.Nil(t, err)
assert.Equal(t, api.PullRequestMergeMethodRebase, method)
assert.Equal(t, PullRequestMergeMethodRebase, method)
}