gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repo
  4. import (
  5. "net/http"
  6. "net/url"
  7. "code.gitea.io/gitea/models/db"
  8. issues_model "code.gitea.io/gitea/models/issues"
  9. "code.gitea.io/gitea/models/renderhelper"
  10. "code.gitea.io/gitea/modules/markup/markdown"
  11. "code.gitea.io/gitea/modules/optional"
  12. "code.gitea.io/gitea/modules/setting"
  13. "code.gitea.io/gitea/modules/templates"
  14. "code.gitea.io/gitea/modules/web"
  15. "code.gitea.io/gitea/routers/common"
  16. "code.gitea.io/gitea/services/context"
  17. "code.gitea.io/gitea/services/forms"
  18. "code.gitea.io/gitea/services/issue"
  19. "xorm.io/builder"
  20. )
  21. const (
  22. tplMilestone templates.TplName = "repo/issue/milestones"
  23. tplMilestoneNew templates.TplName = "repo/issue/milestone_new"
  24. tplMilestoneIssues templates.TplName = "repo/issue/milestone_issues"
  25. )
  26. // Milestones render milestones page
  27. func Milestones(ctx *context.Context) {
  28. ctx.Data["Title"] = ctx.Tr("repo.milestones")
  29. ctx.Data["PageIsIssueList"] = true
  30. ctx.Data["PageIsMilestones"] = true
  31. isShowClosed := ctx.FormString("state") == "closed"
  32. sortType := ctx.FormString("sort")
  33. keyword := ctx.FormTrim("q")
  34. page := max(ctx.FormInt("page"), 1)
  35. miles, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
  36. ListOptions: db.ListOptions{
  37. Page: page,
  38. PageSize: setting.UI.IssuePagingNum,
  39. },
  40. RepoID: ctx.Repo.Repository.ID,
  41. IsClosed: optional.Some(isShowClosed),
  42. SortType: sortType,
  43. Name: keyword,
  44. })
  45. if err != nil {
  46. ctx.ServerError("GetMilestones", err)
  47. return
  48. }
  49. stats, err := issues_model.GetMilestonesStatsByRepoCondAndKw(ctx, builder.And(builder.Eq{"id": ctx.Repo.Repository.ID}), keyword)
  50. if err != nil {
  51. ctx.ServerError("GetMilestoneStats", err)
  52. return
  53. }
  54. ctx.Data["OpenCount"] = stats.OpenCount
  55. ctx.Data["ClosedCount"] = stats.ClosedCount
  56. if ctx.Repo.Repository.IsTimetrackerEnabled(ctx) {
  57. if err := issues_model.MilestoneList(miles).LoadTotalTrackedTimes(ctx); err != nil {
  58. ctx.ServerError("LoadTotalTrackedTimes", err)
  59. return
  60. }
  61. }
  62. for _, m := range miles {
  63. rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
  64. m.RenderedContent, err = markdown.RenderString(rctx, m.Content)
  65. if err != nil {
  66. ctx.ServerError("RenderString", err)
  67. return
  68. }
  69. }
  70. ctx.Data["Milestones"] = miles
  71. if isShowClosed {
  72. ctx.Data["State"] = "closed"
  73. } else {
  74. ctx.Data["State"] = "open"
  75. }
  76. ctx.Data["SortType"] = sortType
  77. ctx.Data["Keyword"] = keyword
  78. ctx.Data["IsShowClosed"] = isShowClosed
  79. pager := context.NewPagination(int(total), setting.UI.IssuePagingNum, page, 5)
  80. pager.AddParamFromRequest(ctx.Req)
  81. ctx.Data["Page"] = pager
  82. ctx.HTML(http.StatusOK, tplMilestone)
  83. }
  84. // NewMilestone render creating milestone page
  85. func NewMilestone(ctx *context.Context) {
  86. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  87. ctx.Data["PageIsIssueList"] = true
  88. ctx.Data["PageIsMilestones"] = true
  89. ctx.HTML(http.StatusOK, tplMilestoneNew)
  90. }
  91. // NewMilestonePost response for creating milestone
  92. func NewMilestonePost(ctx *context.Context) {
  93. form := web.GetForm(ctx).(*forms.CreateMilestoneForm)
  94. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  95. ctx.Data["PageIsIssueList"] = true
  96. ctx.Data["PageIsMilestones"] = true
  97. if ctx.HasError() {
  98. ctx.HTML(http.StatusOK, tplMilestoneNew)
  99. return
  100. }
  101. deadlineUnix, err := common.ParseDeadlineDateToEndOfDay(form.Deadline)
  102. if err != nil {
  103. ctx.Data["Err_Deadline"] = true
  104. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  105. return
  106. }
  107. if err := issues_model.NewMilestone(ctx, &issues_model.Milestone{
  108. RepoID: ctx.Repo.Repository.ID,
  109. Name: form.Title,
  110. Content: form.Content,
  111. DeadlineUnix: deadlineUnix,
  112. }); err != nil {
  113. ctx.ServerError("NewMilestone", err)
  114. return
  115. }
  116. ctx.Flash.Success(ctx.Tr("repo.milestones.create_success", form.Title))
  117. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  118. }
  119. // EditMilestone render edting milestone page
  120. func EditMilestone(ctx *context.Context) {
  121. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  122. ctx.Data["PageIsMilestones"] = true
  123. ctx.Data["PageIsEditMilestone"] = true
  124. m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
  125. if err != nil {
  126. if issues_model.IsErrMilestoneNotExist(err) {
  127. ctx.NotFound(nil)
  128. } else {
  129. ctx.ServerError("GetMilestoneByRepoID", err)
  130. }
  131. return
  132. }
  133. ctx.Data["title"] = m.Name
  134. ctx.Data["content"] = m.Content
  135. if len(m.DeadlineString) > 0 {
  136. ctx.Data["deadline"] = m.DeadlineString
  137. }
  138. ctx.HTML(http.StatusOK, tplMilestoneNew)
  139. }
  140. // EditMilestonePost response for edting milestone
  141. func EditMilestonePost(ctx *context.Context) {
  142. form := web.GetForm(ctx).(*forms.CreateMilestoneForm)
  143. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  144. ctx.Data["PageIsMilestones"] = true
  145. ctx.Data["PageIsEditMilestone"] = true
  146. if ctx.HasError() {
  147. ctx.HTML(http.StatusOK, tplMilestoneNew)
  148. return
  149. }
  150. deadlineUnix, err := common.ParseDeadlineDateToEndOfDay(form.Deadline)
  151. if err != nil {
  152. ctx.Data["Err_Deadline"] = true
  153. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  154. return
  155. }
  156. m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
  157. if err != nil {
  158. if issues_model.IsErrMilestoneNotExist(err) {
  159. ctx.NotFound(nil)
  160. } else {
  161. ctx.ServerError("GetMilestoneByRepoID", err)
  162. }
  163. return
  164. }
  165. m.Name = form.Title
  166. m.Content = form.Content
  167. m.DeadlineUnix = deadlineUnix
  168. if err = issues_model.UpdateMilestone(ctx, m, m.IsClosed); err != nil {
  169. ctx.ServerError("UpdateMilestone", err)
  170. return
  171. }
  172. ctx.Flash.Success(ctx.Tr("repo.milestones.edit_success", m.Name))
  173. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  174. }
  175. // ChangeMilestoneStatus response for change a milestone's status
  176. func ChangeMilestoneStatus(ctx *context.Context) {
  177. var toClose bool
  178. switch ctx.PathParam("action") {
  179. case "open":
  180. toClose = false
  181. case "close":
  182. toClose = true
  183. default:
  184. ctx.JSONRedirect(ctx.Repo.RepoLink + "/milestones")
  185. return
  186. }
  187. id := ctx.PathParamInt64("id")
  188. if err := issues_model.ChangeMilestoneStatusByRepoIDAndID(ctx, ctx.Repo.Repository.ID, id, toClose); err != nil {
  189. if issues_model.IsErrMilestoneNotExist(err) {
  190. ctx.NotFound(err)
  191. } else {
  192. ctx.ServerError("ChangeMilestoneStatusByIDAndRepoID", err)
  193. }
  194. return
  195. }
  196. ctx.JSONRedirect(ctx.Repo.RepoLink + "/milestones?state=" + url.QueryEscape(ctx.PathParam("action")))
  197. }
  198. // DeleteMilestone delete a milestone
  199. func DeleteMilestone(ctx *context.Context) {
  200. if err := issues_model.DeleteMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {
  201. ctx.Flash.Error("DeleteMilestoneByRepoID: " + err.Error())
  202. } else {
  203. ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success"))
  204. }
  205. ctx.JSONRedirect(ctx.Repo.RepoLink + "/milestones")
  206. }
  207. // MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone
  208. func MilestoneIssuesAndPulls(ctx *context.Context) {
  209. milestoneID := ctx.PathParamInt64("id")
  210. projectID := ctx.FormInt64("project")
  211. milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
  212. if err != nil {
  213. if issues_model.IsErrMilestoneNotExist(err) {
  214. ctx.NotFound(err)
  215. return
  216. }
  217. ctx.ServerError("GetMilestoneByID", err)
  218. return
  219. }
  220. rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
  221. milestone.RenderedContent, err = markdown.RenderString(rctx, milestone.Content)
  222. if err != nil {
  223. ctx.ServerError("RenderString", err)
  224. return
  225. }
  226. ctx.Data["Title"] = milestone.Name
  227. ctx.Data["Milestone"] = milestone
  228. prepareIssueFilterAndList(ctx, milestoneID, projectID, optional.None[bool]())
  229. ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
  230. ctx.Data["NewIssueChooseTemplate"] = len(ret.IssueTemplates) > 0
  231. ctx.Data["CanWriteIssues"] = ctx.Repo.CanWriteIssuesOrPulls(false)
  232. ctx.Data["CanWritePulls"] = ctx.Repo.CanWriteIssuesOrPulls(true)
  233. ctx.HTML(http.StatusOK, tplMilestoneIssues)
  234. }