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.

pull_review.go 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. "fmt"
  7. "net/http"
  8. "code.gitea.io/gitea/models"
  9. "code.gitea.io/gitea/modules/base"
  10. "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/log"
  12. "code.gitea.io/gitea/modules/web"
  13. "code.gitea.io/gitea/services/forms"
  14. pull_service "code.gitea.io/gitea/services/pull"
  15. )
  16. const (
  17. tplConversation base.TplName = "repo/diff/conversation"
  18. tplNewComment base.TplName = "repo/diff/new_comment"
  19. )
  20. // RenderNewCodeCommentForm will render the form for creating a new review comment
  21. func RenderNewCodeCommentForm(ctx *context.Context) {
  22. issue := GetActionIssue(ctx)
  23. if !issue.IsPull {
  24. return
  25. }
  26. currentReview, err := models.GetCurrentReview(ctx.User, issue)
  27. if err != nil && !models.IsErrReviewNotExist(err) {
  28. ctx.ServerError("GetCurrentReview", err)
  29. return
  30. }
  31. ctx.Data["PageIsPullFiles"] = true
  32. ctx.Data["Issue"] = issue
  33. ctx.Data["CurrentReview"] = currentReview
  34. pullHeadCommitID, err := ctx.Repo.GitRepo.GetRefCommitID(issue.PullRequest.GetGitRefName())
  35. if err != nil {
  36. ctx.ServerError("GetRefCommitID", err)
  37. return
  38. }
  39. ctx.Data["AfterCommitID"] = pullHeadCommitID
  40. ctx.HTML(http.StatusOK, tplNewComment)
  41. }
  42. // CreateCodeComment will create a code comment including an pending review if required
  43. func CreateCodeComment(ctx *context.Context) {
  44. form := web.GetForm(ctx).(*forms.CodeCommentForm)
  45. issue := GetActionIssue(ctx)
  46. if !issue.IsPull {
  47. return
  48. }
  49. if ctx.Written() {
  50. return
  51. }
  52. if ctx.HasError() {
  53. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  54. ctx.Redirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
  55. return
  56. }
  57. signedLine := form.Line
  58. if form.Side == "previous" {
  59. signedLine *= -1
  60. }
  61. comment, err := pull_service.CreateCodeComment(
  62. ctx.User,
  63. ctx.Repo.GitRepo,
  64. issue,
  65. signedLine,
  66. form.Content,
  67. form.TreePath,
  68. form.IsReview,
  69. form.Reply,
  70. form.LatestCommitID,
  71. )
  72. if err != nil {
  73. ctx.ServerError("CreateCodeComment", err)
  74. return
  75. }
  76. if comment == nil {
  77. log.Trace("Comment not created: %-v #%d[%d]", ctx.Repo.Repository, issue.Index, issue.ID)
  78. ctx.Redirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
  79. return
  80. }
  81. log.Trace("Comment created: %-v #%d[%d] Comment[%d]", ctx.Repo.Repository, issue.Index, issue.ID, comment.ID)
  82. if form.Origin == "diff" {
  83. renderConversation(ctx, comment)
  84. return
  85. }
  86. ctx.Redirect(comment.HTMLURL())
  87. }
  88. // UpdateResolveConversation add or remove an Conversation resolved mark
  89. func UpdateResolveConversation(ctx *context.Context) {
  90. origin := ctx.Query("origin")
  91. action := ctx.Query("action")
  92. commentID := ctx.QueryInt64("comment_id")
  93. comment, err := models.GetCommentByID(commentID)
  94. if err != nil {
  95. ctx.ServerError("GetIssueByID", err)
  96. return
  97. }
  98. if err = comment.LoadIssue(); err != nil {
  99. ctx.ServerError("comment.LoadIssue", err)
  100. return
  101. }
  102. var permResult bool
  103. if permResult, err = models.CanMarkConversation(comment.Issue, ctx.User); err != nil {
  104. ctx.ServerError("CanMarkConversation", err)
  105. return
  106. }
  107. if !permResult {
  108. ctx.Error(http.StatusForbidden)
  109. return
  110. }
  111. if !comment.Issue.IsPull {
  112. ctx.Error(http.StatusBadRequest)
  113. return
  114. }
  115. if action == "Resolve" || action == "UnResolve" {
  116. err = models.MarkConversation(comment, ctx.User, action == "Resolve")
  117. if err != nil {
  118. ctx.ServerError("MarkConversation", err)
  119. return
  120. }
  121. } else {
  122. ctx.Error(http.StatusBadRequest)
  123. return
  124. }
  125. if origin == "diff" {
  126. renderConversation(ctx, comment)
  127. return
  128. }
  129. ctx.JSON(http.StatusOK, map[string]interface{}{
  130. "ok": true,
  131. })
  132. }
  133. func renderConversation(ctx *context.Context, comment *models.Comment) {
  134. comments, err := models.FetchCodeCommentsByLine(comment.Issue, ctx.User, comment.TreePath, comment.Line)
  135. if err != nil {
  136. ctx.ServerError("FetchCodeCommentsByLine", err)
  137. return
  138. }
  139. ctx.Data["PageIsPullFiles"] = true
  140. ctx.Data["comments"] = comments
  141. ctx.Data["CanMarkConversation"] = true
  142. ctx.Data["Issue"] = comment.Issue
  143. if err = comment.Issue.LoadPullRequest(); err != nil {
  144. ctx.ServerError("comment.Issue.LoadPullRequest", err)
  145. return
  146. }
  147. pullHeadCommitID, err := ctx.Repo.GitRepo.GetRefCommitID(comment.Issue.PullRequest.GetGitRefName())
  148. if err != nil {
  149. ctx.ServerError("GetRefCommitID", err)
  150. return
  151. }
  152. ctx.Data["AfterCommitID"] = pullHeadCommitID
  153. ctx.HTML(http.StatusOK, tplConversation)
  154. }
  155. // SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist
  156. func SubmitReview(ctx *context.Context) {
  157. form := web.GetForm(ctx).(*forms.SubmitReviewForm)
  158. issue := GetActionIssue(ctx)
  159. if !issue.IsPull {
  160. return
  161. }
  162. if ctx.Written() {
  163. return
  164. }
  165. if ctx.HasError() {
  166. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  167. ctx.Redirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
  168. return
  169. }
  170. reviewType := form.ReviewType()
  171. switch reviewType {
  172. case models.ReviewTypeUnknown:
  173. ctx.ServerError("ReviewType", fmt.Errorf("unknown ReviewType: %s", form.Type))
  174. return
  175. // can not approve/reject your own PR
  176. case models.ReviewTypeApprove, models.ReviewTypeReject:
  177. if issue.IsPoster(ctx.User.ID) {
  178. var translated string
  179. if reviewType == models.ReviewTypeApprove {
  180. translated = ctx.Tr("repo.issues.review.self.approval")
  181. } else {
  182. translated = ctx.Tr("repo.issues.review.self.rejection")
  183. }
  184. ctx.Flash.Error(translated)
  185. ctx.Redirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
  186. return
  187. }
  188. }
  189. _, comm, err := pull_service.SubmitReview(ctx.User, ctx.Repo.GitRepo, issue, reviewType, form.Content, form.CommitID)
  190. if err != nil {
  191. if models.IsContentEmptyErr(err) {
  192. ctx.Flash.Error(ctx.Tr("repo.issues.review.content.empty"))
  193. ctx.Redirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
  194. } else {
  195. ctx.ServerError("SubmitReview", err)
  196. }
  197. return
  198. }
  199. ctx.Redirect(fmt.Sprintf("%s/pulls/%d#%s", ctx.Repo.RepoLink, issue.Index, comm.HashTag()))
  200. }
  201. // DismissReview dismissing stale review by repo admin
  202. func DismissReview(ctx *context.Context) {
  203. form := web.GetForm(ctx).(*forms.DismissReviewForm)
  204. comm, err := pull_service.DismissReview(form.ReviewID, form.Message, ctx.User, true)
  205. if err != nil {
  206. ctx.ServerError("pull_service.DismissReview", err)
  207. return
  208. }
  209. ctx.Redirect(fmt.Sprintf("%s/pulls/%d#%s", ctx.Repo.RepoLink, comm.Issue.Index, comm.HashTag()))
  210. }