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