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.

1107 lines
28 KiB

10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
Squashed commit of the following: commit 0afcb843d7ffd596991c4885cab768273a6eb42c Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 17:13:29 2016 -0600 Removed Upload stats as the upload table is just a temporary table commit 7ecd73ff5535612d79d471409173ee7f1fcfa157 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:42:41 2016 -0600 Fix for CodeMirror mode commit c29b9ab531e2e7af0fb5db24dc17e51027dd1174 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:03:33 2016 -0600 Made tabbing in editor use spaces commit 23af384c53206a8a40e11e45bf49d7a149c4adcd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:56:46 2016 -0600 Fix for data-url commit cfb8a97591cb6fc0a92e49563b7b764c524db0e9 Merge: 7fc8a89 991ce42 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:42:53 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit 7fc8a89cb495478225b02d613e647f99a1489634 Merge: fd3d86c c03d040 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:40:00 2016 -0600 Merge branch 'feature-create-and-edit-repo-file' of github.com:richmahn/gogs into feature-create-and-edit-repo-file commit fd3d86ca6bbc02cfda566a504ffd6b03db4f75ef Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:39:44 2016 -0600 Code cleanup commit c03d0401c1049eeeccc32ab1f9c3303c130be5ee Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 29 15:38:23 2016 -0600 Code cleanup commit 98e1206ccf9f9a4503c020e3a7830cf9f861dfae Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:36:01 2016 -0600 Code cleanup and fixes commit c2895dc742f25f8412879c9fa15e18f27f42f194 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:24:04 2016 -0600 Fixes per Unknwon's requests commit 6aa7e46b21ad4c96e562daa2eac26a8fb408f8ef Merge: 889e9fa ad7ea88 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 17:13:43 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go modules/setting/setting.go commit 889e9faf1bd8559a4979c8f46005d488c1a234d4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:09:18 2016 -0600 Fix in gogs.js commit 47603edf223f147b114be65f3bd27bc1e88827a5 Merge: bb57912 cf85e9e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:07:36 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit bb5791255867a71c11a77b639db050ad09c597a4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:02:18 2016 -0600 Update for using CodeMirror mode addon commit d10d128c51039be19e2af9c66c63db66a9f2ec6d Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 16:12:57 2016 -0600 Update for Edit commit 34a34982025144e3225e389f7849eb6273c1d576 Merge: fa1b752 1c7dcdd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 11:52:02 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go commit fa1b752be29cd455c5184ddac2ffe80b3489763e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 15 18:35:42 2016 -0600 Feature for editing, creating, uploading and deleting files
8 years ago
9 years ago
Squashed commit of the following: commit 0afcb843d7ffd596991c4885cab768273a6eb42c Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 17:13:29 2016 -0600 Removed Upload stats as the upload table is just a temporary table commit 7ecd73ff5535612d79d471409173ee7f1fcfa157 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:42:41 2016 -0600 Fix for CodeMirror mode commit c29b9ab531e2e7af0fb5db24dc17e51027dd1174 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:03:33 2016 -0600 Made tabbing in editor use spaces commit 23af384c53206a8a40e11e45bf49d7a149c4adcd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:56:46 2016 -0600 Fix for data-url commit cfb8a97591cb6fc0a92e49563b7b764c524db0e9 Merge: 7fc8a89 991ce42 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:42:53 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit 7fc8a89cb495478225b02d613e647f99a1489634 Merge: fd3d86c c03d040 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:40:00 2016 -0600 Merge branch 'feature-create-and-edit-repo-file' of github.com:richmahn/gogs into feature-create-and-edit-repo-file commit fd3d86ca6bbc02cfda566a504ffd6b03db4f75ef Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:39:44 2016 -0600 Code cleanup commit c03d0401c1049eeeccc32ab1f9c3303c130be5ee Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 29 15:38:23 2016 -0600 Code cleanup commit 98e1206ccf9f9a4503c020e3a7830cf9f861dfae Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:36:01 2016 -0600 Code cleanup and fixes commit c2895dc742f25f8412879c9fa15e18f27f42f194 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:24:04 2016 -0600 Fixes per Unknwon's requests commit 6aa7e46b21ad4c96e562daa2eac26a8fb408f8ef Merge: 889e9fa ad7ea88 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 17:13:43 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go modules/setting/setting.go commit 889e9faf1bd8559a4979c8f46005d488c1a234d4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:09:18 2016 -0600 Fix in gogs.js commit 47603edf223f147b114be65f3bd27bc1e88827a5 Merge: bb57912 cf85e9e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:07:36 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit bb5791255867a71c11a77b639db050ad09c597a4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:02:18 2016 -0600 Update for using CodeMirror mode addon commit d10d128c51039be19e2af9c66c63db66a9f2ec6d Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 16:12:57 2016 -0600 Update for Edit commit 34a34982025144e3225e389f7849eb6273c1d576 Merge: fa1b752 1c7dcdd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 11:52:02 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go commit fa1b752be29cd455c5184ddac2ffe80b3489763e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 15 18:35:42 2016 -0600 Feature for editing, creating, uploading and deleting files
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
Squashed commit of the following: commit 0afcb843d7ffd596991c4885cab768273a6eb42c Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 17:13:29 2016 -0600 Removed Upload stats as the upload table is just a temporary table commit 7ecd73ff5535612d79d471409173ee7f1fcfa157 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:42:41 2016 -0600 Fix for CodeMirror mode commit c29b9ab531e2e7af0fb5db24dc17e51027dd1174 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:03:33 2016 -0600 Made tabbing in editor use spaces commit 23af384c53206a8a40e11e45bf49d7a149c4adcd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:56:46 2016 -0600 Fix for data-url commit cfb8a97591cb6fc0a92e49563b7b764c524db0e9 Merge: 7fc8a89 991ce42 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:42:53 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit 7fc8a89cb495478225b02d613e647f99a1489634 Merge: fd3d86c c03d040 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:40:00 2016 -0600 Merge branch 'feature-create-and-edit-repo-file' of github.com:richmahn/gogs into feature-create-and-edit-repo-file commit fd3d86ca6bbc02cfda566a504ffd6b03db4f75ef Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:39:44 2016 -0600 Code cleanup commit c03d0401c1049eeeccc32ab1f9c3303c130be5ee Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 29 15:38:23 2016 -0600 Code cleanup commit 98e1206ccf9f9a4503c020e3a7830cf9f861dfae Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:36:01 2016 -0600 Code cleanup and fixes commit c2895dc742f25f8412879c9fa15e18f27f42f194 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:24:04 2016 -0600 Fixes per Unknwon's requests commit 6aa7e46b21ad4c96e562daa2eac26a8fb408f8ef Merge: 889e9fa ad7ea88 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 17:13:43 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go modules/setting/setting.go commit 889e9faf1bd8559a4979c8f46005d488c1a234d4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:09:18 2016 -0600 Fix in gogs.js commit 47603edf223f147b114be65f3bd27bc1e88827a5 Merge: bb57912 cf85e9e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:07:36 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit bb5791255867a71c11a77b639db050ad09c597a4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:02:18 2016 -0600 Update for using CodeMirror mode addon commit d10d128c51039be19e2af9c66c63db66a9f2ec6d Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 16:12:57 2016 -0600 Update for Edit commit 34a34982025144e3225e389f7849eb6273c1d576 Merge: fa1b752 1c7dcdd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 11:52:02 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go commit fa1b752be29cd455c5184ddac2ffe80b3489763e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 15 18:35:42 2016 -0600 Feature for editing, creating, uploading and deleting files
8 years ago
9 years ago
9 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. // Copyright 2014 The Gogs 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. "bytes"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "strings"
  12. "time"
  13. "github.com/Unknwon/com"
  14. "github.com/Unknwon/paginater"
  15. "code.gitea.io/git"
  16. "code.gitea.io/gitea/models"
  17. "code.gitea.io/gitea/modules/auth"
  18. "code.gitea.io/gitea/modules/base"
  19. "code.gitea.io/gitea/modules/context"
  20. "code.gitea.io/gitea/modules/log"
  21. "code.gitea.io/gitea/modules/markdown"
  22. "code.gitea.io/gitea/modules/notification"
  23. "code.gitea.io/gitea/modules/setting"
  24. "code.gitea.io/gitea/modules/util"
  25. )
  26. const (
  27. tplIssues base.TplName = "repo/issue/list"
  28. tplIssueNew base.TplName = "repo/issue/new"
  29. tplIssueView base.TplName = "repo/issue/view"
  30. tplMilestone base.TplName = "repo/issue/milestones"
  31. tplMilestoneNew base.TplName = "repo/issue/milestone_new"
  32. tplMilestoneEdit base.TplName = "repo/issue/milestone_edit"
  33. issueTemplateKey = "IssueTemplate"
  34. )
  35. var (
  36. // ErrFileTypeForbidden not allowed file type error
  37. ErrFileTypeForbidden = errors.New("File type is not allowed")
  38. // ErrTooManyFiles upload too many files
  39. ErrTooManyFiles = errors.New("Maximum number of files to upload exceeded")
  40. // IssueTemplateCandidates issue templates
  41. IssueTemplateCandidates = []string{
  42. "ISSUE_TEMPLATE.md",
  43. "issue_template.md",
  44. ".gitea/ISSUE_TEMPLATE.md",
  45. ".gitea/issue_template.md",
  46. ".github/ISSUE_TEMPLATE.md",
  47. ".github/issue_template.md",
  48. }
  49. )
  50. // MustEnableIssues check if repository enable internal issues
  51. func MustEnableIssues(ctx *context.Context) {
  52. if !ctx.Repo.Repository.EnableUnit(models.UnitTypeIssues) &&
  53. !ctx.Repo.Repository.EnableUnit(models.UnitTypeExternalTracker) {
  54. ctx.Handle(404, "MustEnableIssues", nil)
  55. return
  56. }
  57. unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
  58. if err == nil {
  59. ctx.Redirect(unit.ExternalTrackerConfig().ExternalTrackerURL)
  60. return
  61. }
  62. }
  63. // MustAllowPulls check if repository enable pull requests
  64. func MustAllowPulls(ctx *context.Context) {
  65. if !ctx.Repo.Repository.AllowsPulls() {
  66. ctx.Handle(404, "MustAllowPulls", nil)
  67. return
  68. }
  69. // User can send pull request if owns a forked repository.
  70. if ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID) {
  71. ctx.Repo.PullRequest.Allowed = true
  72. ctx.Repo.PullRequest.HeadInfo = ctx.User.Name + ":" + ctx.Repo.BranchName
  73. }
  74. }
  75. // Issues render issues page
  76. func Issues(ctx *context.Context) {
  77. isPullList := ctx.Params(":type") == "pulls"
  78. if isPullList {
  79. MustAllowPulls(ctx)
  80. if ctx.Written() {
  81. return
  82. }
  83. ctx.Data["Title"] = ctx.Tr("repo.pulls")
  84. ctx.Data["PageIsPullList"] = true
  85. } else {
  86. MustEnableIssues(ctx)
  87. if ctx.Written() {
  88. return
  89. }
  90. ctx.Data["Title"] = ctx.Tr("repo.issues")
  91. ctx.Data["PageIsIssueList"] = true
  92. }
  93. viewType := ctx.Query("type")
  94. sortType := ctx.Query("sort")
  95. types := []string{"all", "assigned", "created_by", "mentioned"}
  96. if !com.IsSliceContainsStr(types, viewType) {
  97. viewType = "all"
  98. }
  99. var (
  100. assigneeID = ctx.QueryInt64("assignee")
  101. posterID int64
  102. mentionedID int64
  103. forceEmpty bool
  104. )
  105. repo := ctx.Repo.Repository
  106. selectLabels := ctx.Query("labels")
  107. milestoneID := ctx.QueryInt64("milestone")
  108. isShowClosed := ctx.Query("state") == "closed"
  109. keyword := strings.Trim(ctx.Query("q"), " ")
  110. if bytes.Contains([]byte(keyword), []byte{0x00}) {
  111. keyword = ""
  112. }
  113. var issueIDs []int64
  114. var err error
  115. if len(keyword) > 0 {
  116. issueIDs, err = models.SearchIssuesByKeyword(repo.ID, keyword)
  117. if len(issueIDs) == 0 {
  118. forceEmpty = true
  119. }
  120. }
  121. var issueStats *models.IssueStats
  122. if forceEmpty {
  123. issueStats = &models.IssueStats{}
  124. } else {
  125. var err error
  126. issueStats, err = models.GetIssueStats(&models.IssueStatsOptions{
  127. RepoID: repo.ID,
  128. Labels: selectLabels,
  129. MilestoneID: milestoneID,
  130. AssigneeID: assigneeID,
  131. MentionedID: mentionedID,
  132. IsPull: isPullList,
  133. IssueIDs: issueIDs,
  134. })
  135. if err != nil {
  136. ctx.Error(500, "GetSearchIssueStats")
  137. return
  138. }
  139. }
  140. page := ctx.QueryInt("page")
  141. if page <= 1 {
  142. page = 1
  143. }
  144. var total int
  145. if !isShowClosed {
  146. total = int(issueStats.OpenCount)
  147. } else {
  148. total = int(issueStats.ClosedCount)
  149. }
  150. pager := paginater.New(total, setting.UI.IssuePagingNum, page, 5)
  151. ctx.Data["Page"] = pager
  152. var issues []*models.Issue
  153. if forceEmpty {
  154. issues = []*models.Issue{}
  155. } else {
  156. issues, err = models.Issues(&models.IssuesOptions{
  157. AssigneeID: assigneeID,
  158. RepoID: repo.ID,
  159. PosterID: posterID,
  160. MentionedID: mentionedID,
  161. MilestoneID: milestoneID,
  162. Page: pager.Current(),
  163. IsClosed: util.OptionalBoolOf(isShowClosed),
  164. IsPull: util.OptionalBoolOf(isPullList),
  165. Labels: selectLabels,
  166. SortType: sortType,
  167. IssueIDs: issueIDs,
  168. })
  169. if err != nil {
  170. ctx.Handle(500, "Issues", err)
  171. return
  172. }
  173. }
  174. // Get posters.
  175. for i := range issues {
  176. // Check read status
  177. if !ctx.IsSigned {
  178. issues[i].IsRead = true
  179. } else if err = issues[i].GetIsRead(ctx.User.ID); err != nil {
  180. ctx.Handle(500, "GetIsRead", err)
  181. return
  182. }
  183. }
  184. ctx.Data["Issues"] = issues
  185. // Get milestones.
  186. ctx.Data["Milestones"], err = models.GetMilestonesByRepoID(repo.ID)
  187. if err != nil {
  188. ctx.Handle(500, "GetAllRepoMilestones", err)
  189. return
  190. }
  191. // Get assignees.
  192. ctx.Data["Assignees"], err = repo.GetAssignees()
  193. if err != nil {
  194. ctx.Handle(500, "GetAssignees", err)
  195. return
  196. }
  197. if ctx.QueryInt64("assignee") == 0 {
  198. assigneeID = 0 // Reset ID to prevent unexpected selection of assignee.
  199. }
  200. ctx.Data["IssueStats"] = issueStats
  201. ctx.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64()
  202. ctx.Data["ViewType"] = viewType
  203. ctx.Data["SortType"] = sortType
  204. ctx.Data["MilestoneID"] = milestoneID
  205. ctx.Data["AssigneeID"] = assigneeID
  206. ctx.Data["IsShowClosed"] = isShowClosed
  207. ctx.Data["Keyword"] = keyword
  208. if isShowClosed {
  209. ctx.Data["State"] = "closed"
  210. } else {
  211. ctx.Data["State"] = "open"
  212. }
  213. ctx.HTML(200, tplIssues)
  214. }
  215. // RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository
  216. func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repository) {
  217. var err error
  218. ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false, "")
  219. if err != nil {
  220. ctx.Handle(500, "GetMilestones", err)
  221. return
  222. }
  223. ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true, "")
  224. if err != nil {
  225. ctx.Handle(500, "GetMilestones", err)
  226. return
  227. }
  228. ctx.Data["Assignees"], err = repo.GetAssignees()
  229. if err != nil {
  230. ctx.Handle(500, "GetAssignees", err)
  231. return
  232. }
  233. }
  234. // RetrieveRepoMetas find all the meta information of a repository
  235. func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.Label {
  236. if !ctx.Repo.IsWriter() {
  237. return nil
  238. }
  239. labels, err := models.GetLabelsByRepoID(repo.ID, "")
  240. if err != nil {
  241. ctx.Handle(500, "GetLabelsByRepoID", err)
  242. return nil
  243. }
  244. ctx.Data["Labels"] = labels
  245. RetrieveRepoMilestonesAndAssignees(ctx, repo)
  246. if ctx.Written() {
  247. return nil
  248. }
  249. return labels
  250. }
  251. func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (string, bool) {
  252. var r io.Reader
  253. var bytes []byte
  254. if ctx.Repo.Commit == nil {
  255. var err error
  256. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
  257. if err != nil {
  258. return "", false
  259. }
  260. }
  261. entry, err := ctx.Repo.Commit.GetTreeEntryByPath(filename)
  262. if err != nil {
  263. return "", false
  264. }
  265. r, err = entry.Blob().Data()
  266. if err != nil {
  267. return "", false
  268. }
  269. bytes, err = ioutil.ReadAll(r)
  270. if err != nil {
  271. return "", false
  272. }
  273. return string(bytes), true
  274. }
  275. func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string) {
  276. for _, filename := range possibleFiles {
  277. content, found := getFileContentFromDefaultBranch(ctx, filename)
  278. if found {
  279. ctx.Data[ctxDataKey] = content
  280. return
  281. }
  282. }
  283. }
  284. // NewIssue render createing issue page
  285. func NewIssue(ctx *context.Context) {
  286. ctx.Data["Title"] = ctx.Tr("repo.issues.new")
  287. ctx.Data["PageIsIssueList"] = true
  288. ctx.Data["RequireHighlightJS"] = true
  289. ctx.Data["RequireSimpleMDE"] = true
  290. setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates)
  291. renderAttachmentSettings(ctx)
  292. RetrieveRepoMetas(ctx, ctx.Repo.Repository)
  293. if ctx.Written() {
  294. return
  295. }
  296. ctx.HTML(200, tplIssueNew)
  297. }
  298. // ValidateRepoMetas check and returns repository's meta informations
  299. func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64, int64, int64) {
  300. var (
  301. repo = ctx.Repo.Repository
  302. err error
  303. )
  304. labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository)
  305. if ctx.Written() {
  306. return nil, 0, 0
  307. }
  308. if !ctx.Repo.IsWriter() {
  309. return nil, 0, 0
  310. }
  311. var labelIDs []int64
  312. hasSelected := false
  313. // Check labels.
  314. if len(form.LabelIDs) > 0 {
  315. labelIDs, err = base.StringsToInt64s(strings.Split(form.LabelIDs, ","))
  316. if err != nil {
  317. return nil, 0, 0
  318. }
  319. labelIDMark := base.Int64sToMap(labelIDs)
  320. for i := range labels {
  321. if labelIDMark[labels[i].ID] {
  322. labels[i].IsChecked = true
  323. hasSelected = true
  324. }
  325. }
  326. }
  327. ctx.Data["Labels"] = labels
  328. ctx.Data["HasSelectedLabel"] = hasSelected
  329. ctx.Data["label_ids"] = form.LabelIDs
  330. // Check milestone.
  331. milestoneID := form.MilestoneID
  332. if milestoneID > 0 {
  333. ctx.Data["Milestone"], err = repo.GetMilestoneByID(milestoneID)
  334. if err != nil {
  335. ctx.Handle(500, "GetMilestoneByID", err)
  336. return nil, 0, 0
  337. }
  338. ctx.Data["milestone_id"] = milestoneID
  339. }
  340. // Check assignee.
  341. assigneeID := form.AssigneeID
  342. if assigneeID > 0 {
  343. ctx.Data["Assignee"], err = repo.GetAssigneeByID(assigneeID)
  344. if err != nil {
  345. ctx.Handle(500, "GetAssigneeByID", err)
  346. return nil, 0, 0
  347. }
  348. ctx.Data["assignee_id"] = assigneeID
  349. }
  350. return labelIDs, milestoneID, assigneeID
  351. }
  352. // NewIssuePost response for creating new issue
  353. func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
  354. ctx.Data["Title"] = ctx.Tr("repo.issues.new")
  355. ctx.Data["PageIsIssueList"] = true
  356. ctx.Data["RequireHighlightJS"] = true
  357. ctx.Data["RequireSimpleMDE"] = true
  358. renderAttachmentSettings(ctx)
  359. var (
  360. repo = ctx.Repo.Repository
  361. attachments []string
  362. )
  363. labelIDs, milestoneID, assigneeID := ValidateRepoMetas(ctx, form)
  364. if ctx.Written() {
  365. return
  366. }
  367. if setting.AttachmentEnabled {
  368. attachments = form.Files
  369. }
  370. if ctx.HasError() {
  371. ctx.HTML(200, tplIssueNew)
  372. return
  373. }
  374. issue := &models.Issue{
  375. RepoID: repo.ID,
  376. Title: form.Title,
  377. PosterID: ctx.User.ID,
  378. Poster: ctx.User,
  379. MilestoneID: milestoneID,
  380. AssigneeID: assigneeID,
  381. Content: form.Content,
  382. }
  383. if err := models.NewIssue(repo, issue, labelIDs, attachments); err != nil {
  384. ctx.Handle(500, "NewIssue", err)
  385. return
  386. }
  387. notification.Service.NotifyIssue(issue, ctx.User.ID)
  388. log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
  389. ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
  390. }
  391. // ViewIssue render issue view page
  392. func ViewIssue(ctx *context.Context) {
  393. ctx.Data["RequireHighlightJS"] = true
  394. ctx.Data["RequireDropzone"] = true
  395. renderAttachmentSettings(ctx)
  396. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  397. if err != nil {
  398. if models.IsErrIssueNotExist(err) {
  399. ctx.Handle(404, "GetIssueByIndex", err)
  400. } else {
  401. ctx.Handle(500, "GetIssueByIndex", err)
  402. }
  403. return
  404. }
  405. ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
  406. // Make sure type and URL matches.
  407. if ctx.Params(":type") == "issues" && issue.IsPull {
  408. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  409. return
  410. } else if ctx.Params(":type") == "pulls" && !issue.IsPull {
  411. ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
  412. return
  413. }
  414. if issue.IsPull {
  415. MustAllowPulls(ctx)
  416. if ctx.Written() {
  417. return
  418. }
  419. ctx.Data["PageIsPullList"] = true
  420. ctx.Data["PageIsPullConversation"] = true
  421. } else {
  422. MustEnableIssues(ctx)
  423. if ctx.Written() {
  424. return
  425. }
  426. ctx.Data["PageIsIssueList"] = true
  427. }
  428. issue.RenderedContent = string(markdown.Render([]byte(issue.Content), ctx.Repo.RepoLink,
  429. ctx.Repo.Repository.ComposeMetas()))
  430. repo := ctx.Repo.Repository
  431. // Get more information if it's a pull request.
  432. if issue.IsPull {
  433. if issue.PullRequest.HasMerged {
  434. ctx.Data["DisableStatusChange"] = issue.PullRequest.HasMerged
  435. PrepareMergedViewPullInfo(ctx, issue)
  436. } else {
  437. PrepareViewPullInfo(ctx, issue)
  438. }
  439. if ctx.Written() {
  440. return
  441. }
  442. }
  443. // Metas.
  444. // Check labels.
  445. labelIDMark := make(map[int64]bool)
  446. for i := range issue.Labels {
  447. labelIDMark[issue.Labels[i].ID] = true
  448. }
  449. labels, err := models.GetLabelsByRepoID(repo.ID, "")
  450. if err != nil {
  451. ctx.Handle(500, "GetLabelsByRepoID", err)
  452. return
  453. }
  454. hasSelected := false
  455. for i := range labels {
  456. if labelIDMark[labels[i].ID] {
  457. labels[i].IsChecked = true
  458. hasSelected = true
  459. }
  460. }
  461. ctx.Data["HasSelectedLabel"] = hasSelected
  462. ctx.Data["Labels"] = labels
  463. // Check milestone and assignee.
  464. if ctx.Repo.IsWriter() {
  465. RetrieveRepoMilestonesAndAssignees(ctx, repo)
  466. if ctx.Written() {
  467. return
  468. }
  469. }
  470. if ctx.IsSigned {
  471. // Update issue-user.
  472. if err = issue.ReadBy(ctx.User.ID); err != nil {
  473. ctx.Handle(500, "ReadBy", err)
  474. return
  475. }
  476. }
  477. var (
  478. tag models.CommentTag
  479. ok bool
  480. marked = make(map[int64]models.CommentTag)
  481. comment *models.Comment
  482. participants = make([]*models.User, 1, 10)
  483. )
  484. // Render comments and and fetch participants.
  485. participants[0] = issue.Poster
  486. for _, comment = range issue.Comments {
  487. if comment.Type == models.CommentTypeComment {
  488. comment.RenderedContent = string(markdown.Render([]byte(comment.Content), ctx.Repo.RepoLink,
  489. ctx.Repo.Repository.ComposeMetas()))
  490. // Check tag.
  491. tag, ok = marked[comment.PosterID]
  492. if ok {
  493. comment.ShowTag = tag
  494. continue
  495. }
  496. if repo.IsOwnedBy(comment.PosterID) ||
  497. (repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(comment.PosterID)) {
  498. comment.ShowTag = models.CommentTagOwner
  499. } else if comment.Poster.IsWriterOfRepo(repo) {
  500. comment.ShowTag = models.CommentTagWriter
  501. } else if comment.PosterID == issue.PosterID {
  502. comment.ShowTag = models.CommentTagPoster
  503. }
  504. marked[comment.PosterID] = comment.ShowTag
  505. isAdded := false
  506. for j := range participants {
  507. if comment.Poster == participants[j] {
  508. isAdded = true
  509. break
  510. }
  511. }
  512. if !isAdded && !issue.IsPoster(comment.Poster.ID) {
  513. participants = append(participants, comment.Poster)
  514. }
  515. } else if comment.Type == models.CommentTypeLabel {
  516. if err = comment.LoadLabel(); err != nil {
  517. ctx.Handle(500, "LoadLabel", err)
  518. return
  519. }
  520. } else if comment.Type == models.CommentTypeMilestone {
  521. if err = comment.LoadMilestone(); err != nil {
  522. ctx.Handle(500, "LoadMilestone", err)
  523. return
  524. }
  525. } else if comment.Type == models.CommentTypeAssignees {
  526. if err = comment.LoadAssignees(); err != nil {
  527. ctx.Handle(500, "LoadAssignees", err)
  528. return
  529. }
  530. }
  531. }
  532. if issue.IsPull {
  533. pull := issue.PullRequest
  534. canDelete := false
  535. if ctx.IsSigned && pull.HeadBranch != "master" {
  536. if err := pull.GetHeadRepo(); err != nil {
  537. log.Error(4, "GetHeadRepo: %v", err)
  538. } else if ctx.User.IsWriterOfRepo(pull.HeadRepo) {
  539. canDelete = true
  540. deleteBranchURL := pull.HeadRepo.Link() + "/branches/" + pull.HeadBranch + "/delete"
  541. ctx.Data["DeleteBranchLink"] = fmt.Sprintf("%s?commit=%s&redirect_to=%s&issue_id=%d",
  542. deleteBranchURL, pull.MergedCommitID, ctx.Data["Link"], issue.ID)
  543. }
  544. }
  545. ctx.Data["IsPullBranchDeletable"] = canDelete && git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch)
  546. }
  547. ctx.Data["Participants"] = participants
  548. ctx.Data["NumParticipants"] = len(participants)
  549. ctx.Data["Issue"] = issue
  550. ctx.Data["IsIssueOwner"] = ctx.Repo.IsWriter() || (ctx.IsSigned && issue.IsPoster(ctx.User.ID))
  551. ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + ctx.Data["Link"].(string)
  552. ctx.HTML(200, tplIssueView)
  553. }
  554. func getActionIssue(ctx *context.Context) *models.Issue {
  555. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  556. if err != nil {
  557. if models.IsErrIssueNotExist(err) {
  558. ctx.Error(404, "GetIssueByIndex")
  559. } else {
  560. ctx.Handle(500, "GetIssueByIndex", err)
  561. }
  562. return nil
  563. }
  564. return issue
  565. }
  566. // UpdateIssueTitle change issue's title
  567. func UpdateIssueTitle(ctx *context.Context) {
  568. issue := getActionIssue(ctx)
  569. if ctx.Written() {
  570. return
  571. }
  572. if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.IsWriter()) {
  573. ctx.Error(403)
  574. return
  575. }
  576. title := ctx.QueryTrim("title")
  577. if len(title) == 0 {
  578. ctx.Error(204)
  579. return
  580. }
  581. if err := issue.ChangeTitle(ctx.User, title); err != nil {
  582. ctx.Handle(500, "ChangeTitle", err)
  583. return
  584. }
  585. ctx.JSON(200, map[string]interface{}{
  586. "title": issue.Title,
  587. })
  588. }
  589. // UpdateIssueContent change issue's content
  590. func UpdateIssueContent(ctx *context.Context) {
  591. issue := getActionIssue(ctx)
  592. if ctx.Written() {
  593. return
  594. }
  595. if !ctx.IsSigned || (ctx.User.ID != issue.PosterID && !ctx.Repo.IsWriter()) {
  596. ctx.Error(403)
  597. return
  598. }
  599. content := ctx.Query("content")
  600. if err := issue.ChangeContent(ctx.User, content); err != nil {
  601. ctx.Handle(500, "ChangeContent", err)
  602. return
  603. }
  604. ctx.JSON(200, map[string]interface{}{
  605. "content": string(markdown.Render([]byte(issue.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
  606. })
  607. }
  608. // UpdateIssueMilestone change issue's milestone
  609. func UpdateIssueMilestone(ctx *context.Context) {
  610. issue := getActionIssue(ctx)
  611. if ctx.Written() {
  612. return
  613. }
  614. oldMilestoneID := issue.MilestoneID
  615. milestoneID := ctx.QueryInt64("id")
  616. if oldMilestoneID == milestoneID {
  617. ctx.JSON(200, map[string]interface{}{
  618. "ok": true,
  619. })
  620. return
  621. }
  622. // Not check for invalid milestone id and give responsibility to owners.
  623. issue.MilestoneID = milestoneID
  624. if err := models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
  625. ctx.Handle(500, "ChangeMilestoneAssign", err)
  626. return
  627. }
  628. ctx.JSON(200, map[string]interface{}{
  629. "ok": true,
  630. })
  631. }
  632. // UpdateIssueAssignee change issue's assignee
  633. func UpdateIssueAssignee(ctx *context.Context) {
  634. issue := getActionIssue(ctx)
  635. if ctx.Written() {
  636. return
  637. }
  638. assigneeID := ctx.QueryInt64("id")
  639. if issue.AssigneeID == assigneeID {
  640. ctx.JSON(200, map[string]interface{}{
  641. "ok": true,
  642. })
  643. return
  644. }
  645. if err := issue.ChangeAssignee(ctx.User, assigneeID); err != nil {
  646. ctx.Handle(500, "ChangeAssignee", err)
  647. return
  648. }
  649. ctx.JSON(200, map[string]interface{}{
  650. "ok": true,
  651. })
  652. }
  653. // NewComment create a comment for issue
  654. func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
  655. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  656. if err != nil {
  657. ctx.NotFoundOrServerError("GetIssueByIndex", models.IsErrIssueNotExist, err)
  658. return
  659. }
  660. var attachments []string
  661. if setting.AttachmentEnabled {
  662. attachments = form.Files
  663. }
  664. if ctx.HasError() {
  665. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  666. ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index))
  667. return
  668. }
  669. var comment *models.Comment
  670. defer func() {
  671. // Check if issue admin/poster changes the status of issue.
  672. if (ctx.Repo.IsWriter() || (ctx.IsSigned && issue.IsPoster(ctx.User.ID))) &&
  673. (form.Status == "reopen" || form.Status == "close") &&
  674. !(issue.IsPull && issue.PullRequest.HasMerged) {
  675. // Duplication and conflict check should apply to reopen pull request.
  676. var pr *models.PullRequest
  677. if form.Status == "reopen" && issue.IsPull {
  678. pull := issue.PullRequest
  679. pr, err = models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch)
  680. if err != nil {
  681. if !models.IsErrPullRequestNotExist(err) {
  682. ctx.Handle(500, "GetUnmergedPullRequest", err)
  683. return
  684. }
  685. }
  686. // Regenerate patch and test conflict.
  687. if pr == nil {
  688. if err = issue.PullRequest.UpdatePatch(); err != nil {
  689. ctx.Handle(500, "UpdatePatch", err)
  690. return
  691. }
  692. issue.PullRequest.AddToTaskQueue()
  693. }
  694. }
  695. if pr != nil {
  696. ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index))
  697. } else {
  698. if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, form.Status == "close"); err != nil {
  699. log.Error(4, "ChangeStatus: %v", err)
  700. } else {
  701. log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
  702. notification.Service.NotifyIssue(issue, ctx.User.ID)
  703. }
  704. }
  705. }
  706. // Redirect to comment hashtag if there is any actual content.
  707. typeName := "issues"
  708. if issue.IsPull {
  709. typeName = "pulls"
  710. }
  711. if comment != nil {
  712. ctx.Redirect(fmt.Sprintf("%s/%s/%d#%s", ctx.Repo.RepoLink, typeName, issue.Index, comment.HashTag()))
  713. } else {
  714. ctx.Redirect(fmt.Sprintf("%s/%s/%d", ctx.Repo.RepoLink, typeName, issue.Index))
  715. }
  716. }()
  717. // Fix #321: Allow empty comments, as long as we have attachments.
  718. if len(form.Content) == 0 && len(attachments) == 0 {
  719. return
  720. }
  721. comment, err = models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Content, attachments)
  722. if err != nil {
  723. ctx.Handle(500, "CreateIssueComment", err)
  724. return
  725. }
  726. notification.Service.NotifyIssue(issue, ctx.User.ID)
  727. log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID)
  728. }
  729. // UpdateCommentContent change comment of issue's content
  730. func UpdateCommentContent(ctx *context.Context) {
  731. comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
  732. if err != nil {
  733. ctx.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
  734. return
  735. }
  736. if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
  737. ctx.Error(403)
  738. return
  739. } else if comment.Type != models.CommentTypeComment {
  740. ctx.Error(204)
  741. return
  742. }
  743. comment.Content = ctx.Query("content")
  744. if len(comment.Content) == 0 {
  745. ctx.JSON(200, map[string]interface{}{
  746. "content": "",
  747. })
  748. return
  749. }
  750. if err = models.UpdateComment(comment); err != nil {
  751. ctx.Handle(500, "UpdateComment", err)
  752. return
  753. }
  754. ctx.JSON(200, map[string]interface{}{
  755. "content": string(markdown.Render([]byte(comment.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
  756. })
  757. }
  758. // DeleteComment delete comment of issue
  759. func DeleteComment(ctx *context.Context) {
  760. comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
  761. if err != nil {
  762. ctx.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
  763. return
  764. }
  765. if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
  766. ctx.Error(403)
  767. return
  768. } else if comment.Type != models.CommentTypeComment {
  769. ctx.Error(204)
  770. return
  771. }
  772. if err = models.DeleteComment(comment); err != nil {
  773. ctx.Handle(500, "DeleteCommentByID", err)
  774. return
  775. }
  776. ctx.Status(200)
  777. }
  778. // Milestones render milestones page
  779. func Milestones(ctx *context.Context) {
  780. ctx.Data["Title"] = ctx.Tr("repo.milestones")
  781. ctx.Data["PageIsIssueList"] = true
  782. ctx.Data["PageIsMilestones"] = true
  783. isShowClosed := ctx.Query("state") == "closed"
  784. openCount, closedCount := models.MilestoneStats(ctx.Repo.Repository.ID)
  785. ctx.Data["OpenCount"] = openCount
  786. ctx.Data["ClosedCount"] = closedCount
  787. sortType := ctx.Query("sort")
  788. page := ctx.QueryInt("page")
  789. if page <= 1 {
  790. page = 1
  791. }
  792. var total int
  793. if !isShowClosed {
  794. total = int(openCount)
  795. } else {
  796. total = int(closedCount)
  797. }
  798. ctx.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5)
  799. miles, err := models.GetMilestones(ctx.Repo.Repository.ID, page, isShowClosed, sortType)
  800. if err != nil {
  801. ctx.Handle(500, "GetMilestones", err)
  802. return
  803. }
  804. for _, m := range miles {
  805. m.RenderedContent = string(markdown.Render([]byte(m.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
  806. }
  807. ctx.Data["Milestones"] = miles
  808. if isShowClosed {
  809. ctx.Data["State"] = "closed"
  810. } else {
  811. ctx.Data["State"] = "open"
  812. }
  813. ctx.Data["SortType"] = sortType
  814. ctx.Data["IsShowClosed"] = isShowClosed
  815. ctx.HTML(200, tplMilestone)
  816. }
  817. // NewMilestone render creating milestone page
  818. func NewMilestone(ctx *context.Context) {
  819. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  820. ctx.Data["PageIsIssueList"] = true
  821. ctx.Data["PageIsMilestones"] = true
  822. ctx.Data["RequireDatetimepicker"] = true
  823. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  824. ctx.HTML(200, tplMilestoneNew)
  825. }
  826. // NewMilestonePost response for creating milestone
  827. func NewMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
  828. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  829. ctx.Data["PageIsIssueList"] = true
  830. ctx.Data["PageIsMilestones"] = true
  831. ctx.Data["RequireDatetimepicker"] = true
  832. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  833. if ctx.HasError() {
  834. ctx.HTML(200, tplMilestoneNew)
  835. return
  836. }
  837. if len(form.Deadline) == 0 {
  838. form.Deadline = "9999-12-31"
  839. }
  840. deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
  841. if err != nil {
  842. ctx.Data["Err_Deadline"] = true
  843. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  844. return
  845. }
  846. if err = models.NewMilestone(&models.Milestone{
  847. RepoID: ctx.Repo.Repository.ID,
  848. Name: form.Title,
  849. Content: form.Content,
  850. Deadline: deadline,
  851. }); err != nil {
  852. ctx.Handle(500, "NewMilestone", err)
  853. return
  854. }
  855. ctx.Flash.Success(ctx.Tr("repo.milestones.create_success", form.Title))
  856. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  857. }
  858. // EditMilestone render edting milestone page
  859. func EditMilestone(ctx *context.Context) {
  860. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  861. ctx.Data["PageIsMilestones"] = true
  862. ctx.Data["PageIsEditMilestone"] = true
  863. ctx.Data["RequireDatetimepicker"] = true
  864. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  865. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  866. if err != nil {
  867. if models.IsErrMilestoneNotExist(err) {
  868. ctx.Handle(404, "", nil)
  869. } else {
  870. ctx.Handle(500, "GetMilestoneByRepoID", err)
  871. }
  872. return
  873. }
  874. ctx.Data["title"] = m.Name
  875. ctx.Data["content"] = m.Content
  876. if len(m.DeadlineString) > 0 {
  877. ctx.Data["deadline"] = m.DeadlineString
  878. }
  879. ctx.HTML(200, tplMilestoneNew)
  880. }
  881. // EditMilestonePost response for edting milestone
  882. func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
  883. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  884. ctx.Data["PageIsMilestones"] = true
  885. ctx.Data["PageIsEditMilestone"] = true
  886. ctx.Data["RequireDatetimepicker"] = true
  887. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  888. if ctx.HasError() {
  889. ctx.HTML(200, tplMilestoneNew)
  890. return
  891. }
  892. if len(form.Deadline) == 0 {
  893. form.Deadline = "9999-12-31"
  894. }
  895. deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
  896. if err != nil {
  897. ctx.Data["Err_Deadline"] = true
  898. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  899. return
  900. }
  901. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  902. if err != nil {
  903. if models.IsErrMilestoneNotExist(err) {
  904. ctx.Handle(404, "", nil)
  905. } else {
  906. ctx.Handle(500, "GetMilestoneByRepoID", err)
  907. }
  908. return
  909. }
  910. m.Name = form.Title
  911. m.Content = form.Content
  912. m.Deadline = deadline
  913. if err = models.UpdateMilestone(m); err != nil {
  914. ctx.Handle(500, "UpdateMilestone", err)
  915. return
  916. }
  917. ctx.Flash.Success(ctx.Tr("repo.milestones.edit_success", m.Name))
  918. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  919. }
  920. // ChangeMilestonStatus response for change a milestone's status
  921. func ChangeMilestonStatus(ctx *context.Context) {
  922. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  923. if err != nil {
  924. if models.IsErrMilestoneNotExist(err) {
  925. ctx.Handle(404, "", err)
  926. } else {
  927. ctx.Handle(500, "GetMilestoneByRepoID", err)
  928. }
  929. return
  930. }
  931. switch ctx.Params(":action") {
  932. case "open":
  933. if m.IsClosed {
  934. if err = models.ChangeMilestoneStatus(m, false); err != nil {
  935. ctx.Handle(500, "ChangeMilestoneStatus", err)
  936. return
  937. }
  938. }
  939. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=open")
  940. case "close":
  941. if !m.IsClosed {
  942. m.ClosedDate = time.Now()
  943. if err = models.ChangeMilestoneStatus(m, true); err != nil {
  944. ctx.Handle(500, "ChangeMilestoneStatus", err)
  945. return
  946. }
  947. }
  948. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=closed")
  949. default:
  950. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  951. }
  952. }
  953. // DeleteMilestone delete a milestone
  954. func DeleteMilestone(ctx *context.Context) {
  955. if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil {
  956. ctx.Flash.Error("DeleteMilestoneByRepoID: " + err.Error())
  957. } else {
  958. ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success"))
  959. }
  960. ctx.JSON(200, map[string]interface{}{
  961. "redirect": ctx.Repo.RepoLink + "/milestones",
  962. })
  963. }