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 41 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
10 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
10 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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524
  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. "fmt"
  9. "io"
  10. "mime/multipart"
  11. "os"
  12. "path"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/Unknwon/com"
  17. "github.com/go-xorm/xorm"
  18. "github.com/gogits/gogs/modules/base"
  19. "github.com/gogits/gogs/modules/log"
  20. "github.com/gogits/gogs/modules/setting"
  21. gouuid "github.com/gogits/gogs/modules/uuid"
  22. )
  23. var (
  24. ErrWrongIssueCounter = errors.New("Invalid number of issues for this milestone")
  25. ErrAttachmentNotLinked = errors.New("Attachment does not belong to this issue")
  26. ErrMissingIssueNumber = errors.New("No issue number specified")
  27. )
  28. // Issue represents an issue or pull request of repository.
  29. type Issue struct {
  30. ID int64 `xorm:"pk autoincr"`
  31. RepoID int64 `xorm:"INDEX"`
  32. Index int64 // Index in one repository.
  33. Name string
  34. Repo *Repository `xorm:"-"`
  35. PosterID int64
  36. Poster *User `xorm:"-"`
  37. Labels []*Label `xorm:"-"`
  38. MilestoneID int64
  39. Milestone *Milestone `xorm:"-"`
  40. AssigneeID int64
  41. Assignee *User `xorm:"-"`
  42. IsRead bool `xorm:"-"`
  43. IsPull bool // Indicates whether is a pull request or not.
  44. IsClosed bool
  45. Content string `xorm:"TEXT"`
  46. RenderedContent string `xorm:"-"`
  47. Priority int
  48. NumComments int
  49. Deadline time.Time
  50. Created time.Time `xorm:"CREATED"`
  51. Updated time.Time `xorm:"UPDATED"`
  52. Attachments []*Attachment `xorm:"-"`
  53. Comments []*Comment `xorm:"-"`
  54. }
  55. // HashTag returns unique hash tag for issue.
  56. func (i *Issue) HashTag() string {
  57. return "issue-" + com.ToStr(i.ID)
  58. }
  59. func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
  60. var err error
  61. switch colName {
  62. case "id":
  63. i.Attachments, err = GetAttachmentsByIssueID(i.ID)
  64. if err != nil {
  65. log.Error(3, "GetAttachmentsByIssueID[%d]: %v", i.ID, err)
  66. }
  67. i.Comments, err = GetCommentsByIssueID(i.ID)
  68. if err != nil {
  69. log.Error(3, "GetCommentsByIssueID[%d]: %v", i.ID, err)
  70. }
  71. case "milestone_id":
  72. if i.MilestoneID == 0 {
  73. return
  74. }
  75. i.Milestone, err = GetMilestoneByID(i.MilestoneID)
  76. if err != nil {
  77. log.Error(3, "GetMilestoneById[%d]: %v", i.ID, err)
  78. }
  79. case "assignee_id":
  80. if i.AssigneeID == 0 {
  81. return
  82. }
  83. i.Assignee, err = GetUserByID(i.AssigneeID)
  84. if err != nil {
  85. log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
  86. }
  87. }
  88. }
  89. // IsPoster returns true if given user by ID is the poster.
  90. func (i *Issue) IsPoster(uid int64) bool {
  91. return i.PosterID == uid
  92. }
  93. func (i *Issue) GetPoster() (err error) {
  94. i.Poster, err = GetUserByID(i.PosterID)
  95. if IsErrUserNotExist(err) {
  96. i.Poster = &User{Name: "Someone"}
  97. return nil
  98. }
  99. return err
  100. }
  101. func (i *Issue) hasLabel(e Engine, labelID int64) bool {
  102. return hasIssueLabel(e, i.ID, labelID)
  103. }
  104. // HasLabel returns true if issue has been labeled by given ID.
  105. func (i *Issue) HasLabel(labelID int64) bool {
  106. return i.hasLabel(x, labelID)
  107. }
  108. func (i *Issue) addLabel(e Engine, labelID int64) error {
  109. return newIssueLabel(e, i.ID, labelID)
  110. }
  111. // AddLabel adds new label to issue by given ID.
  112. func (i *Issue) AddLabel(labelID int64) error {
  113. return i.addLabel(x, labelID)
  114. }
  115. func (i *Issue) getLabels(e Engine) (err error) {
  116. if len(i.Labels) > 0 {
  117. return nil
  118. }
  119. i.Labels, err = getLabelsByIssueID(e, i.ID)
  120. if err != nil {
  121. return fmt.Errorf("getLabelsByIssueID: %v", err)
  122. }
  123. return nil
  124. }
  125. // GetLabels retrieves all labels of issue and assign to corresponding field.
  126. func (i *Issue) GetLabels() error {
  127. return i.getLabels(x)
  128. }
  129. func (i *Issue) removeLabel(e Engine, labelID int64) error {
  130. return deleteIssueLabel(e, i.ID, labelID)
  131. }
  132. // RemoveLabel removes a label from issue by given ID.
  133. func (i *Issue) RemoveLabel(labelID int64) error {
  134. return i.removeLabel(x, labelID)
  135. }
  136. func (i *Issue) GetAssignee() (err error) {
  137. if i.AssigneeID == 0 || i.Assignee != nil {
  138. return nil
  139. }
  140. i.Assignee, err = GetUserByID(i.AssigneeID)
  141. if IsErrUserNotExist(err) {
  142. return nil
  143. }
  144. return err
  145. }
  146. // ReadBy sets issue to be read by given user.
  147. func (i *Issue) ReadBy(uid int64) error {
  148. return UpdateIssueUserByRead(uid, i.ID)
  149. }
  150. func (i *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (err error) {
  151. if i.IsClosed == isClosed {
  152. return nil
  153. }
  154. i.IsClosed = isClosed
  155. if err = updateIssue(e, i); err != nil {
  156. return err
  157. } else if err = updateIssueUsersByStatus(e, i.ID, isClosed); err != nil {
  158. return err
  159. }
  160. // Update labels.
  161. if err = i.getLabels(e); err != nil {
  162. return err
  163. }
  164. for idx := range i.Labels {
  165. if i.IsClosed {
  166. i.Labels[idx].NumClosedIssues++
  167. } else {
  168. i.Labels[idx].NumClosedIssues--
  169. }
  170. if err = updateLabel(e, i.Labels[idx]); err != nil {
  171. return err
  172. }
  173. }
  174. // Update milestone.
  175. if err = changeMilestoneIssueStats(e, i); err != nil {
  176. return err
  177. }
  178. // New action comment.
  179. if _, err = createStatusComment(e, doer, i.Repo, i); err != nil {
  180. return err
  181. }
  182. return nil
  183. }
  184. // ChangeStatus changes issue status to open/closed.
  185. func (i *Issue) ChangeStatus(doer *User, isClosed bool) (err error) {
  186. sess := x.NewSession()
  187. defer sessionRelease(sess)
  188. if err = sess.Begin(); err != nil {
  189. return err
  190. }
  191. if err = i.changeStatus(sess, doer, isClosed); err != nil {
  192. return err
  193. }
  194. return sess.Commit()
  195. }
  196. // CreateIssue creates new issue with labels for repository.
  197. func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
  198. // Check attachments.
  199. attachments := make([]*Attachment, 0, len(uuids))
  200. for _, uuid := range uuids {
  201. attach, err := GetAttachmentByUUID(uuid)
  202. if err != nil {
  203. if IsErrAttachmentNotExist(err) {
  204. continue
  205. }
  206. return fmt.Errorf("GetAttachmentByUUID[%s]: %v", uuid, err)
  207. }
  208. attachments = append(attachments, attach)
  209. }
  210. sess := x.NewSession()
  211. defer sessionRelease(sess)
  212. if err = sess.Begin(); err != nil {
  213. return err
  214. }
  215. if _, err = sess.Insert(issue); err != nil {
  216. return err
  217. } else if _, err = sess.Exec("UPDATE `repository` SET num_issues=num_issues+1 WHERE id=?", issue.RepoID); err != nil {
  218. return err
  219. }
  220. for _, id := range labelIDs {
  221. if err = issue.addLabel(sess, id); err != nil {
  222. return fmt.Errorf("addLabel: %v", err)
  223. }
  224. }
  225. if issue.MilestoneID > 0 {
  226. if err = changeMilestoneAssign(sess, 0, issue); err != nil {
  227. return err
  228. }
  229. }
  230. if err = newIssueUsers(sess, repo, issue); err != nil {
  231. return err
  232. }
  233. for i := range attachments {
  234. attachments[i].IssueID = issue.ID
  235. // No assign value could be 0, so ignore AllCols().
  236. if _, err = sess.Id(attachments[i].ID).Update(attachments[i]); err != nil {
  237. return fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
  238. }
  239. }
  240. // Notify watchers.
  241. act := &Action{
  242. ActUserID: issue.Poster.Id,
  243. ActUserName: issue.Poster.Name,
  244. ActEmail: issue.Poster.Email,
  245. OpType: CREATE_ISSUE,
  246. Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
  247. RepoID: repo.ID,
  248. RepoUserName: repo.Owner.Name,
  249. RepoName: repo.Name,
  250. IsPrivate: repo.IsPrivate,
  251. }
  252. if err = notifyWatchers(sess, act); err != nil {
  253. return err
  254. }
  255. return sess.Commit()
  256. }
  257. // GetIssueByRef returns an Issue specified by a GFM reference.
  258. // See https://help.github.com/articles/writing-on-github#references for more information on the syntax.
  259. func GetIssueByRef(ref string) (issue *Issue, err error) {
  260. var issueNumber int64
  261. var repo *Repository
  262. n := strings.IndexByte(ref, byte('#'))
  263. if n == -1 {
  264. return nil, ErrMissingIssueNumber
  265. }
  266. if issueNumber, err = strconv.ParseInt(ref[n+1:], 10, 64); err != nil {
  267. return
  268. }
  269. if repo, err = GetRepositoryByRef(ref[:n]); err != nil {
  270. return
  271. }
  272. return GetIssueByIndex(repo.ID, issueNumber)
  273. }
  274. // GetIssueByIndex returns issue by given index in repository.
  275. func GetIssueByIndex(repoID, index int64) (*Issue, error) {
  276. issue := &Issue{
  277. RepoID: repoID,
  278. Index: index,
  279. }
  280. has, err := x.Get(issue)
  281. if err != nil {
  282. return nil, err
  283. } else if !has {
  284. return nil, ErrIssueNotExist{0, repoID, index}
  285. }
  286. return issue, nil
  287. }
  288. // GetIssueByID returns an issue by given ID.
  289. func GetIssueByID(id int64) (*Issue, error) {
  290. issue := new(Issue)
  291. has, err := x.Id(id).Get(issue)
  292. if err != nil {
  293. return nil, err
  294. } else if !has {
  295. return nil, ErrIssueNotExist{id, 0, 0}
  296. }
  297. return issue, nil
  298. }
  299. // Issues returns a list of issues by given conditions.
  300. func Issues(uid, assigneeID, repoID, posterID, milestoneID int64, page int, isClosed, isMention bool, labels, sortType string) ([]*Issue, error) {
  301. sess := x.Limit(setting.IssuePagingNum, (page-1)*setting.IssuePagingNum)
  302. if repoID > 0 {
  303. sess.Where("issue.repo_id=?", repoID).And("issue.is_closed=?", isClosed)
  304. } else {
  305. sess.Where("issue.is_closed=?", isClosed)
  306. }
  307. if assigneeID > 0 {
  308. sess.And("issue.assignee_id=?", assigneeID)
  309. } else if posterID > 0 {
  310. sess.And("issue.poster_id=?", posterID)
  311. }
  312. if milestoneID > 0 {
  313. sess.And("issue.milestone_id=?", milestoneID)
  314. }
  315. switch sortType {
  316. case "oldest":
  317. sess.Asc("created")
  318. case "recentupdate":
  319. sess.Desc("updated")
  320. case "leastupdate":
  321. sess.Asc("updated")
  322. case "mostcomment":
  323. sess.Desc("num_comments")
  324. case "leastcomment":
  325. sess.Asc("num_comments")
  326. case "priority":
  327. sess.Desc("priority")
  328. default:
  329. sess.Desc("created")
  330. }
  331. labelIDs := base.StringsToInt64s(strings.Split(labels, ","))
  332. if len(labelIDs) > 0 {
  333. validJoin := false
  334. queryStr := "issue.id=issue_label.issue_id"
  335. for _, id := range labelIDs {
  336. if id == 0 {
  337. continue
  338. }
  339. validJoin = true
  340. queryStr += " AND issue_label.label_id=" + com.ToStr(id)
  341. }
  342. if validJoin {
  343. sess.Join("INNER", "issue_label", queryStr)
  344. }
  345. }
  346. if isMention {
  347. queryStr := "issue.id=issue_user.issue_id AND issue_user.is_mentioned=1"
  348. if uid > 0 {
  349. queryStr += " AND issue_user.uid=" + com.ToStr(uid)
  350. }
  351. sess.Join("INNER", "issue_user", queryStr)
  352. }
  353. issues := make([]*Issue, 0, setting.IssuePagingNum)
  354. return issues, sess.Find(&issues)
  355. }
  356. type IssueStatus int
  357. const (
  358. IS_OPEN = iota + 1
  359. IS_CLOSE
  360. )
  361. // GetIssueCountByPoster returns number of issues of repository by poster.
  362. func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
  363. count, _ := x.Where("repo_id=?", rid).And("poster_id=?", uid).And("is_closed=?", isClosed).Count(new(Issue))
  364. return count
  365. }
  366. // .___ ____ ___
  367. // | | ______ ________ __ ____ | | \______ ___________
  368. // | |/ ___// ___/ | \_/ __ \| | / ___// __ \_ __ \
  369. // | |\___ \ \___ \| | /\ ___/| | /\___ \\ ___/| | \/
  370. // |___/____ >____ >____/ \___ >______//____ >\___ >__|
  371. // \/ \/ \/ \/ \/
  372. // IssueUser represents an issue-user relation.
  373. type IssueUser struct {
  374. ID int64 `xorm:"pk autoincr"`
  375. UID int64 `xorm:"uid INDEX"` // User ID.
  376. IssueID int64
  377. RepoID int64 `xorm:"INDEX"`
  378. MilestoneID int64
  379. IsRead bool
  380. IsAssigned bool
  381. IsMentioned bool
  382. IsPoster bool
  383. IsClosed bool
  384. }
  385. func newIssueUsers(e *xorm.Session, repo *Repository, issue *Issue) error {
  386. users, err := repo.GetAssignees()
  387. if err != nil {
  388. return err
  389. }
  390. iu := &IssueUser{
  391. IssueID: issue.ID,
  392. RepoID: repo.ID,
  393. }
  394. // Poster can be anyone.
  395. isNeedAddPoster := true
  396. for _, u := range users {
  397. iu.ID = 0
  398. iu.UID = u.Id
  399. iu.IsPoster = iu.UID == issue.PosterID
  400. if isNeedAddPoster && iu.IsPoster {
  401. isNeedAddPoster = false
  402. }
  403. iu.IsAssigned = iu.UID == issue.AssigneeID
  404. if _, err = e.Insert(iu); err != nil {
  405. return err
  406. }
  407. }
  408. if isNeedAddPoster {
  409. iu.ID = 0
  410. iu.UID = issue.PosterID
  411. iu.IsPoster = true
  412. if _, err = e.Insert(iu); err != nil {
  413. return err
  414. }
  415. }
  416. return nil
  417. }
  418. // NewIssueUsers adds new issue-user relations for new issue of repository.
  419. func NewIssueUsers(repo *Repository, issue *Issue) (err error) {
  420. sess := x.NewSession()
  421. defer sessionRelease(sess)
  422. if err = sess.Begin(); err != nil {
  423. return err
  424. }
  425. if err = newIssueUsers(sess, repo, issue); err != nil {
  426. return err
  427. }
  428. return sess.Commit()
  429. }
  430. // PairsContains returns true when pairs list contains given issue.
  431. func PairsContains(ius []*IssueUser, issueId, uid int64) int {
  432. for i := range ius {
  433. if ius[i].IssueID == issueId &&
  434. ius[i].UID == uid {
  435. return i
  436. }
  437. }
  438. return -1
  439. }
  440. // GetIssueUsers returns issue-user pairs by given repository and user.
  441. func GetIssueUsers(rid, uid int64, isClosed bool) ([]*IssueUser, error) {
  442. ius := make([]*IssueUser, 0, 10)
  443. err := x.Where("is_closed=?", isClosed).Find(&ius, &IssueUser{RepoID: rid, UID: uid})
  444. return ius, err
  445. }
  446. // GetIssueUserPairsByRepoIds returns issue-user pairs by given repository IDs.
  447. func GetIssueUserPairsByRepoIds(rids []int64, isClosed bool, page int) ([]*IssueUser, error) {
  448. if len(rids) == 0 {
  449. return []*IssueUser{}, nil
  450. }
  451. buf := bytes.NewBufferString("")
  452. for _, rid := range rids {
  453. buf.WriteString("repo_id=")
  454. buf.WriteString(com.ToStr(rid))
  455. buf.WriteString(" OR ")
  456. }
  457. cond := strings.TrimSuffix(buf.String(), " OR ")
  458. ius := make([]*IssueUser, 0, 10)
  459. sess := x.Limit(20, (page-1)*20).Where("is_closed=?", isClosed)
  460. if len(cond) > 0 {
  461. sess.And(cond)
  462. }
  463. err := sess.Find(&ius)
  464. return ius, err
  465. }
  466. // GetIssueUserPairsByMode returns issue-user pairs by given repository and user.
  467. func GetIssueUserPairsByMode(uid, rid int64, isClosed bool, page, filterMode int) ([]*IssueUser, error) {
  468. ius := make([]*IssueUser, 0, 10)
  469. sess := x.Limit(20, (page-1)*20).Where("uid=?", uid).And("is_closed=?", isClosed)
  470. if rid > 0 {
  471. sess.And("repo_id=?", rid)
  472. }
  473. switch filterMode {
  474. case FM_ASSIGN:
  475. sess.And("is_assigned=?", true)
  476. case FM_CREATE:
  477. sess.And("is_poster=?", true)
  478. default:
  479. return ius, nil
  480. }
  481. err := sess.Find(&ius)
  482. return ius, err
  483. }
  484. // IssueStats represents issue statistic information.
  485. type IssueStats struct {
  486. OpenCount, ClosedCount int64
  487. AllCount int64
  488. AssignCount int64
  489. CreateCount int64
  490. MentionCount int64
  491. }
  492. // Filter modes.
  493. const (
  494. FM_ALL = iota
  495. FM_ASSIGN
  496. FM_CREATE
  497. FM_MENTION
  498. )
  499. func parseCountResult(results []map[string][]byte) int64 {
  500. if len(results) == 0 {
  501. return 0
  502. }
  503. for _, result := range results[0] {
  504. return com.StrTo(string(result)).MustInt64()
  505. }
  506. return 0
  507. }
  508. // GetIssueStats returns issue statistic information by given conditions.
  509. func GetIssueStats(repoID, uid, labelID, milestoneID int64, isShowClosed bool, filterMode int) *IssueStats {
  510. stats := &IssueStats{}
  511. // issue := new(Issue)
  512. queryStr := "SELECT COUNT(*) FROM `issue` "
  513. if labelID > 0 {
  514. queryStr += "INNER JOIN `issue_label` ON `issue`.id=`issue_label`.issue_id AND `issue_label`.label_id=" + com.ToStr(labelID)
  515. }
  516. baseCond := " WHERE issue.repo_id=? AND issue.is_closed=?"
  517. if milestoneID > 0 {
  518. baseCond += " AND issue.milestone_id=" + com.ToStr(milestoneID)
  519. }
  520. switch filterMode {
  521. case FM_ALL:
  522. resutls, _ := x.Query(queryStr+baseCond, repoID, false)
  523. stats.OpenCount = parseCountResult(resutls)
  524. resutls, _ = x.Query(queryStr+baseCond, repoID, true)
  525. stats.ClosedCount = parseCountResult(resutls)
  526. case FM_ASSIGN:
  527. baseCond += " AND assignee_id=?"
  528. resutls, _ := x.Query(queryStr+baseCond, repoID, false, uid)
  529. stats.OpenCount = parseCountResult(resutls)
  530. resutls, _ = x.Query(queryStr+baseCond, repoID, true, uid)
  531. stats.ClosedCount = parseCountResult(resutls)
  532. case FM_CREATE:
  533. baseCond += " AND poster_id=?"
  534. resutls, _ := x.Query(queryStr+baseCond, repoID, false, uid)
  535. stats.OpenCount = parseCountResult(resutls)
  536. resutls, _ = x.Query(queryStr+baseCond, repoID, true, uid)
  537. stats.ClosedCount = parseCountResult(resutls)
  538. case FM_MENTION:
  539. queryStr += " INNER JOIN `issue_user` ON `issue`.id=`issue_user`.issue_id"
  540. baseCond += " AND `issue_user`.uid=? AND `issue_user`.is_mentioned=?"
  541. resutls, _ := x.Query(queryStr+baseCond, repoID, false, uid, true)
  542. stats.OpenCount = parseCountResult(resutls)
  543. resutls, _ = x.Query(queryStr+baseCond, repoID, true, uid, true)
  544. stats.ClosedCount = parseCountResult(resutls)
  545. }
  546. return stats
  547. }
  548. // GetUserIssueStats returns issue statistic information for dashboard by given conditions.
  549. func GetUserIssueStats(uid int64, filterMode int) *IssueStats {
  550. stats := &IssueStats{}
  551. issue := new(Issue)
  552. stats.AssignCount, _ = x.Where("assignee_id=?", uid).And("is_closed=?", false).Count(issue)
  553. stats.CreateCount, _ = x.Where("poster_id=?", uid).And("is_closed=?", false).Count(issue)
  554. return stats
  555. }
  556. func updateIssue(e Engine, issue *Issue) error {
  557. _, err := e.Id(issue.ID).AllCols().Update(issue)
  558. return err
  559. }
  560. // UpdateIssue updates information of issue.
  561. func UpdateIssue(issue *Issue) error {
  562. return updateIssue(x, issue)
  563. }
  564. func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error {
  565. _, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID)
  566. return err
  567. }
  568. // UpdateIssueUsersByStatus updates issue-user relations by issue status.
  569. func UpdateIssueUsersByStatus(issueID int64, isClosed bool) error {
  570. return updateIssueUsersByStatus(x, issueID, isClosed)
  571. }
  572. func updateIssueUserByAssignee(e *xorm.Session, issueID, assigneeID int64) (err error) {
  573. if _, err = e.Exec("UPDATE `issue_user` SET is_assigned=? WHERE issue_id=?", false, issueID); err != nil {
  574. return err
  575. }
  576. // Assignee ID equals to 0 means clear assignee.
  577. if assigneeID == 0 {
  578. return nil
  579. }
  580. _, err = e.Exec("UPDATE `issue_user` SET is_assigned=? WHERE uid=? AND issue_id=?", true, assigneeID, issueID)
  581. return err
  582. }
  583. // UpdateIssueUserByAssignee updates issue-user relation for assignee.
  584. func UpdateIssueUserByAssignee(issueID, assigneeID int64) (err error) {
  585. sess := x.NewSession()
  586. defer sessionRelease(sess)
  587. if err = sess.Begin(); err != nil {
  588. return err
  589. }
  590. if err = updateIssueUserByAssignee(sess, issueID, assigneeID); err != nil {
  591. return err
  592. }
  593. return sess.Commit()
  594. }
  595. // UpdateIssueUserByRead updates issue-user relation for reading.
  596. func UpdateIssueUserByRead(uid, issueID int64) error {
  597. _, err := x.Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID)
  598. return err
  599. }
  600. // UpdateIssueUsersByMentions updates issue-user pairs by mentioning.
  601. func UpdateIssueUsersByMentions(uids []int64, iid int64) error {
  602. for _, uid := range uids {
  603. iu := &IssueUser{UID: uid, IssueID: iid}
  604. has, err := x.Get(iu)
  605. if err != nil {
  606. return err
  607. }
  608. iu.IsMentioned = true
  609. if has {
  610. _, err = x.Id(iu.ID).AllCols().Update(iu)
  611. } else {
  612. _, err = x.Insert(iu)
  613. }
  614. if err != nil {
  615. return err
  616. }
  617. }
  618. return nil
  619. }
  620. // .____ ___. .__
  621. // | | _____ \_ |__ ____ | |
  622. // | | \__ \ | __ \_/ __ \| |
  623. // | |___ / __ \| \_\ \ ___/| |__
  624. // |_______ (____ /___ /\___ >____/
  625. // \/ \/ \/ \/
  626. // Label represents a label of repository for issues.
  627. type Label struct {
  628. ID int64 `xorm:"pk autoincr"`
  629. RepoID int64 `xorm:"INDEX"`
  630. Name string
  631. Color string `xorm:"VARCHAR(7)"`
  632. NumIssues int
  633. NumClosedIssues int
  634. NumOpenIssues int `xorm:"-"`
  635. IsChecked bool `xorm:"-"`
  636. }
  637. // CalOpenIssues calculates the open issues of label.
  638. func (m *Label) CalOpenIssues() {
  639. m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
  640. }
  641. // NewLabel creates new label of repository.
  642. func NewLabel(l *Label) error {
  643. _, err := x.Insert(l)
  644. return err
  645. }
  646. func getLabelByID(e Engine, id int64) (*Label, error) {
  647. if id <= 0 {
  648. return nil, ErrLabelNotExist{id}
  649. }
  650. l := &Label{ID: id}
  651. has, err := x.Get(l)
  652. if err != nil {
  653. return nil, err
  654. } else if !has {
  655. return nil, ErrLabelNotExist{l.ID}
  656. }
  657. return l, nil
  658. }
  659. // GetLabelByID returns a label by given ID.
  660. func GetLabelByID(id int64) (*Label, error) {
  661. return getLabelByID(x, id)
  662. }
  663. // GetLabelsByRepoID returns all labels that belong to given repository by ID.
  664. func GetLabelsByRepoID(repoID int64) ([]*Label, error) {
  665. labels := make([]*Label, 0, 10)
  666. return labels, x.Where("repo_id=?", repoID).Find(&labels)
  667. }
  668. func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) {
  669. issueLabels, err := getIssueLabels(e, issueID)
  670. if err != nil {
  671. return nil, fmt.Errorf("getIssueLabels: %v", err)
  672. }
  673. var label *Label
  674. labels := make([]*Label, 0, len(issueLabels))
  675. for idx := range issueLabels {
  676. label, err = getLabelByID(e, issueLabels[idx].LabelID)
  677. if err != nil && !IsErrLabelNotExist(err) {
  678. return nil, fmt.Errorf("getLabelByID: %v", err)
  679. }
  680. labels = append(labels, label)
  681. }
  682. return labels, nil
  683. }
  684. // GetLabelsByIssueID returns all labels that belong to given issue by ID.
  685. func GetLabelsByIssueID(issueID int64) ([]*Label, error) {
  686. return getLabelsByIssueID(x, issueID)
  687. }
  688. func updateLabel(e Engine, l *Label) error {
  689. _, err := x.Id(l.ID).AllCols().Update(l)
  690. return err
  691. }
  692. // UpdateLabel updates label information.
  693. func UpdateLabel(l *Label) error {
  694. return updateLabel(x, l)
  695. }
  696. // DeleteLabel delete a label of given repository.
  697. func DeleteLabel(repoID, labelID int64) error {
  698. l, err := GetLabelByID(labelID)
  699. if err != nil {
  700. if IsErrLabelNotExist(err) {
  701. return nil
  702. }
  703. return err
  704. }
  705. sess := x.NewSession()
  706. defer sessionRelease(sess)
  707. if err = sess.Begin(); err != nil {
  708. return err
  709. }
  710. if _, err = x.Where("label_id=?", labelID).Delete(new(IssueLabel)); err != nil {
  711. return err
  712. } else if _, err = sess.Delete(l); err != nil {
  713. return err
  714. }
  715. return sess.Commit()
  716. }
  717. // .___ .____ ___. .__
  718. // | | ______ ________ __ ____ | | _____ \_ |__ ____ | |
  719. // | |/ ___// ___/ | \_/ __ \| | \__ \ | __ \_/ __ \| |
  720. // | |\___ \ \___ \| | /\ ___/| |___ / __ \| \_\ \ ___/| |__
  721. // |___/____ >____ >____/ \___ >_______ (____ /___ /\___ >____/
  722. // \/ \/ \/ \/ \/ \/ \/
  723. // IssueLabel represetns an issue-lable relation.
  724. type IssueLabel struct {
  725. ID int64 `xorm:"pk autoincr"`
  726. IssueID int64 `xorm:"UNIQUE(s)"`
  727. LabelID int64 `xorm:"UNIQUE(s)"`
  728. }
  729. func hasIssueLabel(e Engine, issueID, labelID int64) bool {
  730. has, _ := e.Where("issue_id=? AND label_id=?", issueID, labelID).Get(new(IssueLabel))
  731. return has
  732. }
  733. // HasIssueLabel returns true if issue has been labeled.
  734. func HasIssueLabel(issueID, labelID int64) bool {
  735. return hasIssueLabel(x, issueID, labelID)
  736. }
  737. func newIssueLabel(e Engine, issueID, labelID int64) error {
  738. if issueID == 0 || labelID == 0 {
  739. return nil
  740. }
  741. _, err := e.Insert(&IssueLabel{
  742. IssueID: issueID,
  743. LabelID: labelID,
  744. })
  745. return err
  746. }
  747. // NewIssueLabel creates a new issue-label relation.
  748. func NewIssueLabel(issueID, labelID int64) error {
  749. return newIssueLabel(x, issueID, labelID)
  750. }
  751. func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) {
  752. issueLabels := make([]*IssueLabel, 0, 10)
  753. return issueLabels, e.Where("issue_id=?", issueID).Asc("label_id").Find(&issueLabels)
  754. }
  755. // GetIssueLabels returns all issue-label relations of given issue by ID.
  756. func GetIssueLabels(issueID int64) ([]*IssueLabel, error) {
  757. return getIssueLabels(x, issueID)
  758. }
  759. func deleteIssueLabel(e Engine, issueID, labelID int64) error {
  760. _, err := e.Delete(&IssueLabel{
  761. IssueID: issueID,
  762. LabelID: labelID,
  763. })
  764. return err
  765. }
  766. // DeleteIssueLabel deletes issue-label relation.
  767. func DeleteIssueLabel(issueID, labelID int64) error {
  768. return deleteIssueLabel(x, issueID, labelID)
  769. }
  770. // _____ .__.__ __
  771. // / \ |__| | ____ _______/ |_ ____ ____ ____
  772. // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
  773. // / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
  774. // \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
  775. // \/ \/ \/ \/ \/
  776. // Milestone represents a milestone of repository.
  777. type Milestone struct {
  778. ID int64 `xorm:"pk autoincr"`
  779. RepoID int64 `xorm:"INDEX"`
  780. Name string
  781. Content string `xorm:"TEXT"`
  782. RenderedContent string `xorm:"-"`
  783. IsClosed bool
  784. NumIssues int
  785. NumClosedIssues int
  786. NumOpenIssues int `xorm:"-"`
  787. Completeness int // Percentage(1-100).
  788. Deadline time.Time
  789. DeadlineString string `xorm:"-"`
  790. IsOverDue bool `xorm:"-"`
  791. ClosedDate time.Time
  792. }
  793. func (m *Milestone) BeforeUpdate() {
  794. if m.NumIssues > 0 {
  795. m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
  796. } else {
  797. m.Completeness = 0
  798. }
  799. }
  800. func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
  801. if colName == "deadline" {
  802. if m.Deadline.Year() == 9999 {
  803. return
  804. }
  805. m.DeadlineString = m.Deadline.Format("2006-01-02")
  806. if time.Now().After(m.Deadline) {
  807. m.IsOverDue = true
  808. }
  809. }
  810. }
  811. // CalOpenIssues calculates the open issues of milestone.
  812. func (m *Milestone) CalOpenIssues() {
  813. m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
  814. }
  815. // NewMilestone creates new milestone of repository.
  816. func NewMilestone(m *Milestone) (err error) {
  817. sess := x.NewSession()
  818. defer sessionRelease(sess)
  819. if err = sess.Begin(); err != nil {
  820. return err
  821. }
  822. if _, err = sess.Insert(m); err != nil {
  823. return err
  824. }
  825. if _, err = sess.Exec("UPDATE `repository` SET num_milestones=num_milestones+1 WHERE id=?", m.RepoID); err != nil {
  826. return err
  827. }
  828. return sess.Commit()
  829. }
  830. func getMilestoneByID(e Engine, id int64) (*Milestone, error) {
  831. m := &Milestone{ID: id}
  832. has, err := x.Get(m)
  833. if err != nil {
  834. return nil, err
  835. } else if !has {
  836. return nil, ErrMilestoneNotExist{id, 0}
  837. }
  838. return m, nil
  839. }
  840. // GetMilestoneByID returns the milestone of given ID.
  841. func GetMilestoneByID(id int64) (*Milestone, error) {
  842. return getMilestoneByID(x, id)
  843. }
  844. // GetRepoMilestoneByID returns the milestone of given ID and repository.
  845. func GetRepoMilestoneByID(repoID, milestoneID int64) (*Milestone, error) {
  846. m := &Milestone{ID: milestoneID, RepoID: repoID}
  847. has, err := x.Get(m)
  848. if err != nil {
  849. return nil, err
  850. } else if !has {
  851. return nil, ErrMilestoneNotExist{milestoneID, repoID}
  852. }
  853. return m, nil
  854. }
  855. // GetAllRepoMilestones returns all milestones of given repository.
  856. func GetAllRepoMilestones(repoID int64) ([]*Milestone, error) {
  857. miles := make([]*Milestone, 0, 10)
  858. return miles, x.Where("repo_id=?", repoID).Find(&miles)
  859. }
  860. // GetMilestones returns a list of milestones of given repository and status.
  861. func GetMilestones(repoID int64, page int, isClosed bool) ([]*Milestone, error) {
  862. miles := make([]*Milestone, 0, setting.IssuePagingNum)
  863. sess := x.Where("repo_id=? AND is_closed=?", repoID, isClosed)
  864. if page > 0 {
  865. sess = sess.Limit(setting.IssuePagingNum, (page-1)*setting.IssuePagingNum)
  866. }
  867. return miles, sess.Find(&miles)
  868. }
  869. func updateMilestone(e Engine, m *Milestone) error {
  870. _, err := e.Id(m.ID).AllCols().Update(m)
  871. return err
  872. }
  873. // UpdateMilestone updates information of given milestone.
  874. func UpdateMilestone(m *Milestone) error {
  875. return updateMilestone(x, m)
  876. }
  877. func countRepoMilestones(e Engine, repoID int64) int64 {
  878. count, _ := e.Where("repo_id=?", repoID).Count(new(Milestone))
  879. return count
  880. }
  881. // CountRepoMilestones returns number of milestones in given repository.
  882. func CountRepoMilestones(repoID int64) int64 {
  883. return countRepoMilestones(x, repoID)
  884. }
  885. func countRepoClosedMilestones(e Engine, repoID int64) int64 {
  886. closed, _ := e.Where("repo_id=? AND is_closed=?", repoID, true).Count(new(Milestone))
  887. return closed
  888. }
  889. // CountRepoClosedMilestones returns number of closed milestones in given repository.
  890. func CountRepoClosedMilestones(repoID int64) int64 {
  891. return countRepoClosedMilestones(x, repoID)
  892. }
  893. // MilestoneStats returns number of open and closed milestones of given repository.
  894. func MilestoneStats(repoID int64) (open int64, closed int64) {
  895. open, _ = x.Where("repo_id=? AND is_closed=?", repoID, false).Count(new(Milestone))
  896. return open, CountRepoClosedMilestones(repoID)
  897. }
  898. // ChangeMilestoneStatus changes the milestone open/closed status.
  899. func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
  900. repo, err := GetRepositoryByID(m.RepoID)
  901. if err != nil {
  902. return err
  903. }
  904. sess := x.NewSession()
  905. defer sessionRelease(sess)
  906. if err = sess.Begin(); err != nil {
  907. return err
  908. }
  909. m.IsClosed = isClosed
  910. if err = updateMilestone(sess, m); err != nil {
  911. return err
  912. }
  913. repo.NumMilestones = int(countRepoMilestones(sess, repo.ID))
  914. repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID))
  915. if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil {
  916. return err
  917. }
  918. return sess.Commit()
  919. }
  920. func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error {
  921. if issue.MilestoneID == 0 {
  922. return nil
  923. }
  924. m, err := getMilestoneByID(e, issue.MilestoneID)
  925. if err != nil {
  926. return err
  927. }
  928. if issue.IsClosed {
  929. m.NumOpenIssues--
  930. m.NumClosedIssues++
  931. } else {
  932. m.NumOpenIssues++
  933. m.NumClosedIssues--
  934. }
  935. return updateMilestone(e, m)
  936. }
  937. // ChangeMilestoneIssueStats updates the open/closed issues counter and progress
  938. // for the milestone associated witht the given issue.
  939. func ChangeMilestoneIssueStats(issue *Issue) (err error) {
  940. sess := x.NewSession()
  941. defer sessionRelease(sess)
  942. if err = sess.Begin(); err != nil {
  943. return err
  944. }
  945. if err = changeMilestoneIssueStats(sess, issue); err != nil {
  946. return err
  947. }
  948. return sess.Commit()
  949. }
  950. func changeMilestoneAssign(e *xorm.Session, oldMid int64, issue *Issue) error {
  951. if oldMid > 0 {
  952. m, err := getMilestoneByID(e, oldMid)
  953. if err != nil {
  954. return err
  955. }
  956. m.NumIssues--
  957. if issue.IsClosed {
  958. m.NumClosedIssues--
  959. }
  960. if err = updateMilestone(e, m); err != nil {
  961. return err
  962. } else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id=0 WHERE issue_id=?", issue.ID); err != nil {
  963. return err
  964. }
  965. }
  966. if issue.MilestoneID > 0 {
  967. m, err := GetMilestoneByID(issue.MilestoneID)
  968. if err != nil {
  969. return err
  970. }
  971. m.NumIssues++
  972. if issue.IsClosed {
  973. m.NumClosedIssues++
  974. }
  975. if m.NumIssues == 0 {
  976. return ErrWrongIssueCounter
  977. }
  978. if err = updateMilestone(e, m); err != nil {
  979. return err
  980. } else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id=? WHERE issue_id=?", m.ID, issue.ID); err != nil {
  981. return err
  982. }
  983. }
  984. return nil
  985. }
  986. // ChangeMilestoneAssign changes assignment of milestone for issue.
  987. func ChangeMilestoneAssign(oldMid int64, issue *Issue) (err error) {
  988. sess := x.NewSession()
  989. defer sess.Close()
  990. if err = sess.Begin(); err != nil {
  991. return err
  992. }
  993. if err = changeMilestoneAssign(sess, oldMid, issue); err != nil {
  994. return err
  995. }
  996. return sess.Commit()
  997. }
  998. // DeleteMilestoneByID deletes a milestone by given ID.
  999. func DeleteMilestoneByID(mid int64) error {
  1000. m, err := GetMilestoneByID(mid)
  1001. if err != nil {
  1002. if IsErrMilestoneNotExist(err) {
  1003. return nil
  1004. }
  1005. return err
  1006. }
  1007. repo, err := GetRepositoryByID(m.RepoID)
  1008. if err != nil {
  1009. return err
  1010. }
  1011. sess := x.NewSession()
  1012. defer sessionRelease(sess)
  1013. if err = sess.Begin(); err != nil {
  1014. return err
  1015. }
  1016. if _, err = sess.Id(m.ID).Delete(m); err != nil {
  1017. return err
  1018. }
  1019. repo.NumMilestones = int(countRepoMilestones(sess, repo.ID))
  1020. repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID))
  1021. if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil {
  1022. return err
  1023. }
  1024. if _, err = sess.Exec("UPDATE `issue` SET milestone_id=0 WHERE milestone_id=?", m.ID); err != nil {
  1025. return err
  1026. } else if _, err = sess.Exec("UPDATE `issue_user` SET milestone_id=0 WHERE milestone_id=?", m.ID); err != nil {
  1027. return err
  1028. }
  1029. return sess.Commit()
  1030. }
  1031. // _________ __
  1032. // \_ ___ \ ____ _____ _____ ____ _____/ |_
  1033. // / \ \/ / _ \ / \ / \_/ __ \ / \ __\
  1034. // \ \___( <_> ) Y Y \ Y Y \ ___/| | \ |
  1035. // \______ /\____/|__|_| /__|_| /\___ >___| /__|
  1036. // \/ \/ \/ \/ \/
  1037. // CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
  1038. type CommentType int
  1039. const (
  1040. // Plain comment, can be associated with a commit (CommitId > 0) and a line (Line > 0)
  1041. COMMENT_TYPE_COMMENT CommentType = iota
  1042. COMMENT_TYPE_REOPEN
  1043. COMMENT_TYPE_CLOSE
  1044. // References.
  1045. COMMENT_TYPE_ISSUE
  1046. // Reference from some commit (not part of a pull request)
  1047. COMMENT_TYPE_COMMIT
  1048. // Reference from some pull request
  1049. COMMENT_TYPE_PULL
  1050. )
  1051. // Comment represents a comment in commit and issue page.
  1052. type Comment struct {
  1053. ID int64 `xorm:"pk autoincr"`
  1054. Type CommentType
  1055. PosterID int64
  1056. Poster *User `xorm:"-"`
  1057. IssueID int64 `xorm:"INDEX"`
  1058. CommitID int64
  1059. Line int64
  1060. Content string `xorm:"TEXT"`
  1061. RenderedContent string `xorm:"-"`
  1062. Created time.Time `xorm:"CREATED"`
  1063. Attachments []*Attachment `xorm:"-"`
  1064. }
  1065. // HashTag returns unique hash tag for issue.
  1066. func (c *Comment) HashTag() string {
  1067. return "issuecomment-" + com.ToStr(c.ID)
  1068. }
  1069. func (c *Comment) AfterSet(colName string, _ xorm.Cell) {
  1070. var err error
  1071. switch colName {
  1072. case "id":
  1073. c.Attachments, err = GetAttachmentsByCommentID(c.ID)
  1074. if err != nil {
  1075. log.Error(3, "GetAttachmentsByCommentID[%d]: %v", c.ID, err)
  1076. }
  1077. case "poster_id":
  1078. c.Poster, err = GetUserByID(c.PosterID)
  1079. if err != nil {
  1080. if IsErrUserNotExist(err) {
  1081. c.PosterID = -1
  1082. c.Poster = &User{Name: "someone"}
  1083. } else {
  1084. log.Error(3, "GetUserByID[%d]: %v", c.ID, err)
  1085. }
  1086. }
  1087. }
  1088. }
  1089. func createComment(e *xorm.Session, u *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, uuids []string) (_ *Comment, err error) {
  1090. comment := &Comment{
  1091. PosterID: u.Id,
  1092. Type: cmtType,
  1093. IssueID: issue.ID,
  1094. CommitID: commitID,
  1095. Line: line,
  1096. Content: content,
  1097. }
  1098. if _, err = e.Insert(comment); err != nil {
  1099. return nil, err
  1100. }
  1101. // Check comment type.
  1102. switch cmtType {
  1103. case COMMENT_TYPE_COMMENT:
  1104. if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", issue.ID); err != nil {
  1105. return nil, err
  1106. }
  1107. // Check attachments.
  1108. attachments := make([]*Attachment, 0, len(uuids))
  1109. for _, uuid := range uuids {
  1110. attach, err := getAttachmentByUUID(e, uuid)
  1111. if err != nil {
  1112. if IsErrAttachmentNotExist(err) {
  1113. continue
  1114. }
  1115. return nil, fmt.Errorf("getAttachmentByUUID[%s]: %v", uuid, err)
  1116. }
  1117. attachments = append(attachments, attach)
  1118. }
  1119. for i := range attachments {
  1120. attachments[i].IssueID = issue.ID
  1121. attachments[i].CommentID = comment.ID
  1122. // No assign value could be 0, so ignore AllCols().
  1123. if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil {
  1124. return nil, fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
  1125. }
  1126. }
  1127. // Notify watchers.
  1128. act := &Action{
  1129. ActUserID: u.Id,
  1130. ActUserName: u.LowerName,
  1131. ActEmail: u.Email,
  1132. OpType: COMMENT_ISSUE,
  1133. Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]),
  1134. RepoID: repo.ID,
  1135. RepoUserName: repo.Owner.LowerName,
  1136. RepoName: repo.LowerName,
  1137. IsPrivate: repo.IsPrivate,
  1138. }
  1139. if err = notifyWatchers(e, act); err != nil {
  1140. return nil, err
  1141. }
  1142. case COMMENT_TYPE_REOPEN:
  1143. if _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", repo.ID); err != nil {
  1144. return nil, err
  1145. }
  1146. case COMMENT_TYPE_CLOSE:
  1147. if _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", repo.ID); err != nil {
  1148. return nil, err
  1149. }
  1150. }
  1151. return comment, nil
  1152. }
  1153. func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) {
  1154. cmtType := COMMENT_TYPE_CLOSE
  1155. if !issue.IsClosed {
  1156. cmtType = COMMENT_TYPE_REOPEN
  1157. }
  1158. return createComment(e, doer, repo, issue, 0, 0, cmtType, "", nil)
  1159. }
  1160. // CreateComment creates comment of issue or commit.
  1161. func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, attachments []string) (comment *Comment, err error) {
  1162. sess := x.NewSession()
  1163. defer sessionRelease(sess)
  1164. if err = sess.Begin(); err != nil {
  1165. return nil, err
  1166. }
  1167. comment, err = createComment(sess, doer, repo, issue, commitID, line, cmtType, content, attachments)
  1168. if err != nil {
  1169. return nil, err
  1170. }
  1171. return comment, sess.Commit()
  1172. }
  1173. // CreateIssueComment creates a plain issue comment.
  1174. func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
  1175. return CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMENT, content, attachments)
  1176. }
  1177. // GetCommentById returns the comment with the given id
  1178. func GetCommentById(id int64) (*Comment, error) {
  1179. c := new(Comment)
  1180. _, err := x.Id(id).Get(c)
  1181. return c, err
  1182. }
  1183. // GetCommentsByIssueID returns all comments of issue by given ID.
  1184. func GetCommentsByIssueID(issueID int64) ([]*Comment, error) {
  1185. comments := make([]*Comment, 0, 10)
  1186. return comments, x.Where("issue_id=?", issueID).Asc("created").Find(&comments)
  1187. }
  1188. func (c *Comment) AfterDelete() {
  1189. _, err := DeleteAttachmentsByComment(c.ID, true)
  1190. if err != nil {
  1191. log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err)
  1192. }
  1193. }
  1194. // Attachment represent a attachment of issue/comment/release.
  1195. type Attachment struct {
  1196. ID int64 `xorm:"pk autoincr"`
  1197. UUID string `xorm:"uuid UNIQUE"`
  1198. IssueID int64 `xorm:"INDEX"`
  1199. CommentID int64
  1200. ReleaseID int64 `xorm:"INDEX"`
  1201. Name string
  1202. Created time.Time `xorm:"CREATED"`
  1203. }
  1204. // AttachmentLocalPath returns where attachment is stored in local file system based on given UUID.
  1205. func AttachmentLocalPath(uuid string) string {
  1206. return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid)
  1207. }
  1208. // LocalPath returns where attachment is stored in local file system.
  1209. func (attach *Attachment) LocalPath() string {
  1210. return AttachmentLocalPath(attach.UUID)
  1211. }
  1212. // NewAttachment creates a new attachment object.
  1213. func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment, err error) {
  1214. attach := &Attachment{
  1215. UUID: gouuid.NewV4().String(),
  1216. Name: name,
  1217. }
  1218. if err = os.MkdirAll(path.Dir(attach.LocalPath()), os.ModePerm); err != nil {
  1219. return nil, fmt.Errorf("MkdirAll: %v", err)
  1220. }
  1221. fw, err := os.Create(attach.LocalPath())
  1222. if err != nil {
  1223. return nil, fmt.Errorf("Create: %v", err)
  1224. }
  1225. defer fw.Close()
  1226. if _, err = fw.Write(buf); err != nil {
  1227. return nil, fmt.Errorf("Write: %v", err)
  1228. } else if _, err = io.Copy(fw, file); err != nil {
  1229. return nil, fmt.Errorf("Copy: %v", err)
  1230. }
  1231. sess := x.NewSession()
  1232. defer sessionRelease(sess)
  1233. if err := sess.Begin(); err != nil {
  1234. return nil, err
  1235. }
  1236. if _, err := sess.Insert(attach); err != nil {
  1237. return nil, err
  1238. }
  1239. return attach, sess.Commit()
  1240. }
  1241. func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) {
  1242. attach := &Attachment{UUID: uuid}
  1243. has, err := x.Get(attach)
  1244. if err != nil {
  1245. return nil, err
  1246. } else if !has {
  1247. return nil, ErrAttachmentNotExist{0, uuid}
  1248. }
  1249. return attach, nil
  1250. }
  1251. // GetAttachmentByUUID returns attachment by given UUID.
  1252. func GetAttachmentByUUID(uuid string) (*Attachment, error) {
  1253. return getAttachmentByUUID(x, uuid)
  1254. }
  1255. // GetAttachmentsByIssueID returns all attachments for given issue by ID.
  1256. func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) {
  1257. attachments := make([]*Attachment, 0, 10)
  1258. return attachments, x.Where("issue_id=? AND comment_id=0", issueID).Find(&attachments)
  1259. }
  1260. // GetAttachmentsByCommentID returns all attachments if comment by given ID.
  1261. func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) {
  1262. attachments := make([]*Attachment, 0, 10)
  1263. return attachments, x.Where("comment_id=?", commentID).Find(&attachments)
  1264. }
  1265. // DeleteAttachment deletes the given attachment and optionally the associated file.
  1266. func DeleteAttachment(a *Attachment, remove bool) error {
  1267. _, err := DeleteAttachments([]*Attachment{a}, remove)
  1268. return err
  1269. }
  1270. // DeleteAttachments deletes the given attachments and optionally the associated files.
  1271. func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
  1272. for i, a := range attachments {
  1273. if remove {
  1274. if err := os.Remove(a.LocalPath()); err != nil {
  1275. return i, err
  1276. }
  1277. }
  1278. if _, err := x.Delete(a.ID); err != nil {
  1279. return i, err
  1280. }
  1281. }
  1282. return len(attachments), nil
  1283. }
  1284. // DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
  1285. func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) {
  1286. attachments, err := GetAttachmentsByIssueID(issueId)
  1287. if err != nil {
  1288. return 0, err
  1289. }
  1290. return DeleteAttachments(attachments, remove)
  1291. }
  1292. // DeleteAttachmentsByComment deletes all attachments associated with the given comment.
  1293. func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) {
  1294. attachments, err := GetAttachmentsByCommentID(commentId)
  1295. if err != nil {
  1296. return 0, err
  1297. }
  1298. return DeleteAttachments(attachments, remove)
  1299. }