...

Source file src/github.com/google/go-github/v45/github/pulls.go

Documentation: github.com/google/go-github/v45/github

     1  // Copyright 2013 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package github
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"fmt"
    12  	"time"
    13  )
    14  
    15  // PullRequestsService handles communication with the pull request related
    16  // methods of the GitHub API.
    17  //
    18  // GitHub API docs: https://docs.github.com/en/rest/pulls/
    19  type PullRequestsService service
    20  
    21  // PullRequestAutoMerge represents the "auto_merge" response for a PullRequest.
    22  type PullRequestAutoMerge struct {
    23  	EnabledBy     *User   `json:"enabled_by,omitempty"`
    24  	MergeMethod   *string `json:"merge_method,omitempty"`
    25  	CommitTitle   *string `json:"commit_title,omitempty"`
    26  	CommitMessage *string `json:"commit_message,omitempty"`
    27  }
    28  
    29  // PullRequest represents a GitHub pull request on a repository.
    30  type PullRequest struct {
    31  	ID                  *int64                `json:"id,omitempty"`
    32  	Number              *int                  `json:"number,omitempty"`
    33  	State               *string               `json:"state,omitempty"`
    34  	Locked              *bool                 `json:"locked,omitempty"`
    35  	Title               *string               `json:"title,omitempty"`
    36  	Body                *string               `json:"body,omitempty"`
    37  	CreatedAt           *time.Time            `json:"created_at,omitempty"`
    38  	UpdatedAt           *time.Time            `json:"updated_at,omitempty"`
    39  	ClosedAt            *time.Time            `json:"closed_at,omitempty"`
    40  	MergedAt            *time.Time            `json:"merged_at,omitempty"`
    41  	Labels              []*Label              `json:"labels,omitempty"`
    42  	User                *User                 `json:"user,omitempty"`
    43  	Draft               *bool                 `json:"draft,omitempty"`
    44  	Merged              *bool                 `json:"merged,omitempty"`
    45  	Mergeable           *bool                 `json:"mergeable,omitempty"`
    46  	MergeableState      *string               `json:"mergeable_state,omitempty"`
    47  	MergedBy            *User                 `json:"merged_by,omitempty"`
    48  	MergeCommitSHA      *string               `json:"merge_commit_sha,omitempty"`
    49  	Rebaseable          *bool                 `json:"rebaseable,omitempty"`
    50  	Comments            *int                  `json:"comments,omitempty"`
    51  	Commits             *int                  `json:"commits,omitempty"`
    52  	Additions           *int                  `json:"additions,omitempty"`
    53  	Deletions           *int                  `json:"deletions,omitempty"`
    54  	ChangedFiles        *int                  `json:"changed_files,omitempty"`
    55  	URL                 *string               `json:"url,omitempty"`
    56  	HTMLURL             *string               `json:"html_url,omitempty"`
    57  	IssueURL            *string               `json:"issue_url,omitempty"`
    58  	StatusesURL         *string               `json:"statuses_url,omitempty"`
    59  	DiffURL             *string               `json:"diff_url,omitempty"`
    60  	PatchURL            *string               `json:"patch_url,omitempty"`
    61  	CommitsURL          *string               `json:"commits_url,omitempty"`
    62  	CommentsURL         *string               `json:"comments_url,omitempty"`
    63  	ReviewCommentsURL   *string               `json:"review_comments_url,omitempty"`
    64  	ReviewCommentURL    *string               `json:"review_comment_url,omitempty"`
    65  	ReviewComments      *int                  `json:"review_comments,omitempty"`
    66  	Assignee            *User                 `json:"assignee,omitempty"`
    67  	Assignees           []*User               `json:"assignees,omitempty"`
    68  	Milestone           *Milestone            `json:"milestone,omitempty"`
    69  	MaintainerCanModify *bool                 `json:"maintainer_can_modify,omitempty"`
    70  	AuthorAssociation   *string               `json:"author_association,omitempty"`
    71  	NodeID              *string               `json:"node_id,omitempty"`
    72  	RequestedReviewers  []*User               `json:"requested_reviewers,omitempty"`
    73  	AutoMerge           *PullRequestAutoMerge `json:"auto_merge,omitempty"`
    74  
    75  	// RequestedTeams is populated as part of the PullRequestEvent.
    76  	// See, https://docs.github.com/en/developers/webhooks-and-events/github-event-types#pullrequestevent for an example.
    77  	RequestedTeams []*Team `json:"requested_teams,omitempty"`
    78  
    79  	Links *PRLinks           `json:"_links,omitempty"`
    80  	Head  *PullRequestBranch `json:"head,omitempty"`
    81  	Base  *PullRequestBranch `json:"base,omitempty"`
    82  
    83  	// ActiveLockReason is populated only when LockReason is provided while locking the pull request.
    84  	// Possible values are: "off-topic", "too heated", "resolved", and "spam".
    85  	ActiveLockReason *string `json:"active_lock_reason,omitempty"`
    86  }
    87  
    88  func (p PullRequest) String() string {
    89  	return Stringify(p)
    90  }
    91  
    92  // PRLink represents a single link object from GitHub pull request _links.
    93  type PRLink struct {
    94  	HRef *string `json:"href,omitempty"`
    95  }
    96  
    97  // PRLinks represents the "_links" object in a GitHub pull request.
    98  type PRLinks struct {
    99  	Self           *PRLink `json:"self,omitempty"`
   100  	HTML           *PRLink `json:"html,omitempty"`
   101  	Issue          *PRLink `json:"issue,omitempty"`
   102  	Comments       *PRLink `json:"comments,omitempty"`
   103  	ReviewComments *PRLink `json:"review_comments,omitempty"`
   104  	ReviewComment  *PRLink `json:"review_comment,omitempty"`
   105  	Commits        *PRLink `json:"commits,omitempty"`
   106  	Statuses       *PRLink `json:"statuses,omitempty"`
   107  }
   108  
   109  // PullRequestBranch represents a base or head branch in a GitHub pull request.
   110  type PullRequestBranch struct {
   111  	Label *string     `json:"label,omitempty"`
   112  	Ref   *string     `json:"ref,omitempty"`
   113  	SHA   *string     `json:"sha,omitempty"`
   114  	Repo  *Repository `json:"repo,omitempty"`
   115  	User  *User       `json:"user,omitempty"`
   116  }
   117  
   118  // PullRequestListOptions specifies the optional parameters to the
   119  // PullRequestsService.List method.
   120  type PullRequestListOptions struct {
   121  	// State filters pull requests based on their state. Possible values are:
   122  	// open, closed, all. Default is "open".
   123  	State string `url:"state,omitempty"`
   124  
   125  	// Head filters pull requests by head user and branch name in the format of:
   126  	// "user:ref-name".
   127  	Head string `url:"head,omitempty"`
   128  
   129  	// Base filters pull requests by base branch name.
   130  	Base string `url:"base,omitempty"`
   131  
   132  	// Sort specifies how to sort pull requests. Possible values are: created,
   133  	// updated, popularity, long-running. Default is "created".
   134  	Sort string `url:"sort,omitempty"`
   135  
   136  	// Direction in which to sort pull requests. Possible values are: asc, desc.
   137  	// If Sort is "created" or not specified, Default is "desc", otherwise Default
   138  	// is "asc"
   139  	Direction string `url:"direction,omitempty"`
   140  
   141  	ListOptions
   142  }
   143  
   144  // List the pull requests for the specified repository.
   145  //
   146  // GitHub API docs: https://docs.github.com/en/rest/pulls/pulls#list-pull-requests
   147  func (s *PullRequestsService) List(ctx context.Context, owner string, repo string, opts *PullRequestListOptions) ([]*PullRequest, *Response, error) {
   148  	u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo)
   149  	u, err := addOptions(u, opts)
   150  	if err != nil {
   151  		return nil, nil, err
   152  	}
   153  
   154  	req, err := s.client.NewRequest("GET", u, nil)
   155  	if err != nil {
   156  		return nil, nil, err
   157  	}
   158  
   159  	var pulls []*PullRequest
   160  	resp, err := s.client.Do(ctx, req, &pulls)
   161  	if err != nil {
   162  		return nil, resp, err
   163  	}
   164  
   165  	return pulls, resp, nil
   166  }
   167  
   168  // ListPullRequestsWithCommit returns pull requests associated with a commit SHA.
   169  //
   170  // The results may include open and closed pull requests.
   171  // By default, the PullRequestListOptions State filters for "open".
   172  //
   173  // GitHub API docs: https://docs.github.com/en/rest/commits/commits#list-pull-requests-associated-with-a-commit
   174  func (s *PullRequestsService) ListPullRequestsWithCommit(ctx context.Context, owner, repo, sha string, opts *PullRequestListOptions) ([]*PullRequest, *Response, error) {
   175  	u := fmt.Sprintf("repos/%v/%v/commits/%v/pulls", owner, repo, sha)
   176  	u, err := addOptions(u, opts)
   177  	if err != nil {
   178  		return nil, nil, err
   179  	}
   180  
   181  	req, err := s.client.NewRequest("GET", u, nil)
   182  	if err != nil {
   183  		return nil, nil, err
   184  	}
   185  
   186  	// TODO: remove custom Accept header when this API fully launches.
   187  	req.Header.Set("Accept", mediaTypeListPullsOrBranchesForCommitPreview)
   188  	var pulls []*PullRequest
   189  	resp, err := s.client.Do(ctx, req, &pulls)
   190  	if err != nil {
   191  		return nil, resp, err
   192  	}
   193  
   194  	return pulls, resp, nil
   195  }
   196  
   197  // Get a single pull request.
   198  //
   199  // GitHub API docs: https://docs.github.com/en/rest/pulls/pulls#get-a-pull-request
   200  func (s *PullRequestsService) Get(ctx context.Context, owner string, repo string, number int) (*PullRequest, *Response, error) {
   201  	u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
   202  	req, err := s.client.NewRequest("GET", u, nil)
   203  	if err != nil {
   204  		return nil, nil, err
   205  	}
   206  
   207  	pull := new(PullRequest)
   208  	resp, err := s.client.Do(ctx, req, pull)
   209  	if err != nil {
   210  		return nil, resp, err
   211  	}
   212  
   213  	return pull, resp, nil
   214  }
   215  
   216  // GetRaw gets a single pull request in raw (diff or patch) format.
   217  //
   218  // GitHub API docs: https://docs.github.com/en/rest/pulls/pulls#get-a-pull-request
   219  func (s *PullRequestsService) GetRaw(ctx context.Context, owner string, repo string, number int, opts RawOptions) (string, *Response, error) {
   220  	u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
   221  	req, err := s.client.NewRequest("GET", u, nil)
   222  	if err != nil {
   223  		return "", nil, err
   224  	}
   225  
   226  	switch opts.Type {
   227  	case Diff:
   228  		req.Header.Set("Accept", mediaTypeV3Diff)
   229  	case Patch:
   230  		req.Header.Set("Accept", mediaTypeV3Patch)
   231  	default:
   232  		return "", nil, fmt.Errorf("unsupported raw type %d", opts.Type)
   233  	}
   234  
   235  	var buf bytes.Buffer
   236  	resp, err := s.client.Do(ctx, req, &buf)
   237  	if err != nil {
   238  		return "", resp, err
   239  	}
   240  
   241  	return buf.String(), resp, nil
   242  }
   243  
   244  // NewPullRequest represents a new pull request to be created.
   245  type NewPullRequest struct {
   246  	Title               *string `json:"title,omitempty"`
   247  	Head                *string `json:"head,omitempty"`
   248  	Base                *string `json:"base,omitempty"`
   249  	Body                *string `json:"body,omitempty"`
   250  	Issue               *int    `json:"issue,omitempty"`
   251  	MaintainerCanModify *bool   `json:"maintainer_can_modify,omitempty"`
   252  	Draft               *bool   `json:"draft,omitempty"`
   253  }
   254  
   255  // Create a new pull request on the specified repository.
   256  //
   257  // GitHub API docs: https://docs.github.com/en/rest/pulls/pulls#create-a-pull-request
   258  func (s *PullRequestsService) Create(ctx context.Context, owner string, repo string, pull *NewPullRequest) (*PullRequest, *Response, error) {
   259  	u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo)
   260  	req, err := s.client.NewRequest("POST", u, pull)
   261  	if err != nil {
   262  		return nil, nil, err
   263  	}
   264  
   265  	p := new(PullRequest)
   266  	resp, err := s.client.Do(ctx, req, p)
   267  	if err != nil {
   268  		return nil, resp, err
   269  	}
   270  
   271  	return p, resp, nil
   272  }
   273  
   274  // PullRequestBranchUpdateOptions specifies the optional parameters to the
   275  // PullRequestsService.UpdateBranch method.
   276  type PullRequestBranchUpdateOptions struct {
   277  	// ExpectedHeadSHA specifies the most recent commit on the pull request's branch.
   278  	// Default value is the SHA of the pull request's current HEAD ref.
   279  	ExpectedHeadSHA *string `json:"expected_head_sha,omitempty"`
   280  }
   281  
   282  // PullRequestBranchUpdateResponse specifies the response of pull request branch update.
   283  type PullRequestBranchUpdateResponse struct {
   284  	Message *string `json:"message,omitempty"`
   285  	URL     *string `json:"url,omitempty"`
   286  }
   287  
   288  // UpdateBranch updates the pull request branch with latest upstream changes.
   289  //
   290  // This method might return an AcceptedError and a status code of
   291  // 202. This is because this is the status that GitHub returns to signify that
   292  // it has now scheduled the update of the pull request branch in a background task.
   293  // A follow up request, after a delay of a second or so, should result
   294  // in a successful request.
   295  //
   296  // GitHub API docs: https://docs.github.com/en/rest/pulls/pulls#update-a-pull-request-branch
   297  func (s *PullRequestsService) UpdateBranch(ctx context.Context, owner, repo string, number int, opts *PullRequestBranchUpdateOptions) (*PullRequestBranchUpdateResponse, *Response, error) {
   298  	u := fmt.Sprintf("repos/%v/%v/pulls/%d/update-branch", owner, repo, number)
   299  
   300  	req, err := s.client.NewRequest("PUT", u, opts)
   301  	if err != nil {
   302  		return nil, nil, err
   303  	}
   304  
   305  	// TODO: remove custom Accept header when this API fully launches.
   306  	req.Header.Set("Accept", mediaTypeUpdatePullRequestBranchPreview)
   307  
   308  	p := new(PullRequestBranchUpdateResponse)
   309  	resp, err := s.client.Do(ctx, req, p)
   310  	if err != nil {
   311  		return nil, resp, err
   312  	}
   313  
   314  	return p, resp, nil
   315  }
   316  
   317  type pullRequestUpdate struct {
   318  	Title               *string `json:"title,omitempty"`
   319  	Body                *string `json:"body,omitempty"`
   320  	State               *string `json:"state,omitempty"`
   321  	Base                *string `json:"base,omitempty"`
   322  	MaintainerCanModify *bool   `json:"maintainer_can_modify,omitempty"`
   323  }
   324  
   325  // Edit a pull request.
   326  // pull must not be nil.
   327  //
   328  // The following fields are editable: Title, Body, State, Base.Ref and MaintainerCanModify.
   329  // Base.Ref updates the base branch of the pull request.
   330  //
   331  // GitHub API docs: https://docs.github.com/en/rest/pulls/pulls#update-a-pull-request
   332  func (s *PullRequestsService) Edit(ctx context.Context, owner string, repo string, number int, pull *PullRequest) (*PullRequest, *Response, error) {
   333  	if pull == nil {
   334  		return nil, nil, fmt.Errorf("pull must be provided")
   335  	}
   336  
   337  	u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
   338  
   339  	update := &pullRequestUpdate{
   340  		Title:               pull.Title,
   341  		Body:                pull.Body,
   342  		State:               pull.State,
   343  		MaintainerCanModify: pull.MaintainerCanModify,
   344  	}
   345  	// avoid updating the base branch when closing the Pull Request
   346  	// - otherwise the GitHub API server returns a "Validation Failed" error:
   347  	// "Cannot change base branch of closed pull request".
   348  	if pull.Base != nil && pull.GetState() != "closed" {
   349  		update.Base = pull.Base.Ref
   350  	}
   351  
   352  	req, err := s.client.NewRequest("PATCH", u, update)
   353  	if err != nil {
   354  		return nil, nil, err
   355  	}
   356  
   357  	p := new(PullRequest)
   358  	resp, err := s.client.Do(ctx, req, p)
   359  	if err != nil {
   360  		return nil, resp, err
   361  	}
   362  
   363  	return p, resp, nil
   364  }
   365  
   366  // ListCommits lists the commits in a pull request.
   367  //
   368  // GitHub API docs: https://docs.github.com/en/rest/pulls/pulls#list-commits-on-a-pull-request
   369  func (s *PullRequestsService) ListCommits(ctx context.Context, owner string, repo string, number int, opts *ListOptions) ([]*RepositoryCommit, *Response, error) {
   370  	u := fmt.Sprintf("repos/%v/%v/pulls/%d/commits", owner, repo, number)
   371  	u, err := addOptions(u, opts)
   372  	if err != nil {
   373  		return nil, nil, err
   374  	}
   375  
   376  	req, err := s.client.NewRequest("GET", u, nil)
   377  	if err != nil {
   378  		return nil, nil, err
   379  	}
   380  
   381  	var commits []*RepositoryCommit
   382  	resp, err := s.client.Do(ctx, req, &commits)
   383  	if err != nil {
   384  		return nil, resp, err
   385  	}
   386  
   387  	return commits, resp, nil
   388  }
   389  
   390  // ListFiles lists the files in a pull request.
   391  //
   392  // GitHub API docs: https://docs.github.com/en/rest/pulls/pulls#list-pull-requests-files
   393  func (s *PullRequestsService) ListFiles(ctx context.Context, owner string, repo string, number int, opts *ListOptions) ([]*CommitFile, *Response, error) {
   394  	u := fmt.Sprintf("repos/%v/%v/pulls/%d/files", owner, repo, number)
   395  	u, err := addOptions(u, opts)
   396  	if err != nil {
   397  		return nil, nil, err
   398  	}
   399  
   400  	req, err := s.client.NewRequest("GET", u, nil)
   401  	if err != nil {
   402  		return nil, nil, err
   403  	}
   404  
   405  	var commitFiles []*CommitFile
   406  	resp, err := s.client.Do(ctx, req, &commitFiles)
   407  	if err != nil {
   408  		return nil, resp, err
   409  	}
   410  
   411  	return commitFiles, resp, nil
   412  }
   413  
   414  // IsMerged checks if a pull request has been merged.
   415  //
   416  // GitHub API docs: https://docs.github.com/en/rest/pulls/pulls#check-if-a-pull-request-has-been-merged
   417  func (s *PullRequestsService) IsMerged(ctx context.Context, owner string, repo string, number int) (bool, *Response, error) {
   418  	u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number)
   419  	req, err := s.client.NewRequest("GET", u, nil)
   420  	if err != nil {
   421  		return false, nil, err
   422  	}
   423  
   424  	resp, err := s.client.Do(ctx, req, nil)
   425  	merged, err := parseBoolResponse(err)
   426  	return merged, resp, err
   427  }
   428  
   429  // PullRequestMergeResult represents the result of merging a pull request.
   430  type PullRequestMergeResult struct {
   431  	SHA     *string `json:"sha,omitempty"`
   432  	Merged  *bool   `json:"merged,omitempty"`
   433  	Message *string `json:"message,omitempty"`
   434  }
   435  
   436  // PullRequestOptions lets you define how a pull request will be merged.
   437  type PullRequestOptions struct {
   438  	CommitTitle string // Title for the automatic commit message. (Optional.)
   439  	SHA         string // SHA that pull request head must match to allow merge. (Optional.)
   440  
   441  	// The merge method to use. Possible values include: "merge", "squash", and "rebase" with the default being merge. (Optional.)
   442  	MergeMethod string
   443  
   444  	// If false, an empty string commit message will use the default commit message. If true, an empty string commit message will be used.
   445  	DontDefaultIfBlank bool
   446  }
   447  
   448  type pullRequestMergeRequest struct {
   449  	CommitMessage *string `json:"commit_message,omitempty"`
   450  	CommitTitle   string  `json:"commit_title,omitempty"`
   451  	MergeMethod   string  `json:"merge_method,omitempty"`
   452  	SHA           string  `json:"sha,omitempty"`
   453  }
   454  
   455  // Merge a pull request.
   456  // commitMessage is an extra detail to append to automatic commit message.
   457  //
   458  // GitHub API docs: https://docs.github.com/en/rest/pulls/pulls#merge-a-pull-request
   459  func (s *PullRequestsService) Merge(ctx context.Context, owner string, repo string, number int, commitMessage string, options *PullRequestOptions) (*PullRequestMergeResult, *Response, error) {
   460  	u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number)
   461  
   462  	pullRequestBody := &pullRequestMergeRequest{}
   463  	if commitMessage != "" {
   464  		pullRequestBody.CommitMessage = &commitMessage
   465  	}
   466  	if options != nil {
   467  		pullRequestBody.CommitTitle = options.CommitTitle
   468  		pullRequestBody.MergeMethod = options.MergeMethod
   469  		pullRequestBody.SHA = options.SHA
   470  		if options.DontDefaultIfBlank && commitMessage == "" {
   471  			pullRequestBody.CommitMessage = &commitMessage
   472  		}
   473  	}
   474  	req, err := s.client.NewRequest("PUT", u, pullRequestBody)
   475  	if err != nil {
   476  		return nil, nil, err
   477  	}
   478  
   479  	mergeResult := new(PullRequestMergeResult)
   480  	resp, err := s.client.Do(ctx, req, mergeResult)
   481  	if err != nil {
   482  		return nil, resp, err
   483  	}
   484  
   485  	return mergeResult, resp, nil
   486  }
   487  

View as plain text