@@ -208,6 +208,105 @@ func GetPullReviewComments(ctx *context.APIContext) {
208208 ctx .JSON (http .StatusOK , apiComments )
209209}
210210
211+ // CreatePullReviewCommentReply creates a reply to a pull request review comment
212+ func CreatePullReviewCommentReply (ctx * context.APIContext ) {
213+ // swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/comments/{id}/replies repository repoCreatePullReviewCommentReply
214+ // ---
215+ // summary: Reply to a pull request review comment
216+ // consumes:
217+ // - application/json
218+ // produces:
219+ // - application/json
220+ // parameters:
221+ // - name: owner
222+ // in: path
223+ // description: owner of the repo
224+ // type: string
225+ // required: true
226+ // - name: repo
227+ // in: path
228+ // description: name of the repo
229+ // type: string
230+ // required: true
231+ // - name: index
232+ // in: path
233+ // description: index of the pull request
234+ // type: integer
235+ // format: int64
236+ // required: true
237+ // - name: id
238+ // in: path
239+ // description: id of the review comment to reply to
240+ // type: integer
241+ // format: int64
242+ // required: true
243+ // - name: body
244+ // in: body
245+ // required: true
246+ // schema:
247+ // "$ref": "#/definitions/CreatePullReviewCommentReplyOptions"
248+ // responses:
249+ // "201":
250+ // "$ref": "#/responses/PullReviewComment"
251+ // "404":
252+ // "$ref": "#/responses/notFound"
253+ // "422":
254+ // "$ref": "#/responses/validationError"
255+
256+ opts := web .GetForm (ctx ).(* api.CreatePullReviewCommentReplyOptions )
257+
258+ pr , err := issues_model .GetPullRequestByIndex (ctx , ctx .Repo .Repository .ID , ctx .PathParamInt64 ("index" ))
259+ if err != nil {
260+ if issues_model .IsErrPullRequestNotExist (err ) {
261+ ctx .APIErrorNotFound ("GetPullRequestByIndex" , err )
262+ } else {
263+ ctx .APIErrorInternal (err )
264+ }
265+ return
266+ }
267+ if err := pr .LoadIssue (ctx ); err != nil {
268+ ctx .APIErrorInternal (err )
269+ return
270+ }
271+
272+ parent , err := issues_model .GetCommentByID (ctx , ctx .PathParamInt64 ("id" ))
273+ if err != nil {
274+ if issues_model .IsErrCommentNotExist (err ) {
275+ ctx .APIErrorNotFound ()
276+ } else {
277+ ctx .APIErrorInternal (err )
278+ }
279+ return
280+ }
281+ if parent .IssueID != pr .IssueID {
282+ ctx .APIErrorNotFound ()
283+ return
284+ }
285+ if parent .Type != issues_model .CommentTypeCode || parent .ReviewID == 0 {
286+ ctx .APIError (http .StatusUnprocessableEntity , "comment is not a review comment" )
287+ return
288+ }
289+
290+ comment , err := pull_service .CreateCodeComment (ctx ,
291+ ctx .Doer , ctx .Repo .GitRepo , pr .Issue ,
292+ parent .Line , opts .Body , parent .TreePath ,
293+ false , // not pending — replies attach directly to the parent review
294+ parent .ReviewID , // reply target
295+ "" , nil ,
296+ )
297+ if err != nil {
298+ ctx .APIErrorInternal (err )
299+ return
300+ }
301+ if err := comment .LoadPoster (ctx ); err != nil {
302+ ctx .APIErrorInternal (err )
303+ return
304+ }
305+ comment .Issue = pr .Issue
306+
307+ ctx .JSON (http .StatusCreated , convert .ToPullReviewComment (ctx , comment , ctx .Doer ))
308+ }
309+
211310// ResolvePullReviewComment resolves a review comment in a pull request
212311func ResolvePullReviewComment (ctx * context.APIContext ) {
213312 // swagger:operation POST /repos/{owner}/{repo}/pulls/comments/{id}/resolve repository repoResolvePullReviewComment
@@ -465,64 +564,22 @@ func CreatePullReview(ctx *context.APIContext) {
465564 opts .CommitID = headCommitID
466565 }
467566
468- // resolve all in_reply_to IDs up front; replies in one request must target the same review
469- var replyReviewID int64
470- hasNonReplyComment := false
471- for _ , c := range opts .Comments {
472- if c .InReplyToID == 0 {
473- hasNonReplyComment = true
474- continue
475- }
476- comment , err := issues_model .GetCommentByID (ctx , c .InReplyToID )
477- if err != nil {
478- if issues_model .IsErrCommentNotExist (err ) {
479- ctx .APIErrorNotFound ()
480- } else {
481- ctx .APIErrorInternal (err )
482- }
483- return
484- }
485- if comment .IssueID != pr .IssueID {
486- ctx .APIErrorNotFound ()
487- return
488- }
489- if comment .Type != issues_model .CommentTypeCode || comment .ReviewID == 0 {
490- ctx .APIError (http .StatusUnprocessableEntity , "comment is not a review comment" )
491- return
492- }
493- if replyReviewID != 0 && comment .ReviewID != replyReviewID {
494- ctx .APIError (http .StatusUnprocessableEntity , "replies must be to comments in the same review" )
495- return
496- }
497- replyReviewID = comment .ReviewID
498- }
499-
500- // a reply-only request attaches to the target review without creating a new one
501- noNewContent := ! hasNonReplyComment && strings .TrimSpace (opts .Body ) == ""
502- isDecisiveEvent := reviewType == issues_model .ReviewTypeApprove || reviewType == issues_model .ReviewTypeReject
503- isReplyOnly := replyReviewID != 0 && noNewContent && ! isDecisiveEvent
504-
505567 // create review comments
506568 for _ , c := range opts .Comments {
507569 line := c .NewLineNum
508570 if c .OldLineNum > 0 {
509571 line = c .OldLineNum * - 1
510572 }
511573
512- var commentReviewID int64
513- if c .InReplyToID != 0 {
514- commentReviewID = replyReviewID
515- }
516-
517574 if _ , err := pull_service .CreateCodeComment (ctx ,
518575 ctx .Doer ,
519576 ctx .Repo .GitRepo ,
520577 pr .Issue ,
521578 line ,
522579 c .Body ,
523580 c .Path ,
524- commentReviewID == 0 , // pending
525- commentReviewID , // reply
581+ true , // pending review
582+ 0 , // no reply
526583 opts .CommitID ,
527584 nil ,
528585 ); err != nil {
@@ -531,24 +588,15 @@ func CreatePullReview(ctx *context.APIContext) {
531588 }
532589 }
533590
534- var review * issues_model.Review
535- if isReplyOnly {
536- review , err = issues_model .GetReviewByID (ctx , replyReviewID )
537- if err != nil {
591+ // create review and associate all pending review comments
592+ review , _ , err := pull_service .SubmitReview (ctx , ctx .Doer , ctx .Repo .GitRepo , pr .Issue , reviewType , opts .Body , opts .CommitID , nil )
593+ if err != nil {
594+ if errors .Is (err , pull_service .ErrSubmitReviewOnClosedPR ) {
595+ ctx .APIError (http .StatusUnprocessableEntity , err )
596+ } else {
538597 ctx .APIErrorInternal (err )
539- return
540- }
541- } else {
542- // create review and associate all pending review comments
543- review , _ , err = pull_service .SubmitReview (ctx , ctx .Doer , ctx .Repo .GitRepo , pr .Issue , reviewType , opts .Body , opts .CommitID , nil )
544- if err != nil {
545- if errors .Is (err , pull_service .ErrSubmitReviewOnClosedPR ) {
546- ctx .APIError (http .StatusUnprocessableEntity , err )
547- } else {
548- ctx .APIErrorInternal (err )
549- }
550- return
551598 }
599+ return
552600 }
553601
554602 // convert response
0 commit comments