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