1// Copyright 2016 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 6package github 7 8import ( 9 "context" 10 "errors" 11 "fmt" 12 "time" 13) 14 15var ErrMixedCommentStyles = errors.New("cannot use both position and side/line form comments") 16 17// PullRequestReview represents a review of a pull request. 18type PullRequestReview struct { 19 ID *int64 `json:"id,omitempty"` 20 NodeID *string `json:"node_id,omitempty"` 21 User *User `json:"user,omitempty"` 22 Body *string `json:"body,omitempty"` 23 SubmittedAt *time.Time `json:"submitted_at,omitempty"` 24 CommitID *string `json:"commit_id,omitempty"` 25 HTMLURL *string `json:"html_url,omitempty"` 26 PullRequestURL *string `json:"pull_request_url,omitempty"` 27 State *string `json:"state,omitempty"` 28 // AuthorAssociation is the comment author's relationship to the issue's repository. 29 // Possible values are "COLLABORATOR", "CONTRIBUTOR", "FIRST_TIMER", "FIRST_TIME_CONTRIBUTOR", "MEMBER", "OWNER", or "NONE". 30 AuthorAssociation *string `json:"author_association,omitempty"` 31} 32 33func (p PullRequestReview) String() string { 34 return Stringify(p) 35} 36 37// DraftReviewComment represents a comment part of the review. 38type DraftReviewComment struct { 39 Path *string `json:"path,omitempty"` 40 Position *int `json:"position,omitempty"` 41 Body *string `json:"body,omitempty"` 42 43 // The new comfort-fade-preview fields 44 StartSide *string `json:"start_side,omitempty"` 45 Side *string `json:"side,omitempty"` 46 StartLine *int `json:"start_line,omitempty"` 47 Line *int `json:"line,omitempty"` 48} 49 50func (c DraftReviewComment) String() string { 51 return Stringify(c) 52} 53 54// PullRequestReviewRequest represents a request to create a review. 55type PullRequestReviewRequest struct { 56 NodeID *string `json:"node_id,omitempty"` 57 CommitID *string `json:"commit_id,omitempty"` 58 Body *string `json:"body,omitempty"` 59 Event *string `json:"event,omitempty"` 60 Comments []*DraftReviewComment `json:"comments,omitempty"` 61} 62 63func (r PullRequestReviewRequest) String() string { 64 return Stringify(r) 65} 66 67func (r *PullRequestReviewRequest) isComfortFadePreview() (bool, error) { 68 var isCF *bool 69 for _, comment := range r.Comments { 70 if comment == nil { 71 continue 72 } 73 hasPos := comment.Position != nil 74 hasComfortFade := (comment.StartSide != nil) || (comment.Side != nil) || 75 (comment.StartLine != nil) || (comment.Line != nil) 76 77 switch { 78 case hasPos && hasComfortFade: 79 return false, ErrMixedCommentStyles 80 case hasPos && isCF != nil && *isCF: 81 return false, ErrMixedCommentStyles 82 case hasComfortFade && isCF != nil && !*isCF: 83 return false, ErrMixedCommentStyles 84 } 85 isCF = &hasComfortFade 86 } 87 if isCF != nil { 88 return *isCF, nil 89 } 90 return false, nil 91} 92 93// PullRequestReviewDismissalRequest represents a request to dismiss a review. 94type PullRequestReviewDismissalRequest struct { 95 Message *string `json:"message,omitempty"` 96} 97 98func (r PullRequestReviewDismissalRequest) String() string { 99 return Stringify(r) 100} 101 102// ListReviews lists all reviews on the specified pull request. 103// 104// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#list-reviews-for-a-pull-request 105func (s *PullRequestsService) ListReviews(ctx context.Context, owner, repo string, number int, opts *ListOptions) ([]*PullRequestReview, *Response, error) { 106 u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews", owner, repo, number) 107 u, err := addOptions(u, opts) 108 if err != nil { 109 return nil, nil, err 110 } 111 112 req, err := s.client.NewRequest("GET", u, nil) 113 if err != nil { 114 return nil, nil, err 115 } 116 117 var reviews []*PullRequestReview 118 resp, err := s.client.Do(ctx, req, &reviews) 119 if err != nil { 120 return nil, resp, err 121 } 122 123 return reviews, resp, nil 124} 125 126// GetReview fetches the specified pull request review. 127// 128// TODO: Follow up with GitHub support about an issue with this method's 129// returned error format and remove this comment once it's fixed. 130// Read more about it here - https://github.com/google/go-github/issues/540 131// 132// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#get-a-review-for-a-pull-request 133func (s *PullRequestsService) GetReview(ctx context.Context, owner, repo string, number int, reviewID int64) (*PullRequestReview, *Response, error) { 134 u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID) 135 136 req, err := s.client.NewRequest("GET", u, nil) 137 if err != nil { 138 return nil, nil, err 139 } 140 141 review := new(PullRequestReview) 142 resp, err := s.client.Do(ctx, req, review) 143 if err != nil { 144 return nil, resp, err 145 } 146 147 return review, resp, nil 148} 149 150// DeletePendingReview deletes the specified pull request pending review. 151// 152// TODO: Follow up with GitHub support about an issue with this method's 153// returned error format and remove this comment once it's fixed. 154// Read more about it here - https://github.com/google/go-github/issues/540 155// 156// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#delete-a-pending-review-for-a-pull-request 157func (s *PullRequestsService) DeletePendingReview(ctx context.Context, owner, repo string, number int, reviewID int64) (*PullRequestReview, *Response, error) { 158 u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID) 159 160 req, err := s.client.NewRequest("DELETE", u, nil) 161 if err != nil { 162 return nil, nil, err 163 } 164 165 review := new(PullRequestReview) 166 resp, err := s.client.Do(ctx, req, review) 167 if err != nil { 168 return nil, resp, err 169 } 170 171 return review, resp, nil 172} 173 174// ListReviewComments lists all the comments for the specified review. 175// 176// TODO: Follow up with GitHub support about an issue with this method's 177// returned error format and remove this comment once it's fixed. 178// Read more about it here - https://github.com/google/go-github/issues/540 179// 180// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#list-comments-for-a-pull-request-review 181func (s *PullRequestsService) ListReviewComments(ctx context.Context, owner, repo string, number int, reviewID int64, opts *ListOptions) ([]*PullRequestComment, *Response, error) { 182 u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/comments", owner, repo, number, reviewID) 183 u, err := addOptions(u, opts) 184 if err != nil { 185 return nil, nil, err 186 } 187 188 req, err := s.client.NewRequest("GET", u, nil) 189 if err != nil { 190 return nil, nil, err 191 } 192 193 var comments []*PullRequestComment 194 resp, err := s.client.Do(ctx, req, &comments) 195 if err != nil { 196 return nil, resp, err 197 } 198 199 return comments, resp, nil 200} 201 202// CreateReview creates a new review on the specified pull request. 203// 204// TODO: Follow up with GitHub support about an issue with this method's 205// returned error format and remove this comment once it's fixed. 206// Read more about it here - https://github.com/google/go-github/issues/540 207// 208// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#create-a-review-for-a-pull-request 209// 210// In order to use multi-line comments, you must use the "comfort fade" preview. 211// This replaces the use of the "Position" field in comments with 4 new fields: 212// [Start]Side, and [Start]Line. 213// These new fields must be used for ALL comments (including single-line), 214// with the following restrictions (empirically observed, so subject to change). 215// 216// For single-line "comfort fade" comments, you must use: 217// 218// Path: &path, // as before 219// Body: &body, // as before 220// Side: &"RIGHT" (or "LEFT") 221// Line: &123, // NOT THE SAME AS POSITION, this is an actual line number. 222// 223// If StartSide or StartLine is used with single-line comments, a 422 is returned. 224// 225// For multi-line "comfort fade" comments, you must use: 226// 227// Path: &path, // as before 228// Body: &body, // as before 229// StartSide: &"RIGHT" (or "LEFT") 230// Side: &"RIGHT" (or "LEFT") 231// StartLine: &120, 232// Line: &125, 233// 234// Suggested edits are made by commenting on the lines to replace, and including the 235// suggested edit in a block like this (it may be surrounded in non-suggestion markdown): 236// 237// ```suggestion 238// Use this instead. 239// It is waaaaaay better. 240// ``` 241func (s *PullRequestsService) CreateReview(ctx context.Context, owner, repo string, number int, review *PullRequestReviewRequest) (*PullRequestReview, *Response, error) { 242 u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews", owner, repo, number) 243 244 req, err := s.client.NewRequest("POST", u, review) 245 if err != nil { 246 return nil, nil, err 247 } 248 249 // Detect which style of review comment is being used. 250 if isCF, err := review.isComfortFadePreview(); err != nil { 251 return nil, nil, err 252 } else if isCF { 253 // If the review comments are using the comfort fade preview fields, 254 // then pass the comfort fade header. 255 req.Header.Set("Accept", mediaTypeMultiLineCommentsPreview) 256 } 257 258 r := new(PullRequestReview) 259 resp, err := s.client.Do(ctx, req, r) 260 if err != nil { 261 return nil, resp, err 262 } 263 264 return r, resp, nil 265} 266 267// UpdateReview updates the review summary on the specified pull request. 268// 269// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#update-a-review-for-a-pull-request 270func (s *PullRequestsService) UpdateReview(ctx context.Context, owner, repo string, number int, reviewID int64, body string) (*PullRequestReview, *Response, error) { 271 opts := &struct { 272 Body string `json:"body"` 273 }{Body: body} 274 u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID) 275 276 req, err := s.client.NewRequest("PUT", u, opts) 277 if err != nil { 278 return nil, nil, err 279 } 280 281 review := &PullRequestReview{} 282 resp, err := s.client.Do(ctx, req, review) 283 if err != nil { 284 return nil, resp, err 285 } 286 287 return review, resp, nil 288} 289 290// SubmitReview submits a specified review on the specified pull request. 291// 292// TODO: Follow up with GitHub support about an issue with this method's 293// returned error format and remove this comment once it's fixed. 294// Read more about it here - https://github.com/google/go-github/issues/540 295// 296// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#submit-a-review-for-a-pull-request 297func (s *PullRequestsService) SubmitReview(ctx context.Context, owner, repo string, number int, reviewID int64, review *PullRequestReviewRequest) (*PullRequestReview, *Response, error) { 298 u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/events", owner, repo, number, reviewID) 299 300 req, err := s.client.NewRequest("POST", u, review) 301 if err != nil { 302 return nil, nil, err 303 } 304 305 r := new(PullRequestReview) 306 resp, err := s.client.Do(ctx, req, r) 307 if err != nil { 308 return nil, resp, err 309 } 310 311 return r, resp, nil 312} 313 314// DismissReview dismisses a specified review on the specified pull request. 315// 316// TODO: Follow up with GitHub support about an issue with this method's 317// returned error format and remove this comment once it's fixed. 318// Read more about it here - https://github.com/google/go-github/issues/540 319// 320// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#dismiss-a-review-for-a-pull-request 321func (s *PullRequestsService) DismissReview(ctx context.Context, owner, repo string, number int, reviewID int64, review *PullRequestReviewDismissalRequest) (*PullRequestReview, *Response, error) { 322 u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/dismissals", owner, repo, number, reviewID) 323 324 req, err := s.client.NewRequest("PUT", u, review) 325 if err != nil { 326 return nil, nil, err 327 } 328 329 r := new(PullRequestReview) 330 resp, err := s.client.Do(ctx, req, r) 331 if err != nil { 332 return nil, resp, err 333 } 334 335 return r, resp, nil 336} 337