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

619 lines
17 KiB

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
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
10 years ago
10 years ago
10 years ago
10 years ago
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. "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. mid, _ := base.StrTo(ctx.Query("milestone")).Int64()
  47. page, _ := base.StrTo(ctx.Query("page")).Int()
  48. // Get issues.
  49. issues, err := models.GetIssues(assigneeId, ctx.Repo.Repository.Id, posterId, mid, page,
  50. isShowClosed, ctx.Query("labels"), ctx.Query("sortType"))
  51. if err != nil {
  52. ctx.Handle(500, "issue.Issues(GetIssues): %v", err)
  53. return
  54. }
  55. // Get issue-user pairs.
  56. pairs, err := models.GetIssueUserPairs(ctx.Repo.Repository.Id, posterId, isShowClosed)
  57. if err != nil {
  58. ctx.Handle(500, "issue.Issues(GetIssueUserPairs): %v", err)
  59. return
  60. }
  61. // Get posters.
  62. for i := range issues {
  63. idx := models.PairsContains(pairs, issues[i].Id)
  64. if filterMode == models.FM_MENTION && (idx == -1 || !pairs[idx].IsMentioned) {
  65. continue
  66. }
  67. if idx > -1 {
  68. issues[i].IsRead = pairs[idx].IsRead
  69. } else {
  70. issues[i].IsRead = true
  71. }
  72. if err = issues[i].GetPoster(); err != nil {
  73. ctx.Handle(500, "issue.Issues(GetPoster)", fmt.Errorf("[#%d]%v", issues[i].Id, err))
  74. return
  75. }
  76. }
  77. var uid int64 = -1
  78. if ctx.User != nil {
  79. uid = ctx.User.Id
  80. }
  81. issueStats := models.GetIssueStats(ctx.Repo.Repository.Id, uid, isShowClosed, filterMode)
  82. ctx.Data["IssueStats"] = issueStats
  83. ctx.Data["ViewType"] = viewType
  84. ctx.Data["Issues"] = issues
  85. ctx.Data["IsShowClosed"] = isShowClosed
  86. if isShowClosed {
  87. ctx.Data["State"] = "closed"
  88. ctx.Data["ShowCount"] = issueStats.ClosedCount
  89. } else {
  90. ctx.Data["ShowCount"] = issueStats.OpenCount
  91. }
  92. ctx.HTML(200, "issue/list")
  93. }
  94. func CreateIssue(ctx *middleware.Context, params martini.Params) {
  95. ctx.Data["Title"] = "Create issue"
  96. ctx.Data["IsRepoToolbarIssues"] = true
  97. ctx.Data["IsRepoToolbarIssuesList"] = false
  98. us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
  99. if err != nil {
  100. ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
  101. return
  102. }
  103. ctx.Data["Collaborators"] = us
  104. ctx.HTML(200, "issue/create")
  105. }
  106. func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
  107. ctx.Data["Title"] = "Create issue"
  108. ctx.Data["IsRepoToolbarIssues"] = true
  109. ctx.Data["IsRepoToolbarIssuesList"] = false
  110. us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
  111. if err != nil {
  112. ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
  113. return
  114. }
  115. ctx.Data["Collaborators"] = us
  116. if ctx.HasError() {
  117. ctx.HTML(200, "issue/create")
  118. return
  119. }
  120. // Only collaborators can assign.
  121. if !ctx.Repo.IsOwner {
  122. form.AssigneeId = 0
  123. }
  124. issue := &models.Issue{
  125. RepoId: ctx.Repo.Repository.Id,
  126. Index: int64(ctx.Repo.Repository.NumIssues) + 1,
  127. Name: form.IssueName,
  128. PosterId: ctx.User.Id,
  129. MilestoneId: form.MilestoneId,
  130. AssigneeId: form.AssigneeId,
  131. Labels: form.Labels,
  132. Content: form.Content,
  133. }
  134. if err := models.NewIssue(issue); err != nil {
  135. ctx.Handle(500, "issue.CreateIssue(NewIssue)", err)
  136. return
  137. } else if err := models.NewIssueUserPairs(issue.RepoId, issue.Id, ctx.Repo.Owner.Id,
  138. ctx.User.Id, form.AssigneeId, ctx.Repo.Repository.Name); err != nil {
  139. ctx.Handle(500, "issue.CreateIssue(NewIssueUserPairs)", err)
  140. return
  141. }
  142. // Update mentions.
  143. ms := base.MentionPattern.FindAllString(issue.Content, -1)
  144. if len(ms) > 0 {
  145. for i := range ms {
  146. ms[i] = ms[i][1:]
  147. }
  148. ids := models.GetUserIdsByNames(ms)
  149. if err := models.UpdateIssueUserPairsByMentions(ids, issue.Id); err != nil {
  150. ctx.Handle(500, "issue.CreateIssue(UpdateIssueUserPairsByMentions)", err)
  151. return
  152. }
  153. }
  154. act := &models.Action{
  155. ActUserId: ctx.User.Id,
  156. ActUserName: ctx.User.Name,
  157. ActEmail: ctx.User.Email,
  158. OpType: models.OP_CREATE_ISSUE,
  159. Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
  160. RepoId: ctx.Repo.Repository.Id,
  161. RepoUserName: ctx.Repo.Owner.Name,
  162. RepoName: ctx.Repo.Repository.Name,
  163. RefName: ctx.Repo.BranchName,
  164. IsPrivate: ctx.Repo.Repository.IsPrivate,
  165. }
  166. // Notify watchers.
  167. if err := models.NotifyWatchers(act); err != nil {
  168. ctx.Handle(500, "issue.CreateIssue(NotifyWatchers)", err)
  169. return
  170. }
  171. // Mail watchers and mentions.
  172. if base.Service.NotifyMail {
  173. tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
  174. if err != nil {
  175. ctx.Handle(500, "issue.CreateIssue(SendIssueNotifyMail)", err)
  176. return
  177. }
  178. tos = append(tos, ctx.User.LowerName)
  179. newTos := make([]string, 0, len(ms))
  180. for _, m := range ms {
  181. if com.IsSliceContainsStr(tos, m) {
  182. continue
  183. }
  184. newTos = append(newTos, m)
  185. }
  186. if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
  187. ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
  188. ctx.Handle(500, "issue.CreateIssue(SendIssueMentionMail)", err)
  189. return
  190. }
  191. }
  192. log.Trace("%d Issue created: %d", ctx.Repo.Repository.Id, issue.Id)
  193. ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index))
  194. }
  195. func ViewIssue(ctx *middleware.Context, params martini.Params) {
  196. idx, _ := base.StrTo(params["index"]).Int64()
  197. if idx == 0 {
  198. ctx.Handle(404, "issue.ViewIssue", nil)
  199. return
  200. }
  201. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
  202. if err != nil {
  203. if err == models.ErrIssueNotExist {
  204. ctx.Handle(404, "issue.ViewIssue(GetIssueByIndex)", err)
  205. } else {
  206. ctx.Handle(500, "issue.ViewIssue(GetIssueByIndex)", err)
  207. }
  208. return
  209. }
  210. us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
  211. if err != nil {
  212. ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
  213. return
  214. }
  215. ctx.Data["Collaborators"] = us
  216. if ctx.IsSigned {
  217. // Update issue-user.
  218. if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.Id); err != nil {
  219. ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByRead): %v", err)
  220. return
  221. }
  222. }
  223. // Get poster and Assignee.
  224. if err = issue.GetPoster(); err != nil {
  225. ctx.Handle(500, "issue.ViewIssue(GetPoster): %v", err)
  226. return
  227. } else if err = issue.GetAssignee(); err != nil {
  228. ctx.Handle(500, "issue.ViewIssue(GetAssignee): %v", err)
  229. return
  230. }
  231. issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink))
  232. // Get comments.
  233. comments, err := models.GetIssueComments(issue.Id)
  234. if err != nil {
  235. ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err)
  236. return
  237. }
  238. // Get posters.
  239. for i := range comments {
  240. u, err := models.GetUserById(comments[i].PosterId)
  241. if err != nil {
  242. ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err)
  243. return
  244. }
  245. comments[i].Poster = u
  246. comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
  247. }
  248. ctx.Data["Title"] = issue.Name
  249. ctx.Data["Issue"] = issue
  250. ctx.Data["Comments"] = comments
  251. ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id)
  252. ctx.Data["IsRepoToolbarIssues"] = true
  253. ctx.Data["IsRepoToolbarIssuesList"] = false
  254. ctx.HTML(200, "issue/view")
  255. }
  256. func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
  257. idx, err := base.StrTo(params["index"]).Int()
  258. if err != nil {
  259. ctx.Error(404)
  260. return
  261. }
  262. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(idx))
  263. if err != nil {
  264. if err == models.ErrIssueNotExist {
  265. ctx.Handle(404, "issue.UpdateIssue", err)
  266. } else {
  267. ctx.Handle(500, "issue.UpdateIssue(GetIssueByIndex)", err)
  268. }
  269. return
  270. }
  271. if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner {
  272. ctx.Handle(404, "issue.UpdateIssue", nil)
  273. return
  274. }
  275. issue.Name = form.IssueName
  276. issue.MilestoneId = form.MilestoneId
  277. issue.AssigneeId = form.AssigneeId
  278. issue.Labels = form.Labels
  279. issue.Content = form.Content
  280. if err = models.UpdateIssue(issue); err != nil {
  281. ctx.Handle(500, "issue.UpdateIssue(UpdateIssue)", err)
  282. return
  283. }
  284. ctx.JSON(200, map[string]interface{}{
  285. "ok": true,
  286. "title": issue.Name,
  287. "content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)),
  288. })
  289. }
  290. func UpdateAssignee(ctx *middleware.Context) {
  291. if !ctx.Repo.IsOwner {
  292. ctx.Error(403)
  293. return
  294. }
  295. idx, err := base.StrTo(ctx.Query("issue")).Int64()
  296. if err != nil {
  297. ctx.Error(404)
  298. return
  299. }
  300. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
  301. if err != nil {
  302. if err == models.ErrIssueNotExist {
  303. ctx.Handle(404, "issue.UpdateAssignee", err)
  304. } else {
  305. ctx.Handle(500, "issue.UpdateAssignee(GetIssueByIndex)", err)
  306. }
  307. return
  308. }
  309. aid, _ := base.StrTo(ctx.Query("assigneeid")).Int64()
  310. // Not check for invalid assignne id and give responsibility to owners.
  311. issue.AssigneeId = aid
  312. if err = models.UpdateIssueUserPairByAssignee(aid, issue.Id); err != nil {
  313. ctx.Handle(500, "issue.UpdateAssignee(UpdateIssueUserPairByAssignee): %v", err)
  314. return
  315. } else if err = models.UpdateIssue(issue); err != nil {
  316. ctx.Handle(500, "issue.UpdateAssignee(UpdateIssue)", err)
  317. return
  318. }
  319. ctx.JSON(200, map[string]interface{}{
  320. "ok": true,
  321. })
  322. }
  323. func Comment(ctx *middleware.Context, params martini.Params) {
  324. index, err := base.StrTo(ctx.Query("issueIndex")).Int64()
  325. if err != nil {
  326. ctx.Handle(404, "issue.Comment(get index)", err)
  327. return
  328. }
  329. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index)
  330. if err != nil {
  331. if err == models.ErrIssueNotExist {
  332. ctx.Handle(404, "issue.Comment", err)
  333. } else {
  334. ctx.Handle(200, "issue.Comment(get issue)", err)
  335. }
  336. return
  337. }
  338. // Check if issue owner changes the status of issue.
  339. var newStatus string
  340. if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id {
  341. newStatus = ctx.Query("change_status")
  342. }
  343. if len(newStatus) > 0 {
  344. if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) ||
  345. (strings.Contains(newStatus, "Close") && !issue.IsClosed) {
  346. issue.IsClosed = !issue.IsClosed
  347. if err = models.UpdateIssue(issue); err != nil {
  348. ctx.Handle(500, "issue.Comment(UpdateIssue)", err)
  349. return
  350. } else if err = models.UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil {
  351. ctx.Handle(500, "issue.Comment(UpdateIssueUserPairsByStatus)", err)
  352. return
  353. }
  354. cmtType := models.IT_CLOSE
  355. if !issue.IsClosed {
  356. cmtType = models.IT_REOPEN
  357. }
  358. if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, ""); err != nil {
  359. ctx.Handle(200, "issue.Comment(create status change comment)", err)
  360. return
  361. }
  362. log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.Id, !issue.IsClosed)
  363. }
  364. }
  365. var ms []string
  366. content := ctx.Query("content")
  367. if len(content) > 0 {
  368. switch params["action"] {
  369. case "new":
  370. if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.IT_PLAIN, content); err != nil {
  371. ctx.Handle(500, "issue.Comment(create comment)", err)
  372. return
  373. }
  374. // Update mentions.
  375. ms = base.MentionPattern.FindAllString(issue.Content, -1)
  376. if len(ms) > 0 {
  377. for i := range ms {
  378. ms[i] = ms[i][1:]
  379. }
  380. ids := models.GetUserIdsByNames(ms)
  381. if err := models.UpdateIssueUserPairsByMentions(ids, issue.Id); err != nil {
  382. ctx.Handle(500, "issue.CreateIssue(UpdateIssueUserPairsByMentions)", err)
  383. return
  384. }
  385. }
  386. log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id)
  387. default:
  388. ctx.Handle(404, "issue.Comment", err)
  389. return
  390. }
  391. }
  392. // Notify watchers.
  393. if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name, ActEmail: ctx.User.Email,
  394. OpType: models.OP_COMMENT_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]),
  395. RepoId: ctx.Repo.Repository.Id, RepoName: ctx.Repo.Repository.Name, RefName: ""}); err != nil {
  396. ctx.Handle(500, "issue.CreateIssue(NotifyWatchers)", err)
  397. return
  398. }
  399. // Mail watchers and mentions.
  400. if base.Service.NotifyMail {
  401. issue.Content = content
  402. tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
  403. if err != nil {
  404. ctx.Handle(500, "issue.Comment(SendIssueNotifyMail)", err)
  405. return
  406. }
  407. tos = append(tos, ctx.User.LowerName)
  408. newTos := make([]string, 0, len(ms))
  409. for _, m := range ms {
  410. if com.IsSliceContainsStr(tos, m) {
  411. continue
  412. }
  413. newTos = append(newTos, m)
  414. }
  415. if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
  416. ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
  417. ctx.Handle(500, "issue.Comment(SendIssueMentionMail)", err)
  418. return
  419. }
  420. }
  421. ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index))
  422. }
  423. func Milestones(ctx *middleware.Context) {
  424. ctx.Data["Title"] = "Milestones"
  425. ctx.Data["IsRepoToolbarIssues"] = true
  426. ctx.Data["IsRepoToolbarIssuesList"] = true
  427. isShowClosed := ctx.Query("state") == "closed"
  428. miles, err := models.GetMilestones(ctx.Repo.Repository.Id, isShowClosed)
  429. if err != nil {
  430. ctx.Handle(500, "issue.Milestones(GetMilestones)", err)
  431. return
  432. }
  433. for _, m := range miles {
  434. m.RenderedContent = string(base.RenderSpecialLink([]byte(m.Content), ctx.Repo.RepoLink))
  435. m.CalOpenIssues()
  436. }
  437. ctx.Data["Milestones"] = miles
  438. if isShowClosed {
  439. ctx.Data["State"] = "closed"
  440. } else {
  441. ctx.Data["State"] = "open"
  442. }
  443. ctx.HTML(200, "issue/milestone")
  444. }
  445. func NewMilestone(ctx *middleware.Context) {
  446. ctx.Data["Title"] = "New Milestone"
  447. ctx.Data["IsRepoToolbarIssues"] = true
  448. ctx.Data["IsRepoToolbarIssuesList"] = true
  449. ctx.HTML(200, "issue/milestone_new")
  450. }
  451. func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
  452. ctx.Data["Title"] = "New Milestone"
  453. ctx.Data["IsRepoToolbarIssues"] = true
  454. ctx.Data["IsRepoToolbarIssuesList"] = true
  455. var deadline time.Time
  456. var err error
  457. if len(form.Deadline) == 0 {
  458. form.Deadline = "12/31/9999"
  459. }
  460. deadline, err = time.Parse("01/02/2006", form.Deadline)
  461. if err != nil {
  462. ctx.Handle(500, "issue.NewMilestonePost(time.Parse)", err)
  463. return
  464. }
  465. mile := &models.Milestone{
  466. RepoId: ctx.Repo.Repository.Id,
  467. Index: int64(ctx.Repo.Repository.NumMilestones) + 1,
  468. Name: form.Title,
  469. Content: form.Content,
  470. Deadline: deadline,
  471. }
  472. if err = models.NewMilestone(mile); err != nil {
  473. ctx.Handle(500, "issue.NewMilestonePost(NewMilestone)", err)
  474. return
  475. }
  476. ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
  477. }
  478. func UpdateMilestone(ctx *middleware.Context, params martini.Params) {
  479. ctx.Data["Title"] = "Update Milestone"
  480. ctx.Data["IsRepoToolbarIssues"] = true
  481. ctx.Data["IsRepoToolbarIssuesList"] = true
  482. idx, _ := base.StrTo(params["index"]).Int64()
  483. if idx == 0 {
  484. ctx.Handle(404, "issue.UpdateMilestone", nil)
  485. return
  486. }
  487. mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx)
  488. if err != nil {
  489. if err == models.ErrMilestoneNotExist {
  490. ctx.Handle(404, "issue.UpdateMilestone(GetMilestoneByIndex)", err)
  491. } else {
  492. ctx.Handle(500, "issue.UpdateMilestone(GetMilestoneByIndex)", err)
  493. }
  494. return
  495. }
  496. mile.DeadlineString = mile.Deadline.UTC().Format("01/02/2006")
  497. if mile.DeadlineString == "12/31/9999" {
  498. mile.DeadlineString = ""
  499. }
  500. ctx.Data["Milestone"] = mile
  501. ctx.HTML(200, "issue/milestone_edit")
  502. }
  503. func UpdateMilestonePost(ctx *middleware.Context, params martini.Params, form auth.CreateMilestoneForm) {
  504. ctx.Data["Title"] = "Update Milestone"
  505. ctx.Data["IsRepoToolbarIssues"] = true
  506. ctx.Data["IsRepoToolbarIssuesList"] = true
  507. idx, _ := base.StrTo(params["index"]).Int64()
  508. if idx == 0 {
  509. ctx.Handle(404, "issue.UpdateMilestone", nil)
  510. return
  511. }
  512. mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx)
  513. if err != nil {
  514. if err == models.ErrMilestoneNotExist {
  515. ctx.Handle(404, "issue.UpdateMilestone(GetMilestoneByIndex)", err)
  516. } else {
  517. ctx.Handle(500, "issue.UpdateMilestone(GetMilestoneByIndex)", err)
  518. }
  519. return
  520. }
  521. var deadline time.Time
  522. if len(form.Deadline) == 0 {
  523. form.Deadline = "12/31/9999"
  524. }
  525. deadline, err = time.Parse("01/02/2006", form.Deadline)
  526. if err != nil {
  527. ctx.Handle(500, "issue.UpdateMilestonePost(time.Parse)", err)
  528. return
  529. }
  530. mile.Name = form.Title
  531. mile.Content = form.Content
  532. mile.Deadline = deadline
  533. if err = models.UpdateMilestone(mile); err != nil {
  534. ctx.Handle(500, "issue.UpdateMilestonePost(UpdateMilestone)", err)
  535. return
  536. }
  537. ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
  538. }