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