github.com/google/go-github/v60@v60.0.0/github/pulls.go (about)

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