You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

issue.go 34 kB

11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 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
9 years ago
10 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
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 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
9 years ago
10 years ago
10 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299
  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. "errors"
  7. "fmt"
  8. "io"
  9. "io/ioutil"
  10. "net/http"
  11. "net/url"
  12. "strings"
  13. "time"
  14. "github.com/Unknwon/com"
  15. "github.com/Unknwon/paginater"
  16. "code.gitea.io/git"
  17. "code.gitea.io/gitea/models"
  18. "code.gitea.io/gitea/modules/auth"
  19. "code.gitea.io/gitea/modules/base"
  20. "code.gitea.io/gitea/modules/context"
  21. "code.gitea.io/gitea/modules/log"
  22. "code.gitea.io/gitea/modules/markdown"
  23. "code.gitea.io/gitea/modules/setting"
  24. )
  25. const (
  26. tplIssues base.TplName = "repo/issue/list"
  27. tplIssueNew base.TplName = "repo/issue/new"
  28. tplIssueView base.TplName = "repo/issue/view"
  29. tplLabels base.TplName = "repo/issue/labels"
  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. ".gogs/ISSUE_TEMPLATE.md",
  44. ".github/ISSUE_TEMPLATE.md",
  45. }
  46. )
  47. // MustEnableIssues check if repository enable internal issues
  48. func MustEnableIssues(ctx *context.Context) {
  49. if !ctx.Repo.Repository.EnableIssues {
  50. ctx.Handle(404, "MustEnableIssues", nil)
  51. return
  52. }
  53. if ctx.Repo.Repository.EnableExternalTracker {
  54. ctx.Redirect(ctx.Repo.Repository.ExternalTrackerURL)
  55. return
  56. }
  57. }
  58. // MustAllowPulls check if repository enable pull requests
  59. func MustAllowPulls(ctx *context.Context) {
  60. if !ctx.Repo.Repository.AllowsPulls() {
  61. ctx.Handle(404, "MustAllowPulls", nil)
  62. return
  63. }
  64. // User can send pull request if owns a forked repository.
  65. if ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID) {
  66. ctx.Repo.PullRequest.Allowed = true
  67. ctx.Repo.PullRequest.HeadInfo = ctx.User.Name + ":" + ctx.Repo.BranchName
  68. }
  69. }
  70. // RetrieveLabels find all the labels of a repository
  71. func RetrieveLabels(ctx *context.Context) {
  72. labels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, ctx.Query("sort"))
  73. if err != nil {
  74. ctx.Handle(500, "RetrieveLabels.GetLabels", err)
  75. return
  76. }
  77. for _, l := range labels {
  78. l.CalOpenIssues()
  79. }
  80. ctx.Data["Labels"] = labels
  81. ctx.Data["NumLabels"] = len(labels)
  82. ctx.Data["SortType"] = ctx.Query("sort")
  83. }
  84. // Issues render issues page
  85. func Issues(ctx *context.Context) {
  86. isPullList := ctx.Params(":type") == "pulls"
  87. if isPullList {
  88. MustAllowPulls(ctx)
  89. if ctx.Written() {
  90. return
  91. }
  92. ctx.Data["Title"] = ctx.Tr("repo.pulls")
  93. ctx.Data["PageIsPullList"] = true
  94. } else {
  95. MustEnableIssues(ctx)
  96. if ctx.Written() {
  97. return
  98. }
  99. ctx.Data["Title"] = ctx.Tr("repo.issues")
  100. ctx.Data["PageIsIssueList"] = true
  101. }
  102. viewType := ctx.Query("type")
  103. sortType := ctx.Query("sort")
  104. types := []string{"assigned", "created_by", "mentioned"}
  105. if !com.IsSliceContainsStr(types, viewType) {
  106. viewType = "all"
  107. }
  108. // Must sign in to see issues about you.
  109. if viewType != "all" && !ctx.IsSigned {
  110. ctx.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubURL+ctx.Req.RequestURI), 0, setting.AppSubURL)
  111. ctx.Redirect(setting.AppSubURL + "/user/login")
  112. return
  113. }
  114. var (
  115. assigneeID = ctx.QueryInt64("assignee")
  116. posterID int64
  117. mentionedID int64
  118. forceEmpty bool
  119. )
  120. switch viewType {
  121. case "assigned":
  122. if assigneeID > 0 && ctx.User.ID != assigneeID {
  123. // two different assignees, must be empty
  124. forceEmpty = true
  125. } else {
  126. assigneeID = ctx.User.ID
  127. }
  128. case "created_by":
  129. posterID = ctx.User.ID
  130. case "mentioned":
  131. mentionedID = ctx.User.ID
  132. }
  133. repo := ctx.Repo.Repository
  134. selectLabels := ctx.Query("labels")
  135. milestoneID := ctx.QueryInt64("milestone")
  136. isShowClosed := ctx.Query("state") == "closed"
  137. var issueStats *models.IssueStats
  138. if forceEmpty {
  139. issueStats = &models.IssueStats{}
  140. } else {
  141. issueStats = models.GetIssueStats(&models.IssueStatsOptions{
  142. RepoID: repo.ID,
  143. Labels: selectLabels,
  144. MilestoneID: milestoneID,
  145. AssigneeID: assigneeID,
  146. MentionedID: mentionedID,
  147. IsPull: isPullList,
  148. })
  149. }
  150. page := ctx.QueryInt("page")
  151. if page <= 1 {
  152. page = 1
  153. }
  154. var total int
  155. if !isShowClosed {
  156. total = int(issueStats.OpenCount)
  157. } else {
  158. total = int(issueStats.ClosedCount)
  159. }
  160. pager := paginater.New(total, setting.UI.IssuePagingNum, page, 5)
  161. ctx.Data["Page"] = pager
  162. var issues []*models.Issue
  163. if forceEmpty {
  164. issues = []*models.Issue{}
  165. } else {
  166. var err error
  167. issues, err = models.Issues(&models.IssuesOptions{
  168. AssigneeID: assigneeID,
  169. RepoID: repo.ID,
  170. PosterID: posterID,
  171. MentionedID: mentionedID,
  172. MilestoneID: milestoneID,
  173. Page: pager.Current(),
  174. IsClosed: isShowClosed,
  175. IsPull: isPullList,
  176. Labels: selectLabels,
  177. SortType: sortType,
  178. })
  179. if err != nil {
  180. ctx.Handle(500, "Issues", err)
  181. return
  182. }
  183. }
  184. // Get issue-user relations.
  185. pairs, err := models.GetIssueUsers(repo.ID, posterID, isShowClosed)
  186. if err != nil {
  187. ctx.Handle(500, "GetIssueUsers", err)
  188. return
  189. }
  190. // Get posters.
  191. for i := range issues {
  192. if !ctx.IsSigned {
  193. issues[i].IsRead = true
  194. continue
  195. }
  196. // Check read status.
  197. idx := models.PairsContains(pairs, issues[i].ID, ctx.User.ID)
  198. if idx > -1 {
  199. issues[i].IsRead = pairs[idx].IsRead
  200. } else {
  201. issues[i].IsRead = true
  202. }
  203. }
  204. ctx.Data["Issues"] = issues
  205. // Get milestones.
  206. ctx.Data["Milestones"], err = models.GetMilestonesByRepoID(repo.ID)
  207. if err != nil {
  208. ctx.Handle(500, "GetAllRepoMilestones", err)
  209. return
  210. }
  211. // Get assignees.
  212. ctx.Data["Assignees"], err = repo.GetAssignees()
  213. if err != nil {
  214. ctx.Handle(500, "GetAssignees", err)
  215. return
  216. }
  217. if ctx.QueryInt64("assignee") == 0 {
  218. assigneeID = 0 // Reset ID to prevent unexpected selection of assignee.
  219. }
  220. ctx.Data["IssueStats"] = issueStats
  221. ctx.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64()
  222. ctx.Data["ViewType"] = viewType
  223. ctx.Data["SortType"] = sortType
  224. ctx.Data["MilestoneID"] = milestoneID
  225. ctx.Data["AssigneeID"] = assigneeID
  226. ctx.Data["IsShowClosed"] = isShowClosed
  227. if isShowClosed {
  228. ctx.Data["State"] = "closed"
  229. } else {
  230. ctx.Data["State"] = "open"
  231. }
  232. ctx.HTML(200, tplIssues)
  233. }
  234. func renderAttachmentSettings(ctx *context.Context) {
  235. ctx.Data["RequireDropzone"] = true
  236. ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled
  237. ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes
  238. ctx.Data["AttachmentMaxSize"] = setting.AttachmentMaxSize
  239. ctx.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles
  240. }
  241. // RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository
  242. func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repository) {
  243. var err error
  244. ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false, "")
  245. if err != nil {
  246. ctx.Handle(500, "GetMilestones", err)
  247. return
  248. }
  249. ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true, "")
  250. if err != nil {
  251. ctx.Handle(500, "GetMilestones", err)
  252. return
  253. }
  254. ctx.Data["Assignees"], err = repo.GetAssignees()
  255. if err != nil {
  256. ctx.Handle(500, "GetAssignees", err)
  257. return
  258. }
  259. }
  260. // RetrieveRepoMetas find all the meta information of a repository
  261. func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.Label {
  262. if !ctx.Repo.IsWriter() {
  263. return nil
  264. }
  265. labels, err := models.GetLabelsByRepoID(repo.ID, "")
  266. if err != nil {
  267. ctx.Handle(500, "GetLabelsByRepoID", err)
  268. return nil
  269. }
  270. ctx.Data["Labels"] = labels
  271. RetrieveRepoMilestonesAndAssignees(ctx, repo)
  272. if ctx.Written() {
  273. return nil
  274. }
  275. return labels
  276. }
  277. func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (string, bool) {
  278. var r io.Reader
  279. var bytes []byte
  280. if ctx.Repo.Commit == nil {
  281. var err error
  282. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
  283. if err != nil {
  284. return "", false
  285. }
  286. }
  287. entry, err := ctx.Repo.Commit.GetTreeEntryByPath(filename)
  288. if err != nil {
  289. return "", false
  290. }
  291. r, err = entry.Blob().Data()
  292. if err != nil {
  293. return "", false
  294. }
  295. bytes, err = ioutil.ReadAll(r)
  296. if err != nil {
  297. return "", false
  298. }
  299. return string(bytes), true
  300. }
  301. func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string) {
  302. for _, filename := range possibleFiles {
  303. content, found := getFileContentFromDefaultBranch(ctx, filename)
  304. if found {
  305. ctx.Data[ctxDataKey] = content
  306. return
  307. }
  308. }
  309. }
  310. // NewIssue render createing issue page
  311. func NewIssue(ctx *context.Context) {
  312. ctx.Data["Title"] = ctx.Tr("repo.issues.new")
  313. ctx.Data["PageIsIssueList"] = true
  314. ctx.Data["RequireHighlightJS"] = true
  315. ctx.Data["RequireSimpleMDE"] = true
  316. setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates)
  317. renderAttachmentSettings(ctx)
  318. RetrieveRepoMetas(ctx, ctx.Repo.Repository)
  319. if ctx.Written() {
  320. return
  321. }
  322. ctx.HTML(200, tplIssueNew)
  323. }
  324. // ValidateRepoMetas check and returns repository's meta informations
  325. func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64, int64, int64) {
  326. var (
  327. repo = ctx.Repo.Repository
  328. err error
  329. )
  330. labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository)
  331. if ctx.Written() {
  332. return nil, 0, 0
  333. }
  334. if !ctx.Repo.IsWriter() {
  335. return nil, 0, 0
  336. }
  337. // Check labels.
  338. labelIDs, err := base.StringsToInt64s(strings.Split(form.LabelIDs, ","))
  339. if err != nil {
  340. return nil, 0, 0
  341. }
  342. labelIDMark := base.Int64sToMap(labelIDs)
  343. hasSelected := false
  344. for i := range labels {
  345. if labelIDMark[labels[i].ID] {
  346. labels[i].IsChecked = true
  347. hasSelected = true
  348. }
  349. }
  350. ctx.Data["HasSelectedLabel"] = hasSelected
  351. ctx.Data["label_ids"] = form.LabelIDs
  352. ctx.Data["Labels"] = labels
  353. // Check milestone.
  354. milestoneID := form.MilestoneID
  355. if milestoneID > 0 {
  356. ctx.Data["Milestone"], err = repo.GetMilestoneByID(milestoneID)
  357. if err != nil {
  358. ctx.Handle(500, "GetMilestoneByID", err)
  359. return nil, 0, 0
  360. }
  361. ctx.Data["milestone_id"] = milestoneID
  362. }
  363. // Check assignee.
  364. assigneeID := form.AssigneeID
  365. if assigneeID > 0 {
  366. ctx.Data["Assignee"], err = repo.GetAssigneeByID(assigneeID)
  367. if err != nil {
  368. ctx.Handle(500, "GetAssigneeByID", err)
  369. return nil, 0, 0
  370. }
  371. ctx.Data["assignee_id"] = assigneeID
  372. }
  373. return labelIDs, milestoneID, assigneeID
  374. }
  375. // NewIssuePost response for creating new issue
  376. func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
  377. ctx.Data["Title"] = ctx.Tr("repo.issues.new")
  378. ctx.Data["PageIsIssueList"] = true
  379. ctx.Data["RequireHighlightJS"] = true
  380. ctx.Data["RequireSimpleMDE"] = true
  381. renderAttachmentSettings(ctx)
  382. var (
  383. repo = ctx.Repo.Repository
  384. attachments []string
  385. )
  386. labelIDs, milestoneID, assigneeID := ValidateRepoMetas(ctx, form)
  387. if ctx.Written() {
  388. return
  389. }
  390. if setting.AttachmentEnabled {
  391. attachments = form.Files
  392. }
  393. if ctx.HasError() {
  394. ctx.HTML(200, tplIssueNew)
  395. return
  396. }
  397. issue := &models.Issue{
  398. RepoID: repo.ID,
  399. Title: form.Title,
  400. PosterID: ctx.User.ID,
  401. Poster: ctx.User,
  402. MilestoneID: milestoneID,
  403. AssigneeID: assigneeID,
  404. Content: form.Content,
  405. }
  406. if err := models.NewIssue(repo, issue, labelIDs, attachments); err != nil {
  407. ctx.Handle(500, "NewIssue", err)
  408. return
  409. }
  410. log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
  411. ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
  412. }
  413. // UploadIssueAttachment response for uploading issue's attachment
  414. func UploadIssueAttachment(ctx *context.Context) {
  415. if !setting.AttachmentEnabled {
  416. ctx.Error(404, "attachment is not enabled")
  417. return
  418. }
  419. file, header, err := ctx.Req.FormFile("file")
  420. if err != nil {
  421. ctx.Error(500, fmt.Sprintf("FormFile: %v", err))
  422. return
  423. }
  424. defer file.Close()
  425. buf := make([]byte, 1024)
  426. n, _ := file.Read(buf)
  427. if n > 0 {
  428. buf = buf[:n]
  429. }
  430. fileType := http.DetectContentType(buf)
  431. allowedTypes := strings.Split(setting.AttachmentAllowedTypes, ",")
  432. allowed := false
  433. for _, t := range allowedTypes {
  434. t := strings.Trim(t, " ")
  435. if t == "*/*" || t == fileType {
  436. allowed = true
  437. break
  438. }
  439. }
  440. if !allowed {
  441. ctx.Error(400, ErrFileTypeForbidden.Error())
  442. return
  443. }
  444. attach, err := models.NewAttachment(header.Filename, buf, file)
  445. if err != nil {
  446. ctx.Error(500, fmt.Sprintf("NewAttachment: %v", err))
  447. return
  448. }
  449. log.Trace("New attachment uploaded: %s", attach.UUID)
  450. ctx.JSON(200, map[string]string{
  451. "uuid": attach.UUID,
  452. })
  453. }
  454. // ViewIssue render issue view page
  455. func ViewIssue(ctx *context.Context) {
  456. ctx.Data["RequireHighlightJS"] = true
  457. ctx.Data["RequireDropzone"] = true
  458. renderAttachmentSettings(ctx)
  459. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  460. if err != nil {
  461. if models.IsErrIssueNotExist(err) {
  462. ctx.Handle(404, "GetIssueByIndex", err)
  463. } else {
  464. ctx.Handle(500, "GetIssueByIndex", err)
  465. }
  466. return
  467. }
  468. ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
  469. // Make sure type and URL matches.
  470. if ctx.Params(":type") == "issues" && issue.IsPull {
  471. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  472. return
  473. } else if ctx.Params(":type") == "pulls" && !issue.IsPull {
  474. ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
  475. return
  476. }
  477. if issue.IsPull {
  478. MustAllowPulls(ctx)
  479. if ctx.Written() {
  480. return
  481. }
  482. ctx.Data["PageIsPullList"] = true
  483. ctx.Data["PageIsPullConversation"] = true
  484. } else {
  485. MustEnableIssues(ctx)
  486. if ctx.Written() {
  487. return
  488. }
  489. ctx.Data["PageIsIssueList"] = true
  490. }
  491. issue.RenderedContent = string(markdown.Render([]byte(issue.Content), ctx.Repo.RepoLink,
  492. ctx.Repo.Repository.ComposeMetas()))
  493. repo := ctx.Repo.Repository
  494. // Get more information if it's a pull request.
  495. if issue.IsPull {
  496. if issue.PullRequest.HasMerged {
  497. ctx.Data["DisableStatusChange"] = issue.PullRequest.HasMerged
  498. PrepareMergedViewPullInfo(ctx, issue)
  499. } else {
  500. PrepareViewPullInfo(ctx, issue)
  501. }
  502. if ctx.Written() {
  503. return
  504. }
  505. }
  506. // Metas.
  507. // Check labels.
  508. labelIDMark := make(map[int64]bool)
  509. for i := range issue.Labels {
  510. labelIDMark[issue.Labels[i].ID] = true
  511. }
  512. labels, err := models.GetLabelsByRepoID(repo.ID, "")
  513. if err != nil {
  514. ctx.Handle(500, "GetLabelsByRepoID", err)
  515. return
  516. }
  517. hasSelected := false
  518. for i := range labels {
  519. if labelIDMark[labels[i].ID] {
  520. labels[i].IsChecked = true
  521. hasSelected = true
  522. }
  523. }
  524. ctx.Data["HasSelectedLabel"] = hasSelected
  525. ctx.Data["Labels"] = labels
  526. // Check milestone and assignee.
  527. if ctx.Repo.IsWriter() {
  528. RetrieveRepoMilestonesAndAssignees(ctx, repo)
  529. if ctx.Written() {
  530. return
  531. }
  532. }
  533. if ctx.IsSigned {
  534. // Update issue-user.
  535. if err = issue.ReadBy(ctx.User.ID); err != nil {
  536. ctx.Handle(500, "ReadBy", err)
  537. return
  538. }
  539. }
  540. var (
  541. tag models.CommentTag
  542. ok bool
  543. marked = make(map[int64]models.CommentTag)
  544. comment *models.Comment
  545. participants = make([]*models.User, 1, 10)
  546. )
  547. // Render comments and and fetch participants.
  548. participants[0] = issue.Poster
  549. for _, comment = range issue.Comments {
  550. if comment.Type == models.CommentTypeComment {
  551. comment.RenderedContent = string(markdown.Render([]byte(comment.Content), ctx.Repo.RepoLink,
  552. ctx.Repo.Repository.ComposeMetas()))
  553. // Check tag.
  554. tag, ok = marked[comment.PosterID]
  555. if ok {
  556. comment.ShowTag = tag
  557. continue
  558. }
  559. if repo.IsOwnedBy(comment.PosterID) ||
  560. (repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(comment.PosterID)) {
  561. comment.ShowTag = models.CommentTagOwner
  562. } else if comment.Poster.IsWriterOfRepo(repo) {
  563. comment.ShowTag = models.CommentTagWriter
  564. } else if comment.PosterID == issue.PosterID {
  565. comment.ShowTag = models.CommentTagPoster
  566. }
  567. marked[comment.PosterID] = comment.ShowTag
  568. isAdded := false
  569. for j := range participants {
  570. if comment.Poster == participants[j] {
  571. isAdded = true
  572. break
  573. }
  574. }
  575. if !isAdded && !issue.IsPoster(comment.Poster.ID) {
  576. participants = append(participants, comment.Poster)
  577. }
  578. }
  579. }
  580. if issue.IsPull {
  581. pull := issue.PullRequest
  582. canDelete := false
  583. if ctx.IsSigned && pull.HeadBranch != "master" {
  584. if err := pull.GetHeadRepo(); err != nil {
  585. log.Error(4, "GetHeadRepo: %v", err)
  586. } else if ctx.User.IsWriterOfRepo(pull.HeadRepo) {
  587. canDelete = true
  588. deleteBranchURL := pull.HeadRepo.Link() + "/branches/" + pull.HeadBranch + "/delete"
  589. ctx.Data["DeleteBranchLink"] = fmt.Sprintf("%s?commit=%s&redirect_to=%s", deleteBranchURL, pull.MergedCommitID, ctx.Data["Link"])
  590. }
  591. }
  592. ctx.Data["IsPullBranchDeletable"] = canDelete && git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch)
  593. }
  594. ctx.Data["Participants"] = participants
  595. ctx.Data["NumParticipants"] = len(participants)
  596. ctx.Data["Issue"] = issue
  597. ctx.Data["IsIssueOwner"] = ctx.Repo.IsWriter() || (ctx.IsSigned && issue.IsPoster(ctx.User.ID))
  598. ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + ctx.Data["Link"].(string)
  599. ctx.HTML(200, tplIssueView)
  600. }
  601. func getActionIssue(ctx *context.Context) *models.Issue {
  602. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  603. if err != nil {
  604. if models.IsErrIssueNotExist(err) {
  605. ctx.Error(404, "GetIssueByIndex")
  606. } else {
  607. ctx.Handle(500, "GetIssueByIndex", err)
  608. }
  609. return nil
  610. }
  611. return issue
  612. }
  613. // UpdateIssueTitle change issue's title
  614. func UpdateIssueTitle(ctx *context.Context) {
  615. issue := getActionIssue(ctx)
  616. if ctx.Written() {
  617. return
  618. }
  619. if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.IsWriter()) {
  620. ctx.Error(403)
  621. return
  622. }
  623. title := ctx.QueryTrim("title")
  624. if len(title) == 0 {
  625. ctx.Error(204)
  626. return
  627. }
  628. if err := issue.ChangeTitle(ctx.User, title); err != nil {
  629. ctx.Handle(500, "ChangeTitle", err)
  630. return
  631. }
  632. ctx.JSON(200, map[string]interface{}{
  633. "title": issue.Title,
  634. })
  635. }
  636. // UpdateIssueContent change issue's content
  637. func UpdateIssueContent(ctx *context.Context) {
  638. issue := getActionIssue(ctx)
  639. if ctx.Written() {
  640. return
  641. }
  642. if !ctx.IsSigned || (ctx.User.ID != issue.PosterID && !ctx.Repo.IsWriter()) {
  643. ctx.Error(403)
  644. return
  645. }
  646. content := ctx.Query("content")
  647. if err := issue.ChangeContent(ctx.User, content); err != nil {
  648. ctx.Handle(500, "ChangeContent", err)
  649. return
  650. }
  651. ctx.JSON(200, map[string]interface{}{
  652. "content": string(markdown.Render([]byte(issue.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
  653. })
  654. }
  655. // UpdateIssueLabel change issue's labels
  656. func UpdateIssueLabel(ctx *context.Context) {
  657. issue := getActionIssue(ctx)
  658. if ctx.Written() {
  659. return
  660. }
  661. if ctx.Query("action") == "clear" {
  662. if err := issue.ClearLabels(ctx.User); err != nil {
  663. ctx.Handle(500, "ClearLabels", err)
  664. return
  665. }
  666. } else {
  667. isAttach := ctx.Query("action") == "attach"
  668. label, err := models.GetLabelByID(ctx.QueryInt64("id"))
  669. if err != nil {
  670. if models.IsErrLabelNotExist(err) {
  671. ctx.Error(404, "GetLabelByID")
  672. } else {
  673. ctx.Handle(500, "GetLabelByID", err)
  674. }
  675. return
  676. }
  677. if isAttach && !issue.HasLabel(label.ID) {
  678. if err = issue.AddLabel(ctx.User, label); err != nil {
  679. ctx.Handle(500, "AddLabel", err)
  680. return
  681. }
  682. } else if !isAttach && issue.HasLabel(label.ID) {
  683. if err = issue.RemoveLabel(ctx.User, label); err != nil {
  684. ctx.Handle(500, "RemoveLabel", err)
  685. return
  686. }
  687. }
  688. }
  689. ctx.JSON(200, map[string]interface{}{
  690. "ok": true,
  691. })
  692. }
  693. // UpdateIssueMilestone change issue's milestone
  694. func UpdateIssueMilestone(ctx *context.Context) {
  695. issue := getActionIssue(ctx)
  696. if ctx.Written() {
  697. return
  698. }
  699. oldMilestoneID := issue.MilestoneID
  700. milestoneID := ctx.QueryInt64("id")
  701. if oldMilestoneID == milestoneID {
  702. ctx.JSON(200, map[string]interface{}{
  703. "ok": true,
  704. })
  705. return
  706. }
  707. // Not check for invalid milestone id and give responsibility to owners.
  708. issue.MilestoneID = milestoneID
  709. if err := models.ChangeMilestoneAssign(issue, oldMilestoneID); err != nil {
  710. ctx.Handle(500, "ChangeMilestoneAssign", err)
  711. return
  712. }
  713. ctx.JSON(200, map[string]interface{}{
  714. "ok": true,
  715. })
  716. }
  717. // UpdateIssueAssignee change issue's assignee
  718. func UpdateIssueAssignee(ctx *context.Context) {
  719. issue := getActionIssue(ctx)
  720. if ctx.Written() {
  721. return
  722. }
  723. assigneeID := ctx.QueryInt64("id")
  724. if issue.AssigneeID == assigneeID {
  725. ctx.JSON(200, map[string]interface{}{
  726. "ok": true,
  727. })
  728. return
  729. }
  730. if err := issue.ChangeAssignee(ctx.User, assigneeID); err != nil {
  731. ctx.Handle(500, "ChangeAssignee", err)
  732. return
  733. }
  734. ctx.JSON(200, map[string]interface{}{
  735. "ok": true,
  736. })
  737. }
  738. // NewComment create a comment for issue
  739. func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
  740. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  741. if err != nil {
  742. ctx.NotFoundOrServerError("GetIssueByIndex", models.IsErrIssueNotExist, err)
  743. return
  744. }
  745. var attachments []string
  746. if setting.AttachmentEnabled {
  747. attachments = form.Files
  748. }
  749. if ctx.HasError() {
  750. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  751. ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index))
  752. return
  753. }
  754. var comment *models.Comment
  755. defer func() {
  756. // Check if issue admin/poster changes the status of issue.
  757. if (ctx.Repo.IsWriter() || (ctx.IsSigned && issue.IsPoster(ctx.User.ID))) &&
  758. (form.Status == "reopen" || form.Status == "close") &&
  759. !(issue.IsPull && issue.PullRequest.HasMerged) {
  760. // Duplication and conflict check should apply to reopen pull request.
  761. var pr *models.PullRequest
  762. if form.Status == "reopen" && issue.IsPull {
  763. pull := issue.PullRequest
  764. pr, err = models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch)
  765. if err != nil {
  766. if !models.IsErrPullRequestNotExist(err) {
  767. ctx.Handle(500, "GetUnmergedPullRequest", err)
  768. return
  769. }
  770. }
  771. // Regenerate patch and test conflict.
  772. if pr == nil {
  773. if err = issue.PullRequest.UpdatePatch(); err != nil {
  774. ctx.Handle(500, "UpdatePatch", err)
  775. return
  776. }
  777. issue.PullRequest.AddToTaskQueue()
  778. }
  779. }
  780. if pr != nil {
  781. ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index))
  782. } else {
  783. if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, form.Status == "close"); err != nil {
  784. log.Error(4, "ChangeStatus: %v", err)
  785. } else {
  786. log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
  787. }
  788. }
  789. }
  790. // Redirect to comment hashtag if there is any actual content.
  791. typeName := "issues"
  792. if issue.IsPull {
  793. typeName = "pulls"
  794. }
  795. if comment != nil {
  796. ctx.Redirect(fmt.Sprintf("%s/%s/%d#%s", ctx.Repo.RepoLink, typeName, issue.Index, comment.HashTag()))
  797. } else {
  798. ctx.Redirect(fmt.Sprintf("%s/%s/%d", ctx.Repo.RepoLink, typeName, issue.Index))
  799. }
  800. }()
  801. // Fix #321: Allow empty comments, as long as we have attachments.
  802. if len(form.Content) == 0 && len(attachments) == 0 {
  803. return
  804. }
  805. comment, err = models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Content, attachments)
  806. if err != nil {
  807. ctx.Handle(500, "CreateIssueComment", err)
  808. return
  809. }
  810. log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID)
  811. }
  812. // UpdateCommentContent change comment of issue's content
  813. func UpdateCommentContent(ctx *context.Context) {
  814. comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
  815. if err != nil {
  816. ctx.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
  817. return
  818. }
  819. if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
  820. ctx.Error(403)
  821. return
  822. } else if comment.Type != models.CommentTypeComment {
  823. ctx.Error(204)
  824. return
  825. }
  826. comment.Content = ctx.Query("content")
  827. if len(comment.Content) == 0 {
  828. ctx.JSON(200, map[string]interface{}{
  829. "content": "",
  830. })
  831. return
  832. }
  833. if err = models.UpdateComment(comment); err != nil {
  834. ctx.Handle(500, "UpdateComment", err)
  835. return
  836. }
  837. ctx.JSON(200, map[string]interface{}{
  838. "content": string(markdown.Render([]byte(comment.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
  839. })
  840. }
  841. // DeleteComment delete comment of issue
  842. func DeleteComment(ctx *context.Context) {
  843. comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
  844. if err != nil {
  845. ctx.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
  846. return
  847. }
  848. if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
  849. ctx.Error(403)
  850. return
  851. } else if comment.Type != models.CommentTypeComment {
  852. ctx.Error(204)
  853. return
  854. }
  855. if err = models.DeleteCommentByID(comment.ID); err != nil {
  856. ctx.Handle(500, "DeleteCommentByID", err)
  857. return
  858. }
  859. ctx.Status(200)
  860. }
  861. // Labels render issue's labels page
  862. func Labels(ctx *context.Context) {
  863. ctx.Data["Title"] = ctx.Tr("repo.labels")
  864. ctx.Data["PageIsIssueList"] = true
  865. ctx.Data["PageIsLabels"] = true
  866. ctx.Data["RequireMinicolors"] = true
  867. ctx.Data["LabelTemplates"] = models.LabelTemplates
  868. ctx.HTML(200, tplLabels)
  869. }
  870. // InitializeLabels init labels for a repository
  871. func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) {
  872. if ctx.HasError() {
  873. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  874. return
  875. }
  876. list, err := models.GetLabelTemplateFile(form.TemplateName)
  877. if err != nil {
  878. ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, err))
  879. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  880. return
  881. }
  882. labels := make([]*models.Label, len(list))
  883. for i := 0; i < len(list); i++ {
  884. labels[i] = &models.Label{
  885. RepoID: ctx.Repo.Repository.ID,
  886. Name: list[i][0],
  887. Color: list[i][1],
  888. }
  889. }
  890. if err := models.NewLabels(labels...); err != nil {
  891. ctx.Handle(500, "NewLabels", err)
  892. return
  893. }
  894. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  895. }
  896. // NewLabel create new label for repository
  897. func NewLabel(ctx *context.Context, form auth.CreateLabelForm) {
  898. ctx.Data["Title"] = ctx.Tr("repo.labels")
  899. ctx.Data["PageIsLabels"] = true
  900. if ctx.HasError() {
  901. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  902. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  903. return
  904. }
  905. l := &models.Label{
  906. RepoID: ctx.Repo.Repository.ID,
  907. Name: form.Title,
  908. Color: form.Color,
  909. }
  910. if err := models.NewLabels(l); err != nil {
  911. ctx.Handle(500, "NewLabel", err)
  912. return
  913. }
  914. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  915. }
  916. // UpdateLabel update a label's name and color
  917. func UpdateLabel(ctx *context.Context, form auth.CreateLabelForm) {
  918. l, err := models.GetLabelByID(form.ID)
  919. if err != nil {
  920. switch {
  921. case models.IsErrLabelNotExist(err):
  922. ctx.Error(404)
  923. default:
  924. ctx.Handle(500, "UpdateLabel", err)
  925. }
  926. return
  927. }
  928. l.Name = form.Title
  929. l.Color = form.Color
  930. if err := models.UpdateLabel(l); err != nil {
  931. ctx.Handle(500, "UpdateLabel", err)
  932. return
  933. }
  934. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  935. }
  936. // DeleteLabel delete a label
  937. func DeleteLabel(ctx *context.Context) {
  938. if err := models.DeleteLabel(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil {
  939. ctx.Flash.Error("DeleteLabel: " + err.Error())
  940. } else {
  941. ctx.Flash.Success(ctx.Tr("repo.issues.label_deletion_success"))
  942. }
  943. ctx.JSON(200, map[string]interface{}{
  944. "redirect": ctx.Repo.RepoLink + "/labels",
  945. })
  946. return
  947. }
  948. // Milestones render milestones page
  949. func Milestones(ctx *context.Context) {
  950. ctx.Data["Title"] = ctx.Tr("repo.milestones")
  951. ctx.Data["PageIsIssueList"] = true
  952. ctx.Data["PageIsMilestones"] = true
  953. isShowClosed := ctx.Query("state") == "closed"
  954. openCount, closedCount := models.MilestoneStats(ctx.Repo.Repository.ID)
  955. ctx.Data["OpenCount"] = openCount
  956. ctx.Data["ClosedCount"] = closedCount
  957. sortType := ctx.Query("sort")
  958. page := ctx.QueryInt("page")
  959. if page <= 1 {
  960. page = 1
  961. }
  962. var total int
  963. if !isShowClosed {
  964. total = int(openCount)
  965. } else {
  966. total = int(closedCount)
  967. }
  968. ctx.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5)
  969. miles, err := models.GetMilestones(ctx.Repo.Repository.ID, page, isShowClosed, sortType)
  970. if err != nil {
  971. ctx.Handle(500, "GetMilestones", err)
  972. return
  973. }
  974. for _, m := range miles {
  975. m.RenderedContent = string(markdown.Render([]byte(m.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
  976. }
  977. ctx.Data["Milestones"] = miles
  978. if isShowClosed {
  979. ctx.Data["State"] = "closed"
  980. } else {
  981. ctx.Data["State"] = "open"
  982. }
  983. ctx.Data["SortType"] = sortType
  984. ctx.Data["IsShowClosed"] = isShowClosed
  985. ctx.HTML(200, tplMilestone)
  986. }
  987. // NewMilestone render creating milestone page
  988. func NewMilestone(ctx *context.Context) {
  989. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  990. ctx.Data["PageIsIssueList"] = true
  991. ctx.Data["PageIsMilestones"] = true
  992. ctx.Data["RequireDatetimepicker"] = true
  993. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  994. ctx.HTML(200, tplMilestoneNew)
  995. }
  996. // NewMilestonePost response for creating milestone
  997. func NewMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
  998. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  999. ctx.Data["PageIsIssueList"] = true
  1000. ctx.Data["PageIsMilestones"] = true
  1001. ctx.Data["RequireDatetimepicker"] = true
  1002. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  1003. if ctx.HasError() {
  1004. ctx.HTML(200, tplMilestoneNew)
  1005. return
  1006. }
  1007. if len(form.Deadline) == 0 {
  1008. form.Deadline = "9999-12-31"
  1009. }
  1010. deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
  1011. if err != nil {
  1012. ctx.Data["Err_Deadline"] = true
  1013. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  1014. return
  1015. }
  1016. if err = models.NewMilestone(&models.Milestone{
  1017. RepoID: ctx.Repo.Repository.ID,
  1018. Name: form.Title,
  1019. Content: form.Content,
  1020. Deadline: deadline,
  1021. }); err != nil {
  1022. ctx.Handle(500, "NewMilestone", err)
  1023. return
  1024. }
  1025. ctx.Flash.Success(ctx.Tr("repo.milestones.create_success", form.Title))
  1026. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  1027. }
  1028. // EditMilestone render edting milestone page
  1029. func EditMilestone(ctx *context.Context) {
  1030. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  1031. ctx.Data["PageIsMilestones"] = true
  1032. ctx.Data["PageIsEditMilestone"] = true
  1033. ctx.Data["RequireDatetimepicker"] = true
  1034. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  1035. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  1036. if err != nil {
  1037. if models.IsErrMilestoneNotExist(err) {
  1038. ctx.Handle(404, "", nil)
  1039. } else {
  1040. ctx.Handle(500, "GetMilestoneByRepoID", err)
  1041. }
  1042. return
  1043. }
  1044. ctx.Data["title"] = m.Name
  1045. ctx.Data["content"] = m.Content
  1046. if len(m.DeadlineString) > 0 {
  1047. ctx.Data["deadline"] = m.DeadlineString
  1048. }
  1049. ctx.HTML(200, tplMilestoneNew)
  1050. }
  1051. // EditMilestonePost response for edting milestone
  1052. func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
  1053. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  1054. ctx.Data["PageIsMilestones"] = true
  1055. ctx.Data["PageIsEditMilestone"] = true
  1056. ctx.Data["RequireDatetimepicker"] = true
  1057. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  1058. if ctx.HasError() {
  1059. ctx.HTML(200, tplMilestoneNew)
  1060. return
  1061. }
  1062. if len(form.Deadline) == 0 {
  1063. form.Deadline = "9999-12-31"
  1064. }
  1065. deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
  1066. if err != nil {
  1067. ctx.Data["Err_Deadline"] = true
  1068. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  1069. return
  1070. }
  1071. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  1072. if err != nil {
  1073. if models.IsErrMilestoneNotExist(err) {
  1074. ctx.Handle(404, "", nil)
  1075. } else {
  1076. ctx.Handle(500, "GetMilestoneByRepoID", err)
  1077. }
  1078. return
  1079. }
  1080. m.Name = form.Title
  1081. m.Content = form.Content
  1082. m.Deadline = deadline
  1083. if err = models.UpdateMilestone(m); err != nil {
  1084. ctx.Handle(500, "UpdateMilestone", err)
  1085. return
  1086. }
  1087. ctx.Flash.Success(ctx.Tr("repo.milestones.edit_success", m.Name))
  1088. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  1089. }
  1090. // ChangeMilestonStatus response for change a milestone's status
  1091. func ChangeMilestonStatus(ctx *context.Context) {
  1092. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  1093. if err != nil {
  1094. if models.IsErrMilestoneNotExist(err) {
  1095. ctx.Handle(404, "", err)
  1096. } else {
  1097. ctx.Handle(500, "GetMilestoneByRepoID", err)
  1098. }
  1099. return
  1100. }
  1101. switch ctx.Params(":action") {
  1102. case "open":
  1103. if m.IsClosed {
  1104. if err = models.ChangeMilestoneStatus(m, false); err != nil {
  1105. ctx.Handle(500, "ChangeMilestoneStatus", err)
  1106. return
  1107. }
  1108. }
  1109. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=open")
  1110. case "close":
  1111. if !m.IsClosed {
  1112. m.ClosedDate = time.Now()
  1113. if err = models.ChangeMilestoneStatus(m, true); err != nil {
  1114. ctx.Handle(500, "ChangeMilestoneStatus", err)
  1115. return
  1116. }
  1117. }
  1118. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=closed")
  1119. default:
  1120. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  1121. }
  1122. }
  1123. // DeleteMilestone delete a milestone
  1124. func DeleteMilestone(ctx *context.Context) {
  1125. if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil {
  1126. ctx.Flash.Error("DeleteMilestoneByRepoID: " + err.Error())
  1127. } else {
  1128. ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success"))
  1129. }
  1130. ctx.JSON(200, map[string]interface{}{
  1131. "redirect": ctx.Repo.RepoLink + "/milestones",
  1132. })
  1133. }