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 22 kB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  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. "fmt"
  7. "net/url"
  8. "strings"
  9. "time"
  10. "github.com/Unknwon/com"
  11. "github.com/go-martini/martini"
  12. "github.com/gogits/gogs/models"
  13. "github.com/gogits/gogs/modules/auth"
  14. "github.com/gogits/gogs/modules/base"
  15. "github.com/gogits/gogs/modules/log"
  16. "github.com/gogits/gogs/modules/mailer"
  17. "github.com/gogits/gogs/modules/middleware"
  18. )
  19. func Issues(ctx *middleware.Context) {
  20. ctx.Data["Title"] = "Issues"
  21. ctx.Data["IsRepoToolbarIssues"] = true
  22. ctx.Data["IsRepoToolbarIssuesList"] = true
  23. viewType := ctx.Query("type")
  24. types := []string{"assigned", "created_by", "mentioned"}
  25. if !com.IsSliceContainsStr(types, viewType) {
  26. viewType = "all"
  27. }
  28. isShowClosed := ctx.Query("state") == "closed"
  29. if viewType != "all" && !ctx.IsSigned {
  30. ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
  31. ctx.Redirect("/user/login")
  32. return
  33. }
  34. var assigneeId, posterId int64
  35. var filterMode int
  36. switch viewType {
  37. case "assigned":
  38. assigneeId = ctx.User.Id
  39. filterMode = models.FM_ASSIGN
  40. case "created_by":
  41. posterId = ctx.User.Id
  42. filterMode = models.FM_CREATE
  43. case "mentioned":
  44. filterMode = models.FM_MENTION
  45. }
  46. var mid int64
  47. midx, _ := base.StrTo(ctx.Query("milestone")).Int64()
  48. if midx > 0 {
  49. mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, midx)
  50. if err != nil {
  51. ctx.Handle(500, "issue.Issues(GetMilestoneByIndex): %v", err)
  52. return
  53. }
  54. mid = mile.Id
  55. }
  56. labels, err := models.GetLabels(ctx.Repo.Repository.Id)
  57. if err != nil {
  58. ctx.Handle(500, "issue.Issues(GetLabels): %v", err)
  59. return
  60. }
  61. for _, l := range labels {
  62. l.CalOpenIssues()
  63. }
  64. ctx.Data["Labels"] = labels
  65. page, _ := base.StrTo(ctx.Query("page")).Int()
  66. // Get issues.
  67. issues, err := models.GetIssues(assigneeId, ctx.Repo.Repository.Id, posterId, mid, page,
  68. isShowClosed, ctx.Query("labels"), ctx.Query("sortType"))
  69. if err != nil {
  70. ctx.Handle(500, "issue.Issues(GetIssues): %v", err)
  71. return
  72. }
  73. // Get issue-user pairs.
  74. pairs, err := models.GetIssueUserPairs(ctx.Repo.Repository.Id, posterId, isShowClosed)
  75. if err != nil {
  76. ctx.Handle(500, "issue.Issues(GetIssueUserPairs): %v", err)
  77. return
  78. }
  79. // Get posters.
  80. for i := range issues {
  81. idx := models.PairsContains(pairs, issues[i].Id)
  82. if filterMode == models.FM_MENTION && (idx == -1 || !pairs[idx].IsMentioned) {
  83. continue
  84. }
  85. if idx > -1 {
  86. issues[i].IsRead = pairs[idx].IsRead
  87. } else {
  88. issues[i].IsRead = true
  89. }
  90. if err = issues[i].GetPoster(); err != nil {
  91. ctx.Handle(500, "issue.Issues(GetPoster)", fmt.Errorf("[#%d]%v", issues[i].Id, err))
  92. return
  93. }
  94. }
  95. var uid int64 = -1
  96. if ctx.User != nil {
  97. uid = ctx.User.Id
  98. }
  99. issueStats := models.GetIssueStats(ctx.Repo.Repository.Id, uid, isShowClosed, filterMode)
  100. ctx.Data["IssueStats"] = issueStats
  101. ctx.Data["ViewType"] = viewType
  102. ctx.Data["Issues"] = issues
  103. ctx.Data["IsShowClosed"] = isShowClosed
  104. if isShowClosed {
  105. ctx.Data["State"] = "closed"
  106. ctx.Data["ShowCount"] = issueStats.ClosedCount
  107. } else {
  108. ctx.Data["ShowCount"] = issueStats.OpenCount
  109. }
  110. ctx.HTML(200, "issue/list")
  111. }
  112. func CreateIssue(ctx *middleware.Context, params martini.Params) {
  113. ctx.Data["Title"] = "Create issue"
  114. ctx.Data["IsRepoToolbarIssues"] = true
  115. ctx.Data["IsRepoToolbarIssuesList"] = false
  116. var err error
  117. // Get all milestones.
  118. ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false)
  119. if err != nil {
  120. ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err)
  121. return
  122. }
  123. ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true)
  124. if err != nil {
  125. ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err)
  126. return
  127. }
  128. us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
  129. if err != nil {
  130. ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
  131. return
  132. }
  133. ctx.Data["Collaborators"] = us
  134. ctx.HTML(200, "issue/create")
  135. }
  136. func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
  137. ctx.Data["Title"] = "Create issue"
  138. ctx.Data["IsRepoToolbarIssues"] = true
  139. ctx.Data["IsRepoToolbarIssuesList"] = false
  140. var err error
  141. // Get all milestones.
  142. ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false)
  143. if err != nil {
  144. ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err)
  145. return
  146. }
  147. ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true)
  148. if err != nil {
  149. ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err)
  150. return
  151. }
  152. us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
  153. if err != nil {
  154. ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
  155. return
  156. }
  157. ctx.Data["Collaborators"] = us
  158. if ctx.HasError() {
  159. ctx.HTML(200, "issue/create")
  160. return
  161. }
  162. // Only collaborators can assign.
  163. if !ctx.Repo.IsOwner {
  164. form.AssigneeId = 0
  165. }
  166. issue := &models.Issue{
  167. RepoId: ctx.Repo.Repository.Id,
  168. Index: int64(ctx.Repo.Repository.NumIssues) + 1,
  169. Name: form.IssueName,
  170. PosterId: ctx.User.Id,
  171. MilestoneId: form.MilestoneId,
  172. AssigneeId: form.AssigneeId,
  173. Labels: form.Labels,
  174. Content: form.Content,
  175. }
  176. if err := models.NewIssue(issue); err != nil {
  177. ctx.Handle(500, "issue.CreateIssue(NewIssue)", err)
  178. return
  179. } else if err := models.NewIssueUserPairs(issue.RepoId, issue.Id, ctx.Repo.Owner.Id,
  180. ctx.User.Id, form.AssigneeId, ctx.Repo.Repository.Name); err != nil {
  181. ctx.Handle(500, "issue.CreateIssue(NewIssueUserPairs)", err)
  182. return
  183. }
  184. // Update mentions.
  185. ms := base.MentionPattern.FindAllString(issue.Content, -1)
  186. if len(ms) > 0 {
  187. for i := range ms {
  188. ms[i] = ms[i][1:]
  189. }
  190. ids := models.GetUserIdsByNames(ms)
  191. if err := models.UpdateIssueUserPairsByMentions(ids, issue.Id); err != nil {
  192. ctx.Handle(500, "issue.CreateIssue(UpdateIssueUserPairsByMentions)", err)
  193. return
  194. }
  195. }
  196. act := &models.Action{
  197. ActUserId: ctx.User.Id,
  198. ActUserName: ctx.User.Name,
  199. ActEmail: ctx.User.Email,
  200. OpType: models.OP_CREATE_ISSUE,
  201. Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
  202. RepoId: ctx.Repo.Repository.Id,
  203. RepoUserName: ctx.Repo.Owner.Name,
  204. RepoName: ctx.Repo.Repository.Name,
  205. RefName: ctx.Repo.BranchName,
  206. IsPrivate: ctx.Repo.Repository.IsPrivate,
  207. }
  208. // Notify watchers.
  209. if err := models.NotifyWatchers(act); err != nil {
  210. ctx.Handle(500, "issue.CreateIssue(NotifyWatchers)", err)
  211. return
  212. }
  213. // Mail watchers and mentions.
  214. if base.Service.NotifyMail {
  215. tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
  216. if err != nil {
  217. ctx.Handle(500, "issue.CreateIssue(SendIssueNotifyMail)", err)
  218. return
  219. }
  220. tos = append(tos, ctx.User.LowerName)
  221. newTos := make([]string, 0, len(ms))
  222. for _, m := range ms {
  223. if com.IsSliceContainsStr(tos, m) {
  224. continue
  225. }
  226. newTos = append(newTos, m)
  227. }
  228. if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
  229. ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
  230. ctx.Handle(500, "issue.CreateIssue(SendIssueMentionMail)", err)
  231. return
  232. }
  233. }
  234. log.Trace("%d Issue created: %d", ctx.Repo.Repository.Id, issue.Id)
  235. ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index))
  236. }
  237. func ViewIssue(ctx *middleware.Context, params martini.Params) {
  238. idx, _ := base.StrTo(params["index"]).Int64()
  239. if idx == 0 {
  240. ctx.Handle(404, "issue.ViewIssue", nil)
  241. return
  242. }
  243. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
  244. if err != nil {
  245. if err == models.ErrIssueNotExist {
  246. ctx.Handle(404, "issue.ViewIssue(GetIssueByIndex)", err)
  247. } else {
  248. ctx.Handle(500, "issue.ViewIssue(GetIssueByIndex)", err)
  249. }
  250. return
  251. }
  252. // Get assigned milestone.
  253. if issue.MilestoneId > 0 {
  254. ctx.Data["Milestone"], err = models.GetMilestoneById(issue.MilestoneId)
  255. if err != nil {
  256. if err == models.ErrMilestoneNotExist {
  257. log.Warn("issue.ViewIssue(GetMilestoneById): %v", err)
  258. } else {
  259. ctx.Handle(500, "issue.ViewIssue(GetMilestoneById)", err)
  260. return
  261. }
  262. }
  263. }
  264. // Get all milestones.
  265. ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false)
  266. if err != nil {
  267. ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err)
  268. return
  269. }
  270. ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true)
  271. if err != nil {
  272. ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err)
  273. return
  274. }
  275. // Get all collaborators.
  276. ctx.Data["Collaborators"], err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
  277. if err != nil {
  278. ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
  279. return
  280. }
  281. if ctx.IsSigned {
  282. // Update issue-user.
  283. if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.Id); err != nil {
  284. ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByRead): %v", err)
  285. return
  286. }
  287. }
  288. // Get poster and Assignee.
  289. if err = issue.GetPoster(); err != nil {
  290. ctx.Handle(500, "issue.ViewIssue(GetPoster): %v", err)
  291. return
  292. } else if err = issue.GetAssignee(); err != nil {
  293. ctx.Handle(500, "issue.ViewIssue(GetAssignee): %v", err)
  294. return
  295. }
  296. issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink))
  297. // Get comments.
  298. comments, err := models.GetIssueComments(issue.Id)
  299. if err != nil {
  300. ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err)
  301. return
  302. }
  303. // Get posters.
  304. for i := range comments {
  305. u, err := models.GetUserById(comments[i].PosterId)
  306. if err != nil {
  307. ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err)
  308. return
  309. }
  310. comments[i].Poster = u
  311. comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
  312. }
  313. ctx.Data["Title"] = issue.Name
  314. ctx.Data["Issue"] = issue
  315. ctx.Data["Comments"] = comments
  316. ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id)
  317. ctx.Data["IsRepoToolbarIssues"] = true
  318. ctx.Data["IsRepoToolbarIssuesList"] = false
  319. ctx.HTML(200, "issue/view")
  320. }
  321. func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
  322. idx, err := base.StrTo(params["index"]).Int()
  323. if err != nil {
  324. ctx.Error(404)
  325. return
  326. }
  327. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(idx))
  328. if err != nil {
  329. if err == models.ErrIssueNotExist {
  330. ctx.Handle(404, "issue.UpdateIssue", err)
  331. } else {
  332. ctx.Handle(500, "issue.UpdateIssue(GetIssueByIndex)", err)
  333. }
  334. return
  335. }
  336. if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner {
  337. ctx.Handle(404, "issue.UpdateIssue", nil)
  338. return
  339. }
  340. issue.Name = form.IssueName
  341. issue.MilestoneId = form.MilestoneId
  342. issue.AssigneeId = form.AssigneeId
  343. issue.Labels = form.Labels
  344. issue.Content = form.Content
  345. if err = models.UpdateIssue(issue); err != nil {
  346. ctx.Handle(500, "issue.UpdateIssue(UpdateIssue)", err)
  347. return
  348. }
  349. ctx.JSON(200, map[string]interface{}{
  350. "ok": true,
  351. "title": issue.Name,
  352. "content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)),
  353. })
  354. }
  355. func UpdateIssueMilestone(ctx *middleware.Context) {
  356. if !ctx.Repo.IsOwner {
  357. ctx.Error(403)
  358. return
  359. }
  360. issueId, err := base.StrTo(ctx.Query("issue")).Int64()
  361. if err != nil {
  362. ctx.Error(404)
  363. return
  364. }
  365. issue, err := models.GetIssueById(issueId)
  366. if err != nil {
  367. if err == models.ErrIssueNotExist {
  368. ctx.Handle(404, "issue.UpdateIssueMilestone(GetIssueById)", err)
  369. } else {
  370. ctx.Handle(500, "issue.UpdateIssueMilestone(GetIssueById)", err)
  371. }
  372. return
  373. }
  374. oldMid := issue.MilestoneId
  375. mid, _ := base.StrTo(ctx.Query("milestone")).Int64()
  376. if oldMid == mid {
  377. ctx.JSON(200, map[string]interface{}{
  378. "ok": true,
  379. })
  380. return
  381. }
  382. // Not check for invalid milestone id and give responsibility to owners.
  383. issue.MilestoneId = mid
  384. if err = models.ChangeMilestoneAssign(oldMid, mid, issue); err != nil {
  385. ctx.Handle(500, "issue.UpdateIssueMilestone(ChangeMilestoneAssign)", err)
  386. return
  387. } else if err = models.UpdateIssue(issue); err != nil {
  388. ctx.Handle(500, "issue.UpdateIssueMilestone(UpdateIssue)", err)
  389. return
  390. }
  391. ctx.JSON(200, map[string]interface{}{
  392. "ok": true,
  393. })
  394. }
  395. func UpdateAssignee(ctx *middleware.Context) {
  396. if !ctx.Repo.IsOwner {
  397. ctx.Error(403)
  398. return
  399. }
  400. issueId, err := base.StrTo(ctx.Query("issue")).Int64()
  401. if err != nil {
  402. ctx.Error(404)
  403. return
  404. }
  405. issue, err := models.GetIssueById(issueId)
  406. if err != nil {
  407. if err == models.ErrIssueNotExist {
  408. ctx.Handle(404, "issue.UpdateAssignee(GetIssueById)", err)
  409. } else {
  410. ctx.Handle(500, "issue.UpdateAssignee(GetIssueById)", err)
  411. }
  412. return
  413. }
  414. aid, _ := base.StrTo(ctx.Query("assigneeid")).Int64()
  415. // Not check for invalid assignne id and give responsibility to owners.
  416. issue.AssigneeId = aid
  417. if err = models.UpdateIssueUserPairByAssignee(aid, issue.Id); err != nil {
  418. ctx.Handle(500, "issue.UpdateAssignee(UpdateIssueUserPairByAssignee): %v", err)
  419. return
  420. } else if err = models.UpdateIssue(issue); err != nil {
  421. ctx.Handle(500, "issue.UpdateAssignee(UpdateIssue)", err)
  422. return
  423. }
  424. ctx.JSON(200, map[string]interface{}{
  425. "ok": true,
  426. })
  427. }
  428. func Comment(ctx *middleware.Context, params martini.Params) {
  429. index, err := base.StrTo(ctx.Query("issueIndex")).Int64()
  430. if err != nil {
  431. ctx.Handle(404, "issue.Comment(get index)", err)
  432. return
  433. }
  434. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index)
  435. if err != nil {
  436. if err == models.ErrIssueNotExist {
  437. ctx.Handle(404, "issue.Comment", err)
  438. } else {
  439. ctx.Handle(200, "issue.Comment(get issue)", err)
  440. }
  441. return
  442. }
  443. // Check if issue owner changes the status of issue.
  444. var newStatus string
  445. if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id {
  446. newStatus = ctx.Query("change_status")
  447. }
  448. if len(newStatus) > 0 {
  449. if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) ||
  450. (strings.Contains(newStatus, "Close") && !issue.IsClosed) {
  451. issue.IsClosed = !issue.IsClosed
  452. if err = models.UpdateIssue(issue); err != nil {
  453. ctx.Handle(500, "issue.Comment(UpdateIssue)", err)
  454. return
  455. } else if err = models.UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil {
  456. ctx.Handle(500, "issue.Comment(UpdateIssueUserPairsByStatus)", err)
  457. return
  458. }
  459. cmtType := models.IT_CLOSE
  460. if !issue.IsClosed {
  461. cmtType = models.IT_REOPEN
  462. }
  463. if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, ""); err != nil {
  464. ctx.Handle(200, "issue.Comment(create status change comment)", err)
  465. return
  466. }
  467. log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.Id, !issue.IsClosed)
  468. }
  469. }
  470. var ms []string
  471. content := ctx.Query("content")
  472. if len(content) > 0 {
  473. switch params["action"] {
  474. case "new":
  475. if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.IT_PLAIN, content); err != nil {
  476. ctx.Handle(500, "issue.Comment(create comment)", err)
  477. return
  478. }
  479. // Update mentions.
  480. ms = base.MentionPattern.FindAllString(issue.Content, -1)
  481. if len(ms) > 0 {
  482. for i := range ms {
  483. ms[i] = ms[i][1:]
  484. }
  485. ids := models.GetUserIdsByNames(ms)
  486. if err := models.UpdateIssueUserPairsByMentions(ids, issue.Id); err != nil {
  487. ctx.Handle(500, "issue.CreateIssue(UpdateIssueUserPairsByMentions)", err)
  488. return
  489. }
  490. }
  491. log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id)
  492. default:
  493. ctx.Handle(404, "issue.Comment", err)
  494. return
  495. }
  496. }
  497. // Notify watchers.
  498. if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name, ActEmail: ctx.User.Email,
  499. OpType: models.OP_COMMENT_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]),
  500. RepoId: ctx.Repo.Repository.Id, RepoName: ctx.Repo.Repository.Name, RefName: ""}); err != nil {
  501. ctx.Handle(500, "issue.CreateIssue(NotifyWatchers)", err)
  502. return
  503. }
  504. // Mail watchers and mentions.
  505. if base.Service.NotifyMail {
  506. issue.Content = content
  507. tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
  508. if err != nil {
  509. ctx.Handle(500, "issue.Comment(SendIssueNotifyMail)", err)
  510. return
  511. }
  512. tos = append(tos, ctx.User.LowerName)
  513. newTos := make([]string, 0, len(ms))
  514. for _, m := range ms {
  515. if com.IsSliceContainsStr(tos, m) {
  516. continue
  517. }
  518. newTos = append(newTos, m)
  519. }
  520. if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
  521. ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
  522. ctx.Handle(500, "issue.Comment(SendIssueMentionMail)", err)
  523. return
  524. }
  525. }
  526. ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index))
  527. }
  528. func NewLabel(ctx *middleware.Context, form auth.CreateLabelForm) {
  529. if ctx.HasError() {
  530. Issues(ctx)
  531. return
  532. }
  533. l := &models.Label{
  534. RepoId: ctx.Repo.Repository.Id,
  535. Name: form.Title,
  536. Color: form.Color,
  537. }
  538. if err := models.NewLabel(l); err != nil {
  539. ctx.Handle(500, "issue.NewLabel(NewLabel)", err)
  540. return
  541. }
  542. ctx.Redirect(ctx.Repo.RepoLink + "/issues")
  543. }
  544. func UpdateLabel(ctx *middleware.Context, params martini.Params) {
  545. }
  546. func Milestones(ctx *middleware.Context) {
  547. ctx.Data["Title"] = "Milestones"
  548. ctx.Data["IsRepoToolbarIssues"] = true
  549. ctx.Data["IsRepoToolbarIssuesList"] = true
  550. isShowClosed := ctx.Query("state") == "closed"
  551. miles, err := models.GetMilestones(ctx.Repo.Repository.Id, isShowClosed)
  552. if err != nil {
  553. ctx.Handle(500, "issue.Milestones(GetMilestones)", err)
  554. return
  555. }
  556. for _, m := range miles {
  557. m.RenderedContent = string(base.RenderSpecialLink([]byte(m.Content), ctx.Repo.RepoLink))
  558. m.CalOpenIssues()
  559. }
  560. ctx.Data["Milestones"] = miles
  561. if isShowClosed {
  562. ctx.Data["State"] = "closed"
  563. } else {
  564. ctx.Data["State"] = "open"
  565. }
  566. ctx.HTML(200, "issue/milestone")
  567. }
  568. func NewMilestone(ctx *middleware.Context) {
  569. ctx.Data["Title"] = "New Milestone"
  570. ctx.Data["IsRepoToolbarIssues"] = true
  571. ctx.Data["IsRepoToolbarIssuesList"] = true
  572. ctx.HTML(200, "issue/milestone_new")
  573. }
  574. func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
  575. ctx.Data["Title"] = "New Milestone"
  576. ctx.Data["IsRepoToolbarIssues"] = true
  577. ctx.Data["IsRepoToolbarIssuesList"] = true
  578. var deadline time.Time
  579. var err error
  580. if len(form.Deadline) == 0 {
  581. form.Deadline = "12/31/9999"
  582. }
  583. deadline, err = time.Parse("01/02/2006", form.Deadline)
  584. if err != nil {
  585. ctx.Handle(500, "issue.NewMilestonePost(time.Parse)", err)
  586. return
  587. }
  588. mile := &models.Milestone{
  589. RepoId: ctx.Repo.Repository.Id,
  590. Index: int64(ctx.Repo.Repository.NumMilestones) + 1,
  591. Name: form.Title,
  592. Content: form.Content,
  593. Deadline: deadline,
  594. }
  595. if err = models.NewMilestone(mile); err != nil {
  596. ctx.Handle(500, "issue.NewMilestonePost(NewMilestone)", err)
  597. return
  598. }
  599. ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
  600. }
  601. func UpdateMilestone(ctx *middleware.Context, params martini.Params) {
  602. ctx.Data["Title"] = "Update Milestone"
  603. ctx.Data["IsRepoToolbarIssues"] = true
  604. ctx.Data["IsRepoToolbarIssuesList"] = true
  605. idx, _ := base.StrTo(params["index"]).Int64()
  606. if idx == 0 {
  607. ctx.Handle(404, "issue.UpdateMilestone", nil)
  608. return
  609. }
  610. mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx)
  611. if err != nil {
  612. if err == models.ErrMilestoneNotExist {
  613. ctx.Handle(404, "issue.UpdateMilestone(GetMilestoneByIndex)", err)
  614. } else {
  615. ctx.Handle(500, "issue.UpdateMilestone(GetMilestoneByIndex)", err)
  616. }
  617. return
  618. }
  619. action := params["action"]
  620. if len(action) > 0 {
  621. switch action {
  622. case "open":
  623. if mile.IsClosed {
  624. if err = models.ChangeMilestoneStatus(mile, false); err != nil {
  625. ctx.Handle(500, "issue.UpdateMilestone(ChangeMilestoneStatus)", err)
  626. return
  627. }
  628. }
  629. case "close":
  630. if !mile.IsClosed {
  631. mile.ClosedDate = time.Now()
  632. if err = models.ChangeMilestoneStatus(mile, true); err != nil {
  633. ctx.Handle(500, "issue.UpdateMilestone(ChangeMilestoneStatus)", err)
  634. return
  635. }
  636. }
  637. case "delete":
  638. if err = models.DeleteMilestone(mile); err != nil {
  639. ctx.Handle(500, "issue.UpdateMilestone(DeleteMilestone)", err)
  640. return
  641. }
  642. }
  643. ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
  644. return
  645. }
  646. mile.DeadlineString = mile.Deadline.UTC().Format("01/02/2006")
  647. if mile.DeadlineString == "12/31/9999" {
  648. mile.DeadlineString = ""
  649. }
  650. ctx.Data["Milestone"] = mile
  651. ctx.HTML(200, "issue/milestone_edit")
  652. }
  653. func UpdateMilestonePost(ctx *middleware.Context, params martini.Params, form auth.CreateMilestoneForm) {
  654. ctx.Data["Title"] = "Update Milestone"
  655. ctx.Data["IsRepoToolbarIssues"] = true
  656. ctx.Data["IsRepoToolbarIssuesList"] = true
  657. idx, _ := base.StrTo(params["index"]).Int64()
  658. if idx == 0 {
  659. ctx.Handle(404, "issue.UpdateMilestonePost", nil)
  660. return
  661. }
  662. mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx)
  663. if err != nil {
  664. if err == models.ErrMilestoneNotExist {
  665. ctx.Handle(404, "issue.UpdateMilestonePost(GetMilestoneByIndex)", err)
  666. } else {
  667. ctx.Handle(500, "issue.UpdateMilestonePost(GetMilestoneByIndex)", err)
  668. }
  669. return
  670. }
  671. var deadline time.Time
  672. if len(form.Deadline) == 0 {
  673. form.Deadline = "12/31/9999"
  674. }
  675. deadline, err = time.Parse("01/02/2006", form.Deadline)
  676. if err != nil {
  677. ctx.Handle(500, "issue.UpdateMilestonePost(time.Parse)", err)
  678. return
  679. }
  680. mile.Name = form.Title
  681. mile.Content = form.Content
  682. mile.Deadline = deadline
  683. if err = models.UpdateMilestone(mile); err != nil {
  684. ctx.Handle(500, "issue.UpdateMilestonePost(UpdateMilestone)", err)
  685. return
  686. }
  687. ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
  688. }