You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

404 lines
14 KiB

  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. package github
  6. import (
  7. "bytes"
  8. "context"
  9. "fmt"
  10. "strings"
  11. "time"
  12. )
  13. // PullRequestsService handles communication with the pull request related
  14. // methods of the GitHub API.
  15. //
  16. // GitHub API docs: https://developer.github.com/v3/pulls/
  17. type PullRequestsService service
  18. // PullRequest represents a GitHub pull request on a repository.
  19. type PullRequest struct {
  20. ID *int64 `json:"id,omitempty"`
  21. Number *int `json:"number,omitempty"`
  22. State *string `json:"state,omitempty"`
  23. Title *string `json:"title,omitempty"`
  24. Body *string `json:"body,omitempty"`
  25. CreatedAt *time.Time `json:"created_at,omitempty"`
  26. UpdatedAt *time.Time `json:"updated_at,omitempty"`
  27. ClosedAt *time.Time `json:"closed_at,omitempty"`
  28. MergedAt *time.Time `json:"merged_at,omitempty"`
  29. Labels []*Label `json:"labels,omitempty"`
  30. User *User `json:"user,omitempty"`
  31. Draft *bool `json:"draft,omitempty"`
  32. Merged *bool `json:"merged,omitempty"`
  33. Mergeable *bool `json:"mergeable,omitempty"`
  34. MergeableState *string `json:"mergeable_state,omitempty"`
  35. MergedBy *User `json:"merged_by,omitempty"`
  36. MergeCommitSHA *string `json:"merge_commit_sha,omitempty"`
  37. Comments *int `json:"comments,omitempty"`
  38. Commits *int `json:"commits,omitempty"`
  39. Additions *int `json:"additions,omitempty"`
  40. Deletions *int `json:"deletions,omitempty"`
  41. ChangedFiles *int `json:"changed_files,omitempty"`
  42. URL *string `json:"url,omitempty"`
  43. HTMLURL *string `json:"html_url,omitempty"`
  44. IssueURL *string `json:"issue_url,omitempty"`
  45. StatusesURL *string `json:"statuses_url,omitempty"`
  46. DiffURL *string `json:"diff_url,omitempty"`
  47. PatchURL *string `json:"patch_url,omitempty"`
  48. CommitsURL *string `json:"commits_url,omitempty"`
  49. CommentsURL *string `json:"comments_url,omitempty"`
  50. ReviewCommentsURL *string `json:"review_comments_url,omitempty"`
  51. ReviewCommentURL *string `json:"review_comment_url,omitempty"`
  52. ReviewComments *int `json:"review_comments,omitempty"`
  53. Assignee *User `json:"assignee,omitempty"`
  54. Assignees []*User `json:"assignees,omitempty"`
  55. Milestone *Milestone `json:"milestone,omitempty"`
  56. MaintainerCanModify *bool `json:"maintainer_can_modify,omitempty"`
  57. AuthorAssociation *string `json:"author_association,omitempty"`
  58. NodeID *string `json:"node_id,omitempty"`
  59. RequestedReviewers []*User `json:"requested_reviewers,omitempty"`
  60. // RequestedTeams is populated as part of the PullRequestEvent.
  61. // See, https://developer.github.com/v3/activity/events/types/#pullrequestevent for an example.
  62. RequestedTeams []*Team `json:"requested_teams,omitempty"`
  63. Links *PRLinks `json:"_links,omitempty"`
  64. Head *PullRequestBranch `json:"head,omitempty"`
  65. Base *PullRequestBranch `json:"base,omitempty"`
  66. // ActiveLockReason is populated only when LockReason is provided while locking the pull request.
  67. // Possible values are: "off-topic", "too heated", "resolved", and "spam".
  68. ActiveLockReason *string `json:"active_lock_reason,omitempty"`
  69. }
  70. func (p PullRequest) String() string {
  71. return Stringify(p)
  72. }
  73. // PRLink represents a single link object from Github pull request _links.
  74. type PRLink struct {
  75. HRef *string `json:"href,omitempty"`
  76. }
  77. // PRLinks represents the "_links" object in a Github pull request.
  78. type PRLinks struct {
  79. Self *PRLink `json:"self,omitempty"`
  80. HTML *PRLink `json:"html,omitempty"`
  81. Issue *PRLink `json:"issue,omitempty"`
  82. Comments *PRLink `json:"comments,omitempty"`
  83. ReviewComments *PRLink `json:"review_comments,omitempty"`
  84. ReviewComment *PRLink `json:"review_comment,omitempty"`
  85. Commits *PRLink `json:"commits,omitempty"`
  86. Statuses *PRLink `json:"statuses,omitempty"`
  87. }
  88. // PullRequestBranch represents a base or head branch in a GitHub pull request.
  89. type PullRequestBranch struct {
  90. Label *string `json:"label,omitempty"`
  91. Ref *string `json:"ref,omitempty"`
  92. SHA *string `json:"sha,omitempty"`
  93. Repo *Repository `json:"repo,omitempty"`
  94. User *User `json:"user,omitempty"`
  95. }
  96. // PullRequestListOptions specifies the optional parameters to the
  97. // PullRequestsService.List method.
  98. type PullRequestListOptions struct {
  99. // State filters pull requests based on their state. Possible values are:
  100. // open, closed, all. Default is "open".
  101. State string `url:"state,omitempty"`
  102. // Head filters pull requests by head user and branch name in the format of:
  103. // "user:ref-name".
  104. Head string `url:"head,omitempty"`
  105. // Base filters pull requests by base branch name.
  106. Base string `url:"base,omitempty"`
  107. // Sort specifies how to sort pull requests. Possible values are: created,
  108. // updated, popularity, long-running. Default is "created".
  109. Sort string `url:"sort,omitempty"`
  110. // Direction in which to sort pull requests. Possible values are: asc, desc.
  111. // If Sort is "created" or not specified, Default is "desc", otherwise Default
  112. // is "asc"
  113. Direction string `url:"direction,omitempty"`
  114. ListOptions
  115. }
  116. // List the pull requests for the specified repository.
  117. //
  118. // GitHub API docs: https://developer.github.com/v3/pulls/#list-pull-requests
  119. func (s *PullRequestsService) List(ctx context.Context, owner string, repo string, opt *PullRequestListOptions) ([]*PullRequest, *Response, error) {
  120. u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo)
  121. u, err := addOptions(u, opt)
  122. if err != nil {
  123. return nil, nil, err
  124. }
  125. req, err := s.client.NewRequest("GET", u, nil)
  126. if err != nil {
  127. return nil, nil, err
  128. }
  129. // TODO: remove custom Accept header when this API fully launches.
  130. acceptHeaders := []string{mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview, mediaTypeDraftPreview}
  131. req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
  132. var pulls []*PullRequest
  133. resp, err := s.client.Do(ctx, req, &pulls)
  134. if err != nil {
  135. return nil, resp, err
  136. }
  137. return pulls, resp, nil
  138. }
  139. // Get a single pull request.
  140. //
  141. // GitHub API docs: https://developer.github.com/v3/pulls/#get-a-single-pull-request
  142. func (s *PullRequestsService) Get(ctx context.Context, owner string, repo string, number int) (*PullRequest, *Response, error) {
  143. u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
  144. req, err := s.client.NewRequest("GET", u, nil)
  145. if err != nil {
  146. return nil, nil, err
  147. }
  148. // TODO: remove custom Accept header when this API fully launches.
  149. acceptHeaders := []string{mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview, mediaTypeDraftPreview}
  150. req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
  151. pull := new(PullRequest)
  152. resp, err := s.client.Do(ctx, req, pull)
  153. if err != nil {
  154. return nil, resp, err
  155. }
  156. return pull, resp, nil
  157. }
  158. // GetRaw gets a single pull request in raw (diff or patch) format.
  159. func (s *PullRequestsService) GetRaw(ctx context.Context, owner string, repo string, number int, opt RawOptions) (string, *Response, error) {
  160. u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
  161. req, err := s.client.NewRequest("GET", u, nil)
  162. if err != nil {
  163. return "", nil, err
  164. }
  165. switch opt.Type {
  166. case Diff:
  167. req.Header.Set("Accept", mediaTypeV3Diff)
  168. case Patch:
  169. req.Header.Set("Accept", mediaTypeV3Patch)
  170. default:
  171. return "", nil, fmt.Errorf("unsupported raw type %d", opt.Type)
  172. }
  173. var buf bytes.Buffer
  174. resp, err := s.client.Do(ctx, req, &buf)
  175. if err != nil {
  176. return "", resp, err
  177. }
  178. return buf.String(), resp, nil
  179. }
  180. // NewPullRequest represents a new pull request to be created.
  181. type NewPullRequest struct {
  182. Title *string `json:"title,omitempty"`
  183. Head *string `json:"head,omitempty"`
  184. Base *string `json:"base,omitempty"`
  185. Body *string `json:"body,omitempty"`
  186. Issue *int `json:"issue,omitempty"`
  187. MaintainerCanModify *bool `json:"maintainer_can_modify,omitempty"`
  188. }
  189. // Create a new pull request on the specified repository.
  190. //
  191. // GitHub API docs: https://developer.github.com/v3/pulls/#create-a-pull-request
  192. func (s *PullRequestsService) Create(ctx context.Context, owner string, repo string, pull *NewPullRequest) (*PullRequest, *Response, error) {
  193. u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo)
  194. req, err := s.client.NewRequest("POST", u, pull)
  195. if err != nil {
  196. return nil, nil, err
  197. }
  198. // TODO: remove custom Accept header when this API fully launches.
  199. req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview)
  200. p := new(PullRequest)
  201. resp, err := s.client.Do(ctx, req, p)
  202. if err != nil {
  203. return nil, resp, err
  204. }
  205. return p, resp, nil
  206. }
  207. type pullRequestUpdate struct {
  208. Title *string `json:"title,omitempty"`
  209. Body *string `json:"body,omitempty"`
  210. State *string `json:"state,omitempty"`
  211. Base *string `json:"base,omitempty"`
  212. MaintainerCanModify *bool `json:"maintainer_can_modify,omitempty"`
  213. }
  214. // Edit a pull request.
  215. // pull must not be nil.
  216. //
  217. // The following fields are editable: Title, Body, State, Base.Ref and MaintainerCanModify.
  218. // Base.Ref updates the base branch of the pull request.
  219. //
  220. // GitHub API docs: https://developer.github.com/v3/pulls/#update-a-pull-request
  221. func (s *PullRequestsService) Edit(ctx context.Context, owner string, repo string, number int, pull *PullRequest) (*PullRequest, *Response, error) {
  222. if pull == nil {
  223. return nil, nil, fmt.Errorf("pull must be provided")
  224. }
  225. u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
  226. update := &pullRequestUpdate{
  227. Title: pull.Title,
  228. Body: pull.Body,
  229. State: pull.State,
  230. MaintainerCanModify: pull.MaintainerCanModify,
  231. }
  232. if pull.Base != nil {
  233. update.Base = pull.Base.Ref
  234. }
  235. req, err := s.client.NewRequest("PATCH", u, update)
  236. if err != nil {
  237. return nil, nil, err
  238. }
  239. // TODO: remove custom Accept header when this API fully launches.
  240. acceptHeaders := []string{mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview}
  241. req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
  242. p := new(PullRequest)
  243. resp, err := s.client.Do(ctx, req, p)
  244. if err != nil {
  245. return nil, resp, err
  246. }
  247. return p, resp, nil
  248. }
  249. // ListCommits lists the commits in a pull request.
  250. //
  251. // GitHub API docs: https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
  252. func (s *PullRequestsService) ListCommits(ctx context.Context, owner string, repo string, number int, opt *ListOptions) ([]*RepositoryCommit, *Response, error) {
  253. u := fmt.Sprintf("repos/%v/%v/pulls/%d/commits", owner, repo, number)
  254. u, err := addOptions(u, opt)
  255. if err != nil {
  256. return nil, nil, err
  257. }
  258. req, err := s.client.NewRequest("GET", u, nil)
  259. if err != nil {
  260. return nil, nil, err
  261. }
  262. var commits []*RepositoryCommit
  263. resp, err := s.client.Do(ctx, req, &commits)
  264. if err != nil {
  265. return nil, resp, err
  266. }
  267. return commits, resp, nil
  268. }
  269. // ListFiles lists the files in a pull request.
  270. //
  271. // GitHub API docs: https://developer.github.com/v3/pulls/#list-pull-requests-files
  272. func (s *PullRequestsService) ListFiles(ctx context.Context, owner string, repo string, number int, opt *ListOptions) ([]*CommitFile, *Response, error) {
  273. u := fmt.Sprintf("repos/%v/%v/pulls/%d/files", owner, repo, number)
  274. u, err := addOptions(u, opt)
  275. if err != nil {
  276. return nil, nil, err
  277. }
  278. req, err := s.client.NewRequest("GET", u, nil)
  279. if err != nil {
  280. return nil, nil, err
  281. }
  282. var commitFiles []*CommitFile
  283. resp, err := s.client.Do(ctx, req, &commitFiles)
  284. if err != nil {
  285. return nil, resp, err
  286. }
  287. return commitFiles, resp, nil
  288. }
  289. // IsMerged checks if a pull request has been merged.
  290. //
  291. // GitHub API docs: https://developer.github.com/v3/pulls/#get-if-a-pull-request-has-been-merged
  292. func (s *PullRequestsService) IsMerged(ctx context.Context, owner string, repo string, number int) (bool, *Response, error) {
  293. u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number)
  294. req, err := s.client.NewRequest("GET", u, nil)
  295. if err != nil {
  296. return false, nil, err
  297. }
  298. resp, err := s.client.Do(ctx, req, nil)
  299. merged, err := parseBoolResponse(err)
  300. return merged, resp, err
  301. }
  302. // PullRequestMergeResult represents the result of merging a pull request.
  303. type PullRequestMergeResult struct {
  304. SHA *string `json:"sha,omitempty"`
  305. Merged *bool `json:"merged,omitempty"`
  306. Message *string `json:"message,omitempty"`
  307. }
  308. // PullRequestOptions lets you define how a pull request will be merged.
  309. type PullRequestOptions struct {
  310. CommitTitle string // Extra detail to append to automatic commit message. (Optional.)
  311. SHA string // SHA that pull request head must match to allow merge. (Optional.)
  312. // The merge method to use. Possible values include: "merge", "squash", and "rebase" with the default being merge. (Optional.)
  313. MergeMethod string
  314. }
  315. type pullRequestMergeRequest struct {
  316. CommitMessage string `json:"commit_message"`
  317. CommitTitle string `json:"commit_title,omitempty"`
  318. MergeMethod string `json:"merge_method,omitempty"`
  319. SHA string `json:"sha,omitempty"`
  320. }
  321. // Merge a pull request (Merge Button™).
  322. // commitMessage is the title for the automatic commit message.
  323. //
  324. // GitHub API docs: https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-buttontrade
  325. func (s *PullRequestsService) Merge(ctx context.Context, owner string, repo string, number int, commitMessage string, options *PullRequestOptions) (*PullRequestMergeResult, *Response, error) {
  326. u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number)
  327. pullRequestBody := &pullRequestMergeRequest{CommitMessage: commitMessage}
  328. if options != nil {
  329. pullRequestBody.CommitTitle = options.CommitTitle
  330. pullRequestBody.MergeMethod = options.MergeMethod
  331. pullRequestBody.SHA = options.SHA
  332. }
  333. req, err := s.client.NewRequest("PUT", u, pullRequestBody)
  334. if err != nil {
  335. return nil, nil, err
  336. }
  337. mergeResult := new(PullRequestMergeResult)
  338. resp, err := s.client.Do(ctx, req, mergeResult)
  339. if err != nil {
  340. return nil, resp, err
  341. }
  342. return mergeResult, resp, nil
  343. }