issue.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  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 models
  5. import (
  6. "bytes"
  7. "errors"
  8. "strings"
  9. "time"
  10. "github.com/go-xorm/xorm"
  11. "github.com/gogits/gogs/modules/base"
  12. )
  13. var (
  14. ErrIssueNotExist = errors.New("Issue does not exist")
  15. ErrLabelNotExist = errors.New("Label does not exist")
  16. ErrMilestoneNotExist = errors.New("Milestone does not exist")
  17. ErrWrongIssueCounter = errors.New("Invalid number of issues for this milestone")
  18. )
  19. // Issue represents an issue or pull request of repository.
  20. type Issue struct {
  21. Id int64
  22. RepoId int64 `xorm:"INDEX"`
  23. Index int64 // Index in one repository.
  24. Name string
  25. Repo *Repository `xorm:"-"`
  26. PosterId int64
  27. Poster *User `xorm:"-"`
  28. LabelIds string `xorm:"TEXT"`
  29. Labels []*Label `xorm:"-"`
  30. MilestoneId int64
  31. AssigneeId int64
  32. Assignee *User `xorm:"-"`
  33. IsRead bool `xorm:"-"`
  34. IsPull bool // Indicates whether is a pull request or not.
  35. IsClosed bool
  36. Content string `xorm:"TEXT"`
  37. RenderedContent string `xorm:"-"`
  38. Priority int
  39. NumComments int
  40. Deadline time.Time
  41. Created time.Time `xorm:"CREATED"`
  42. Updated time.Time `xorm:"UPDATED"`
  43. }
  44. func (i *Issue) GetPoster() (err error) {
  45. i.Poster, err = GetUserById(i.PosterId)
  46. if err == ErrUserNotExist {
  47. i.Poster = &User{Name: "FakeUser"}
  48. return nil
  49. }
  50. return err
  51. }
  52. func (i *Issue) GetLabels() error {
  53. if len(i.LabelIds) < 3 {
  54. return nil
  55. }
  56. strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$")
  57. i.Labels = make([]*Label, 0, len(strIds))
  58. for _, strId := range strIds {
  59. id, _ := base.StrTo(strId).Int64()
  60. if id > 0 {
  61. l, err := GetLabelById(id)
  62. if err != nil {
  63. if err == ErrLabelNotExist {
  64. continue
  65. }
  66. return err
  67. }
  68. i.Labels = append(i.Labels, l)
  69. }
  70. }
  71. return nil
  72. }
  73. func (i *Issue) GetAssignee() (err error) {
  74. if i.AssigneeId == 0 {
  75. return nil
  76. }
  77. i.Assignee, err = GetUserById(i.AssigneeId)
  78. if err == ErrUserNotExist {
  79. return nil
  80. }
  81. return err
  82. }
  83. // CreateIssue creates new issue for repository.
  84. func NewIssue(issue *Issue) (err error) {
  85. sess := x.NewSession()
  86. defer sess.Close()
  87. if err = sess.Begin(); err != nil {
  88. return err
  89. }
  90. if _, err = sess.Insert(issue); err != nil {
  91. sess.Rollback()
  92. return err
  93. }
  94. rawSql := "UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?"
  95. if _, err = sess.Exec(rawSql, issue.RepoId); err != nil {
  96. sess.Rollback()
  97. return err
  98. }
  99. return sess.Commit()
  100. }
  101. // GetIssueByIndex returns issue by given index in repository.
  102. func GetIssueByIndex(rid, index int64) (*Issue, error) {
  103. issue := &Issue{RepoId: rid, Index: index}
  104. has, err := x.Get(issue)
  105. if err != nil {
  106. return nil, err
  107. } else if !has {
  108. return nil, ErrIssueNotExist
  109. }
  110. return issue, nil
  111. }
  112. // GetIssueById returns an issue by ID.
  113. func GetIssueById(id int64) (*Issue, error) {
  114. issue := &Issue{Id: id}
  115. has, err := x.Get(issue)
  116. if err != nil {
  117. return nil, err
  118. } else if !has {
  119. return nil, ErrIssueNotExist
  120. }
  121. return issue, nil
  122. }
  123. // GetIssues returns a list of issues by given conditions.
  124. func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sortType string) ([]Issue, error) {
  125. sess := x.Limit(20, (page-1)*20)
  126. if rid > 0 {
  127. sess.Where("repo_id=?", rid).And("is_closed=?", isClosed)
  128. } else {
  129. sess.Where("is_closed=?", isClosed)
  130. }
  131. if uid > 0 {
  132. sess.And("assignee_id=?", uid)
  133. } else if pid > 0 {
  134. sess.And("poster_id=?", pid)
  135. }
  136. if mid > 0 {
  137. sess.And("milestone_id=?", mid)
  138. }
  139. if len(labelIds) > 0 {
  140. for _, label := range strings.Split(labelIds, ",") {
  141. sess.And("label_ids like '%$" + label + "|%'")
  142. }
  143. }
  144. switch sortType {
  145. case "oldest":
  146. sess.Asc("created")
  147. case "recentupdate":
  148. sess.Desc("updated")
  149. case "leastupdate":
  150. sess.Asc("updated")
  151. case "mostcomment":
  152. sess.Desc("num_comments")
  153. case "leastcomment":
  154. sess.Asc("num_comments")
  155. case "priority":
  156. sess.Desc("priority")
  157. default:
  158. sess.Desc("created")
  159. }
  160. var issues []Issue
  161. err := sess.Find(&issues)
  162. return issues, err
  163. }
  164. type IssueStatus int
  165. const (
  166. IS_OPEN = iota + 1
  167. IS_CLOSE
  168. )
  169. // GetIssuesByLabel returns a list of issues by given label and repository.
  170. func GetIssuesByLabel(repoId int64, label string) ([]*Issue, error) {
  171. issues := make([]*Issue, 0, 10)
  172. err := x.Where("repo_id=?", repoId).And("label_ids like '%$" + label + "|%'").Find(&issues)
  173. return issues, err
  174. }
  175. // GetIssueCountByPoster returns number of issues of repository by poster.
  176. func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
  177. count, _ := x.Where("repo_id=?", rid).And("poster_id=?", uid).And("is_closed=?", isClosed).Count(new(Issue))
  178. return count
  179. }
  180. // .___ ____ ___
  181. // | | ______ ________ __ ____ | | \______ ___________
  182. // | |/ ___// ___/ | \_/ __ \| | / ___// __ \_ __ \
  183. // | |\___ \ \___ \| | /\ ___/| | /\___ \\ ___/| | \/
  184. // |___/____ >____ >____/ \___ >______//____ >\___ >__|
  185. // \/ \/ \/ \/ \/
  186. // IssueUser represents an issue-user relation.
  187. type IssueUser struct {
  188. Id int64
  189. Uid int64 `xorm:"INDEX"` // User ID.
  190. IssueId int64
  191. RepoId int64 `xorm:"INDEX"`
  192. MilestoneId int64
  193. IsRead bool
  194. IsAssigned bool
  195. IsMentioned bool
  196. IsPoster bool
  197. IsClosed bool
  198. }
  199. // NewIssueUserPairs adds new issue-user pairs for new issue of repository.
  200. func NewIssueUserPairs(rid, iid, oid, pid, aid int64, repoName string) (err error) {
  201. iu := &IssueUser{IssueId: iid, RepoId: rid}
  202. us, err := GetCollaborators(repoName)
  203. if err != nil {
  204. return err
  205. }
  206. isNeedAddPoster := true
  207. for _, u := range us {
  208. iu.Uid = u.Id
  209. iu.IsPoster = iu.Uid == pid
  210. if isNeedAddPoster && iu.IsPoster {
  211. isNeedAddPoster = false
  212. }
  213. iu.IsAssigned = iu.Uid == aid
  214. if _, err = x.Insert(iu); err != nil {
  215. return err
  216. }
  217. }
  218. if isNeedAddPoster {
  219. iu.Uid = pid
  220. iu.IsPoster = true
  221. iu.IsAssigned = iu.Uid == aid
  222. if _, err = x.Insert(iu); err != nil {
  223. return err
  224. }
  225. }
  226. return nil
  227. }
  228. // PairsContains returns true when pairs list contains given issue.
  229. func PairsContains(ius []*IssueUser, issueId int64) int {
  230. for i := range ius {
  231. if ius[i].IssueId == issueId {
  232. return i
  233. }
  234. }
  235. return -1
  236. }
  237. // GetIssueUserPairs returns issue-user pairs by given repository and user.
  238. func GetIssueUserPairs(rid, uid int64, isClosed bool) ([]*IssueUser, error) {
  239. ius := make([]*IssueUser, 0, 10)
  240. err := x.Where("is_closed=?", isClosed).Find(&ius, &IssueUser{RepoId: rid, Uid: uid})
  241. return ius, err
  242. }
  243. // GetIssueUserPairsByRepoIds returns issue-user pairs by given repository IDs.
  244. func GetIssueUserPairsByRepoIds(rids []int64, isClosed bool, page int) ([]*IssueUser, error) {
  245. if len(rids) == 0 {
  246. return []*IssueUser{}, nil
  247. }
  248. buf := bytes.NewBufferString("")
  249. for _, rid := range rids {
  250. buf.WriteString("repo_id=")
  251. buf.WriteString(base.ToStr(rid))
  252. buf.WriteString(" OR ")
  253. }
  254. cond := strings.TrimSuffix(buf.String(), " OR ")
  255. ius := make([]*IssueUser, 0, 10)
  256. sess := x.Limit(20, (page-1)*20).Where("is_closed=?", isClosed)
  257. if len(cond) > 0 {
  258. sess.And(cond)
  259. }
  260. err := sess.Find(&ius)
  261. return ius, err
  262. }
  263. // GetIssueUserPairsByMode returns issue-user pairs by given repository and user.
  264. func GetIssueUserPairsByMode(uid, rid int64, isClosed bool, page, filterMode int) ([]*IssueUser, error) {
  265. ius := make([]*IssueUser, 0, 10)
  266. sess := x.Limit(20, (page-1)*20).Where("uid=?", uid).And("is_closed=?", isClosed)
  267. if rid > 0 {
  268. sess.And("repo_id=?", rid)
  269. }
  270. switch filterMode {
  271. case FM_ASSIGN:
  272. sess.And("is_assigned=?", true)
  273. case FM_CREATE:
  274. sess.And("is_poster=?", true)
  275. default:
  276. return ius, nil
  277. }
  278. err := sess.Find(&ius)
  279. return ius, err
  280. }
  281. // IssueStats represents issue statistic information.
  282. type IssueStats struct {
  283. OpenCount, ClosedCount int64
  284. AllCount int64
  285. AssignCount int64
  286. CreateCount int64
  287. MentionCount int64
  288. }
  289. // Filter modes.
  290. const (
  291. FM_ASSIGN = iota + 1
  292. FM_CREATE
  293. FM_MENTION
  294. )
  295. // GetIssueStats returns issue statistic information by given conditions.
  296. func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStats {
  297. stats := &IssueStats{}
  298. issue := new(Issue)
  299. tmpSess := &xorm.Session{}
  300. sess := x.Where("repo_id=?", rid)
  301. *tmpSess = *sess
  302. stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
  303. *tmpSess = *sess
  304. stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
  305. if isShowClosed {
  306. stats.AllCount = stats.ClosedCount
  307. } else {
  308. stats.AllCount = stats.OpenCount
  309. }
  310. if filterMode != FM_MENTION {
  311. sess = x.Where("repo_id=?", rid)
  312. switch filterMode {
  313. case FM_ASSIGN:
  314. sess.And("assignee_id=?", uid)
  315. case FM_CREATE:
  316. sess.And("poster_id=?", uid)
  317. default:
  318. goto nofilter
  319. }
  320. *tmpSess = *sess
  321. stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
  322. *tmpSess = *sess
  323. stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
  324. } else {
  325. sess := x.Where("repo_id=?", rid).And("uid=?", uid).And("is_mentioned=?", true)
  326. *tmpSess = *sess
  327. stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(new(IssueUser))
  328. *tmpSess = *sess
  329. stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(new(IssueUser))
  330. }
  331. nofilter:
  332. stats.AssignCount, _ = x.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("assignee_id=?", uid).Count(issue)
  333. stats.CreateCount, _ = x.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("poster_id=?", uid).Count(issue)
  334. stats.MentionCount, _ = x.Where("repo_id=?", rid).And("uid=?", uid).And("is_closed=?", isShowClosed).And("is_mentioned=?", true).Count(new(IssueUser))
  335. return stats
  336. }
  337. // GetUserIssueStats returns issue statistic information for dashboard by given conditions.
  338. func GetUserIssueStats(uid int64, filterMode int) *IssueStats {
  339. stats := &IssueStats{}
  340. issue := new(Issue)
  341. stats.AssignCount, _ = x.Where("assignee_id=?", uid).And("is_closed=?", false).Count(issue)
  342. stats.CreateCount, _ = x.Where("poster_id=?", uid).And("is_closed=?", false).Count(issue)
  343. return stats
  344. }
  345. // UpdateIssue updates information of issue.
  346. func UpdateIssue(issue *Issue) error {
  347. _, err := x.Id(issue.Id).AllCols().Update(issue)
  348. return err
  349. }
  350. // UpdateIssueUserByStatus updates issue-user pairs by issue status.
  351. func UpdateIssueUserPairsByStatus(iid int64, isClosed bool) error {
  352. rawSql := "UPDATE `issue_user` SET is_closed = ? WHERE issue_id = ?"
  353. _, err := x.Exec(rawSql, isClosed, iid)
  354. return err
  355. }
  356. // UpdateIssueUserPairByAssignee updates issue-user pair for assigning.
  357. func UpdateIssueUserPairByAssignee(aid, iid int64) error {
  358. rawSql := "UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?"
  359. if _, err := x.Exec(rawSql, false, iid); err != nil {
  360. return err
  361. }
  362. // Assignee ID equals to 0 means clear assignee.
  363. if aid == 0 {
  364. return nil
  365. }
  366. rawSql = "UPDATE `issue_user` SET is_assigned = true WHERE uid = ? AND issue_id = ?"
  367. _, err := x.Exec(rawSql, aid, iid)
  368. return err
  369. }
  370. // UpdateIssueUserPairByRead updates issue-user pair for reading.
  371. func UpdateIssueUserPairByRead(uid, iid int64) error {
  372. rawSql := "UPDATE `issue_user` SET is_read = ? WHERE uid = ? AND issue_id = ?"
  373. _, err := x.Exec(rawSql, true, uid, iid)
  374. return err
  375. }
  376. // UpdateIssueUserPairsByMentions updates issue-user pairs by mentioning.
  377. func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
  378. for _, uid := range uids {
  379. iu := &IssueUser{Uid: uid, IssueId: iid}
  380. has, err := x.Get(iu)
  381. if err != nil {
  382. return err
  383. }
  384. iu.IsMentioned = true
  385. if has {
  386. _, err = x.Id(iu.Id).AllCols().Update(iu)
  387. } else {
  388. _, err = x.Insert(iu)
  389. }
  390. if err != nil {
  391. return err
  392. }
  393. }
  394. return nil
  395. }
  396. // .____ ___. .__
  397. // | | _____ \_ |__ ____ | |
  398. // | | \__ \ | __ \_/ __ \| |
  399. // | |___ / __ \| \_\ \ ___/| |__
  400. // |_______ (____ /___ /\___ >____/
  401. // \/ \/ \/ \/
  402. // Label represents a label of repository for issues.
  403. type Label struct {
  404. Id int64
  405. RepoId int64 `xorm:"INDEX"`
  406. Name string
  407. Color string `xorm:"VARCHAR(7)"`
  408. NumIssues int
  409. NumClosedIssues int
  410. NumOpenIssues int `xorm:"-"`
  411. IsChecked bool `xorm:"-"`
  412. }
  413. // CalOpenIssues calculates the open issues of label.
  414. func (m *Label) CalOpenIssues() {
  415. m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
  416. }
  417. // NewLabel creates new label of repository.
  418. func NewLabel(l *Label) error {
  419. _, err := x.Insert(l)
  420. return err
  421. }
  422. // GetLabelById returns a label by given ID.
  423. func GetLabelById(id int64) (*Label, error) {
  424. if id <= 0 {
  425. return nil, ErrLabelNotExist
  426. }
  427. l := &Label{Id: id}
  428. has, err := x.Get(l)
  429. if err != nil {
  430. return nil, err
  431. } else if !has {
  432. return nil, ErrLabelNotExist
  433. }
  434. return l, nil
  435. }
  436. // GetLabels returns a list of labels of given repository ID.
  437. func GetLabels(repoId int64) ([]*Label, error) {
  438. labels := make([]*Label, 0, 10)
  439. err := x.Where("repo_id=?", repoId).Find(&labels)
  440. return labels, err
  441. }
  442. // UpdateLabel updates label information.
  443. func UpdateLabel(l *Label) error {
  444. _, err := x.Id(l.Id).Update(l)
  445. return err
  446. }
  447. // DeleteLabel delete a label of given repository.
  448. func DeleteLabel(repoId int64, strId string) error {
  449. id, _ := base.StrTo(strId).Int64()
  450. l, err := GetLabelById(id)
  451. if err != nil {
  452. if err == ErrLabelNotExist {
  453. return nil
  454. }
  455. return err
  456. }
  457. issues, err := GetIssuesByLabel(repoId, strId)
  458. if err != nil {
  459. return err
  460. }
  461. sess := x.NewSession()
  462. defer sess.Close()
  463. if err = sess.Begin(); err != nil {
  464. return err
  465. }
  466. for _, issue := range issues {
  467. issue.LabelIds = strings.Replace(issue.LabelIds, "$"+strId+"|", "", -1)
  468. if _, err = sess.Id(issue.Id).AllCols().Update(issue); err != nil {
  469. sess.Rollback()
  470. return err
  471. }
  472. }
  473. if _, err = sess.Delete(l); err != nil {
  474. sess.Rollback()
  475. return err
  476. }
  477. return sess.Commit()
  478. }
  479. // _____ .__.__ __
  480. // / \ |__| | ____ _______/ |_ ____ ____ ____
  481. // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
  482. // / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
  483. // \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
  484. // \/ \/ \/ \/ \/
  485. // Milestone represents a milestone of repository.
  486. type Milestone struct {
  487. Id int64
  488. RepoId int64 `xorm:"INDEX"`
  489. Index int64
  490. Name string
  491. Content string
  492. RenderedContent string `xorm:"-"`
  493. IsClosed bool
  494. NumIssues int
  495. NumClosedIssues int
  496. NumOpenIssues int `xorm:"-"`
  497. Completeness int // Percentage(1-100).
  498. Deadline time.Time
  499. DeadlineString string `xorm:"-"`
  500. ClosedDate time.Time
  501. }
  502. // CalOpenIssues calculates the open issues of milestone.
  503. func (m *Milestone) CalOpenIssues() {
  504. m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
  505. }
  506. // NewMilestone creates new milestone of repository.
  507. func NewMilestone(m *Milestone) (err error) {
  508. sess := x.NewSession()
  509. defer sess.Close()
  510. if err = sess.Begin(); err != nil {
  511. return err
  512. }
  513. if _, err = sess.Insert(m); err != nil {
  514. sess.Rollback()
  515. return err
  516. }
  517. rawSql := "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?"
  518. if _, err = sess.Exec(rawSql, m.RepoId); err != nil {
  519. sess.Rollback()
  520. return err
  521. }
  522. return sess.Commit()
  523. }
  524. // GetMilestoneById returns the milestone by given ID.
  525. func GetMilestoneById(id int64) (*Milestone, error) {
  526. m := &Milestone{Id: id}
  527. has, err := x.Get(m)
  528. if err != nil {
  529. return nil, err
  530. } else if !has {
  531. return nil, ErrMilestoneNotExist
  532. }
  533. return m, nil
  534. }
  535. // GetMilestoneByIndex returns the milestone of given repository and index.
  536. func GetMilestoneByIndex(repoId, idx int64) (*Milestone, error) {
  537. m := &Milestone{RepoId: repoId, Index: idx}
  538. has, err := x.Get(m)
  539. if err != nil {
  540. return nil, err
  541. } else if !has {
  542. return nil, ErrMilestoneNotExist
  543. }
  544. return m, nil
  545. }
  546. // GetMilestones returns a list of milestones of given repository and status.
  547. func GetMilestones(repoId int64, isClosed bool) ([]*Milestone, error) {
  548. miles := make([]*Milestone, 0, 10)
  549. err := x.Where("repo_id=?", repoId).And("is_closed=?", isClosed).Find(&miles)
  550. return miles, err
  551. }
  552. // UpdateMilestone updates information of given milestone.
  553. func UpdateMilestone(m *Milestone) error {
  554. _, err := x.Id(m.Id).Update(m)
  555. return err
  556. }
  557. // ChangeMilestoneStatus changes the milestone open/closed status.
  558. func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
  559. repo, err := GetRepositoryById(m.RepoId)
  560. if err != nil {
  561. return err
  562. }
  563. sess := x.NewSession()
  564. defer sess.Close()
  565. if err = sess.Begin(); err != nil {
  566. return err
  567. }
  568. m.IsClosed = isClosed
  569. if _, err = sess.Id(m.Id).AllCols().Update(m); err != nil {
  570. sess.Rollback()
  571. return err
  572. }
  573. if isClosed {
  574. repo.NumClosedMilestones++
  575. } else {
  576. repo.NumClosedMilestones--
  577. }
  578. if _, err = sess.Id(repo.Id).Update(repo); err != nil {
  579. sess.Rollback()
  580. return err
  581. }
  582. return sess.Commit()
  583. }
  584. // ChangeMilestoneAssign changes assignment of milestone for issue.
  585. func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
  586. sess := x.NewSession()
  587. defer sess.Close()
  588. if err = sess.Begin(); err != nil {
  589. return err
  590. }
  591. if oldMid > 0 {
  592. m, err := GetMilestoneById(oldMid)
  593. if err != nil {
  594. return err
  595. }
  596. m.NumIssues--
  597. if issue.IsClosed {
  598. m.NumClosedIssues--
  599. }
  600. if m.NumIssues > 0 {
  601. m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
  602. } else {
  603. m.Completeness = 0
  604. }
  605. if _, err = sess.Id(m.Id).Update(m); err != nil {
  606. sess.Rollback()
  607. return err
  608. }
  609. rawSql := "UPDATE `issue_user` SET milestone_id = 0 WHERE issue_id = ?"
  610. if _, err = sess.Exec(rawSql, issue.Id); err != nil {
  611. sess.Rollback()
  612. return err
  613. }
  614. }
  615. if mid > 0 {
  616. m, err := GetMilestoneById(mid)
  617. if err != nil {
  618. return err
  619. }
  620. m.NumIssues++
  621. if issue.IsClosed {
  622. m.NumClosedIssues++
  623. }
  624. if m.NumIssues == 0 {
  625. return ErrWrongIssueCounter
  626. }
  627. m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
  628. if _, err = sess.Id(m.Id).Update(m); err != nil {
  629. sess.Rollback()
  630. return err
  631. }
  632. rawSql := "UPDATE `issue_user` SET milestone_id = ? WHERE issue_id = ?"
  633. if _, err = sess.Exec(rawSql, m.Id, issue.Id); err != nil {
  634. sess.Rollback()
  635. return err
  636. }
  637. }
  638. return sess.Commit()
  639. }
  640. // DeleteMilestone deletes a milestone.
  641. func DeleteMilestone(m *Milestone) (err error) {
  642. sess := x.NewSession()
  643. defer sess.Close()
  644. if err = sess.Begin(); err != nil {
  645. return err
  646. }
  647. if _, err = sess.Delete(m); err != nil {
  648. sess.Rollback()
  649. return err
  650. }
  651. rawSql := "UPDATE `repository` SET num_milestones = num_milestones - 1 WHERE id = ?"
  652. if _, err = sess.Exec(rawSql, m.RepoId); err != nil {
  653. sess.Rollback()
  654. return err
  655. }
  656. rawSql = "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?"
  657. if _, err = sess.Exec(rawSql, m.Id); err != nil {
  658. sess.Rollback()
  659. return err
  660. }
  661. rawSql = "UPDATE `issue_user` SET milestone_id = 0 WHERE milestone_id = ?"
  662. if _, err = sess.Exec(rawSql, m.Id); err != nil {
  663. sess.Rollback()
  664. return err
  665. }
  666. return sess.Commit()
  667. }
  668. // _________ __
  669. // \_ ___ \ ____ _____ _____ ____ _____/ |_
  670. // / \ \/ / _ \ / \ / \_/ __ \ / \ __\
  671. // \ \___( <_> ) Y Y \ Y Y \ ___/| | \ |
  672. // \______ /\____/|__|_| /__|_| /\___ >___| /__|
  673. // \/ \/ \/ \/ \/
  674. // Issue types.
  675. const (
  676. IT_PLAIN = iota // Pure comment.
  677. IT_REOPEN // Issue reopen status change prompt.
  678. IT_CLOSE // Issue close status change prompt.
  679. )
  680. // Comment represents a comment in commit and issue page.
  681. type Comment struct {
  682. Id int64
  683. Type int
  684. PosterId int64
  685. Poster *User `xorm:"-"`
  686. IssueId int64
  687. CommitId int64
  688. Line int64
  689. Content string `xorm:"TEXT"`
  690. Created time.Time `xorm:"CREATED"`
  691. }
  692. // CreateComment creates comment of issue or commit.
  693. func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, content string) error {
  694. sess := x.NewSession()
  695. defer sess.Close()
  696. if err := sess.Begin(); err != nil {
  697. return err
  698. }
  699. if _, err := sess.Insert(&Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
  700. CommitId: commitId, Line: line, Content: content}); err != nil {
  701. sess.Rollback()
  702. return err
  703. }
  704. // Check comment type.
  705. switch cmtType {
  706. case IT_PLAIN:
  707. rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
  708. if _, err := sess.Exec(rawSql, issueId); err != nil {
  709. sess.Rollback()
  710. return err
  711. }
  712. case IT_REOPEN:
  713. rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
  714. if _, err := sess.Exec(rawSql, repoId); err != nil {
  715. sess.Rollback()
  716. return err
  717. }
  718. case IT_CLOSE:
  719. rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
  720. if _, err := sess.Exec(rawSql, repoId); err != nil {
  721. sess.Rollback()
  722. return err
  723. }
  724. }
  725. return sess.Commit()
  726. }
  727. // GetIssueComments returns list of comment by given issue id.
  728. func GetIssueComments(issueId int64) ([]Comment, error) {
  729. comments := make([]Comment, 0, 10)
  730. err := x.Asc("created").Find(&comments, &Comment{IssueId: issueId})
  731. return comments, err
  732. }