diff --git a/github/github-accessors.go b/github/github-accessors.go index 278ba074271..90abcbb2021 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -32286,6 +32286,110 @@ func (p *PullRequestListOptions) GetState() string { return p.State } +// GetExpectedHeadSHA returns the ExpectedHeadSHA field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncDetails) GetExpectedHeadSHA() string { + if p == nil || p.ExpectedHeadSHA == nil { + return "" + } + return *p.ExpectedHeadSHA +} + +// GetMergeAction returns the MergeAction field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncDetails) GetMergeAction() string { + if p == nil || p.MergeAction == nil { + return "" + } + return *p.MergeAction +} + +// GetMergeMethod returns the MergeMethod field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncDetails) GetMergeMethod() string { + if p == nil || p.MergeMethod == nil { + return "" + } + return *p.MergeMethod +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncDetails) GetMessage() string { + if p == nil || p.Message == nil { + return "" + } + return *p.Message +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncDetails) GetSHA() string { + if p == nil || p.SHA == nil { + return "" + } + return *p.SHA +} + +// GetUUID returns the UUID field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncDetails) GetUUID() string { + if p == nil || p.UUID == nil { + return "" + } + return *p.UUID +} + +// GetCommitMessage returns the CommitMessage field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncRequest) GetCommitMessage() string { + if p == nil || p.CommitMessage == nil { + return "" + } + return *p.CommitMessage +} + +// GetCommitTitle returns the CommitTitle field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncRequest) GetCommitTitle() string { + if p == nil || p.CommitTitle == nil { + return "" + } + return *p.CommitTitle +} + +// GetMergeAction returns the MergeAction field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncRequest) GetMergeAction() string { + if p == nil || p.MergeAction == nil { + return "" + } + return *p.MergeAction +} + +// GetMergeMethod returns the MergeMethod field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncRequest) GetMergeMethod() string { + if p == nil || p.MergeMethod == nil { + return "" + } + return *p.MergeMethod +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncRequest) GetSHA() string { + if p == nil || p.SHA == nil { + return "" + } + return *p.SHA +} + +// GetDetails returns the Details field. +func (p *PullRequestMergeAsyncResult) GetDetails() *PullRequestMergeAsyncDetails { + if p == nil { + return nil + } + return p.Details +} + +// GetStatus returns the Status field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeAsyncResult) GetStatus() string { + if p == nil || p.Status == nil { + return "" + } + return *p.Status +} + // GetMerged returns the Merged field if it's non-nil, zero value otherwise. func (p *PullRequestMergeResult) GetMerged() bool { if p == nil || p.Merged == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 40c8007ea4a..23610c96757 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -40504,6 +40504,146 @@ func TestPullRequestListOptions_GetState(tt *testing.T) { p.GetState() } +func TestPullRequestMergeAsyncDetails_GetExpectedHeadSHA(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncDetails{ExpectedHeadSHA: &zeroValue} + p.GetExpectedHeadSHA() + p = &PullRequestMergeAsyncDetails{} + p.GetExpectedHeadSHA() + p = nil + p.GetExpectedHeadSHA() +} + +func TestPullRequestMergeAsyncDetails_GetMergeAction(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncDetails{MergeAction: &zeroValue} + p.GetMergeAction() + p = &PullRequestMergeAsyncDetails{} + p.GetMergeAction() + p = nil + p.GetMergeAction() +} + +func TestPullRequestMergeAsyncDetails_GetMergeMethod(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncDetails{MergeMethod: &zeroValue} + p.GetMergeMethod() + p = &PullRequestMergeAsyncDetails{} + p.GetMergeMethod() + p = nil + p.GetMergeMethod() +} + +func TestPullRequestMergeAsyncDetails_GetMessage(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncDetails{Message: &zeroValue} + p.GetMessage() + p = &PullRequestMergeAsyncDetails{} + p.GetMessage() + p = nil + p.GetMessage() +} + +func TestPullRequestMergeAsyncDetails_GetSHA(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncDetails{SHA: &zeroValue} + p.GetSHA() + p = &PullRequestMergeAsyncDetails{} + p.GetSHA() + p = nil + p.GetSHA() +} + +func TestPullRequestMergeAsyncDetails_GetUUID(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncDetails{UUID: &zeroValue} + p.GetUUID() + p = &PullRequestMergeAsyncDetails{} + p.GetUUID() + p = nil + p.GetUUID() +} + +func TestPullRequestMergeAsyncRequest_GetCommitMessage(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncRequest{CommitMessage: &zeroValue} + p.GetCommitMessage() + p = &PullRequestMergeAsyncRequest{} + p.GetCommitMessage() + p = nil + p.GetCommitMessage() +} + +func TestPullRequestMergeAsyncRequest_GetCommitTitle(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncRequest{CommitTitle: &zeroValue} + p.GetCommitTitle() + p = &PullRequestMergeAsyncRequest{} + p.GetCommitTitle() + p = nil + p.GetCommitTitle() +} + +func TestPullRequestMergeAsyncRequest_GetMergeAction(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncRequest{MergeAction: &zeroValue} + p.GetMergeAction() + p = &PullRequestMergeAsyncRequest{} + p.GetMergeAction() + p = nil + p.GetMergeAction() +} + +func TestPullRequestMergeAsyncRequest_GetMergeMethod(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncRequest{MergeMethod: &zeroValue} + p.GetMergeMethod() + p = &PullRequestMergeAsyncRequest{} + p.GetMergeMethod() + p = nil + p.GetMergeMethod() +} + +func TestPullRequestMergeAsyncRequest_GetSHA(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncRequest{SHA: &zeroValue} + p.GetSHA() + p = &PullRequestMergeAsyncRequest{} + p.GetSHA() + p = nil + p.GetSHA() +} + +func TestPullRequestMergeAsyncResult_GetDetails(tt *testing.T) { + tt.Parallel() + p := &PullRequestMergeAsyncResult{} + p.GetDetails() + p = nil + p.GetDetails() +} + +func TestPullRequestMergeAsyncResult_GetStatus(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &PullRequestMergeAsyncResult{Status: &zeroValue} + p.GetStatus() + p = &PullRequestMergeAsyncResult{} + p.GetStatus() + p = nil + p.GetStatus() +} + func TestPullRequestMergeResult_GetMerged(tt *testing.T) { tt.Parallel() var zeroValue bool diff --git a/github/pulls.go b/github/pulls.go index 81bc9f89535..d5b616b4bac 100644 --- a/github/pulls.go +++ b/github/pulls.go @@ -514,6 +514,36 @@ type pullRequestMergeRequest struct { SHA string `json:"sha,omitempty"` } +// PullRequestMergeAsyncRequest represents a request to merge a pull request asynchronously. +type PullRequestMergeAsyncRequest struct { + // MergeMethod is the merge method: merge, squash, or rebase. Not supported on merge_queue actions. + MergeMethod *string `json:"merge_method,omitempty"` + // MergeAction is how to merge: default, direct_merge, or merge_queue. + MergeAction *string `json:"merge_action,omitempty"` + // CommitTitle is the title for the automatic commit message. Not supported on merge_queue actions. + CommitTitle *string `json:"commit_title,omitempty"` + // CommitMessage is extra detail to append to the automatic commit message. Not supported on merge_queue actions. + CommitMessage *string `json:"commit_message,omitempty"` + // SHA that the pull request head must match to allow the merge. + SHA *string `json:"sha,omitempty"` +} + +// PullRequestMergeAsyncResult represents the current state of an asynchronous merge request. +type PullRequestMergeAsyncResult struct { + Status *string `json:"status,omitempty"` + Details *PullRequestMergeAsyncDetails `json:"details,omitempty"` +} + +// PullRequestMergeAsyncDetails represents details for the current state of a PullRequestMergeAsyncResult. +type PullRequestMergeAsyncDetails struct { + Message *string `json:"message,omitempty"` + UUID *string `json:"uuid,omitempty"` + MergeMethod *string `json:"merge_method,omitempty"` + MergeAction *string `json:"merge_action,omitempty"` + ExpectedHeadSHA *string `json:"expected_head_sha,omitempty"` + SHA *string `json:"sha,omitempty"` +} + // Merge a pull request. // commitMessage is an extra detail to append to automatic commit message. // @@ -548,3 +578,56 @@ func (s *PullRequestsService) Merge(ctx context.Context, owner, repo string, num return mergeResult, resp, nil } + +// MergeAsync merges a pull request asynchronously. For stacked pull requests, +// this also merges everything below it in the stack. This is the required +// method for merging stacked pull requests; the legacy Merge method cannot be +// used for stacks. +// +// A pending response includes a UUID in PullRequestMergeAsyncResult.Details.UUID +// that must be passed to GetMergeAsyncResult to poll for the outcome. +// +// GitHub API docs: https://docs.github.com/rest/pulls/pulls?apiVersion=2022-11-28#merge-a-pull-request-asynchronously +// +//meta:operation PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge-async +func (s *PullRequestsService) MergeAsync(ctx context.Context, owner, repo string, number int, body PullRequestMergeAsyncRequest) (*PullRequestMergeAsyncResult, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%v/merge-async", owner, repo, number) + + req, err := s.client.NewRequest(ctx, "PUT", u, body) + if err != nil { + return nil, nil, err + } + + var result *PullRequestMergeAsyncResult + resp, err := s.client.Do(req, &result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetMergeAsyncResult fetches the current result of an asynchronous merge +// request, identified by the uuid returned when the merge was submitted via +// MergeAsync. Poll this method until the returned status is no longer +// "pending". Results are retained for 24 hours after their most recent update. +// +// GitHub API docs: https://docs.github.com/rest/pulls/pulls?apiVersion=2022-11-28#get-the-result-of-an-asynchronous-merge +// +//meta:operation GET /repos/{owner}/{repo}/pulls/{pull_number}/merge-async/{uuid} +func (s *PullRequestsService) GetMergeAsyncResult(ctx context.Context, owner, repo string, number int, uuid string) (*PullRequestMergeAsyncResult, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%v/merge-async/%v", owner, repo, number, uuid) + + req, err := s.client.NewRequest(ctx, "GET", u, nil) + if err != nil { + return nil, nil, err + } + + var result *PullRequestMergeAsyncResult + resp, err := s.client.Do(req, &result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} diff --git a/github/pulls_test.go b/github/pulls_test.go index 824eff8ac61..193194ac3c8 100644 --- a/github/pulls_test.go +++ b/github/pulls_test.go @@ -756,6 +756,115 @@ func TestPullRequestsService_Merge(t *testing.T) { }) } +func TestPullRequestsService_MergeAsync(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + request := PullRequestMergeAsyncRequest{ + MergeMethod: Ptr("squash"), + MergeAction: Ptr("default"), + } + + mux.HandleFunc("/repos/o/r/pulls/1/merge-async", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testJSONBody(t, r, request) + fmt.Fprint(w, ` + { + "status": "pending", + "details": { + "message": "Merge request enqueued.", + "uuid": "630b9d5e-3f2a-4f7e-8b0c-2d5f9a8c1e42", + "merge_method": "squash", + "merge_action": "default", + "expected_head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" + } + }`) + }) + + ctx := t.Context() + result, _, err := client.PullRequests.MergeAsync(ctx, "o", "r", 1, request) + if err != nil { + t.Errorf("PullRequests.MergeAsync returned error: %v", err) + } + + want := &PullRequestMergeAsyncResult{ + Status: Ptr("pending"), + Details: &PullRequestMergeAsyncDetails{ + Message: Ptr("Merge request enqueued."), + UUID: Ptr("630b9d5e-3f2a-4f7e-8b0c-2d5f9a8c1e42"), + MergeMethod: Ptr("squash"), + MergeAction: Ptr("default"), + ExpectedHeadSHA: Ptr("6dcb09b5b57875f334f61aebed695e2e4193db5e"), + }, + } + if !cmp.Equal(result, want) { + t.Errorf("PullRequests.MergeAsync returned %+v, want %+v", result, want) + } + + const methodName = "MergeAsync" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.PullRequests.MergeAsync(ctx, "\n", "\n", -1, request) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.PullRequests.MergeAsync(ctx, "o", "r", 1, request) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestPullRequestsService_GetMergeAsyncResult(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + const uuid = "630b9d5e-3f2a-4f7e-8b0c-2d5f9a8c1e42" + mux.HandleFunc(fmt.Sprintf("/repos/o/r/pulls/1/merge-async/%v", uuid), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + { + "status": "merged", + "details": { + "message": "Pull request was merged.", + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" + } + }`) + }) + + ctx := t.Context() + result, _, err := client.PullRequests.GetMergeAsyncResult(ctx, "o", "r", 1, uuid) + if err != nil { + t.Errorf("PullRequests.GetMergeAsyncResult returned error: %v", err) + } + + want := &PullRequestMergeAsyncResult{ + Status: Ptr("merged"), + Details: &PullRequestMergeAsyncDetails{ + Message: Ptr("Pull request was merged."), + SHA: Ptr("6dcb09b5b57875f334f61aebed695e2e4193db5e"), + }, + } + if !cmp.Equal(result, want) { + t.Errorf("PullRequests.GetMergeAsyncResult returned %+v, want %+v", result, want) + } + + const methodName = "GetMergeAsyncResult" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.PullRequests.GetMergeAsyncResult(ctx, "\n", "\n", -1, "\n") + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.PullRequests.GetMergeAsyncResult(ctx, "o", "r", 1, uuid) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + // Test that different merge options produce expected PUT requests. See issue https://github.com/google/go-github/issues/500. func TestPullRequestsService_Merge_options(t *testing.T) { t.Parallel()