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 54 kB

12 years ago
12 years ago
12 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
10 years ago
10 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
12 years ago
12 years ago
10 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "mime/multipart"
  12. "os"
  13. "path"
  14. "strings"
  15. "time"
  16. "github.com/Unknwon/com"
  17. "github.com/go-xorm/xorm"
  18. "github.com/gogits/gogs/modules/base"
  19. "github.com/gogits/gogs/modules/git"
  20. "github.com/gogits/gogs/modules/log"
  21. "github.com/gogits/gogs/modules/process"
  22. "github.com/gogits/gogs/modules/setting"
  23. gouuid "github.com/gogits/gogs/modules/uuid"
  24. )
  25. var (
  26. ErrWrongIssueCounter = errors.New("Invalid number of issues for this milestone")
  27. ErrAttachmentNotLinked = errors.New("Attachment does not belong to this issue")
  28. ErrMissingIssueNumber = errors.New("No issue number specified")
  29. )
  30. // Issue represents an issue or pull request of repository.
  31. type Issue struct {
  32. ID int64 `xorm:"pk autoincr"`
  33. RepoID int64 `xorm:"INDEX"`
  34. Index int64 // Index in one repository.
  35. Name string
  36. Repo *Repository `xorm:"-"`
  37. PosterID int64
  38. Poster *User `xorm:"-"`
  39. Labels []*Label `xorm:"-"`
  40. MilestoneID int64
  41. Milestone *Milestone `xorm:"-"`
  42. AssigneeID int64
  43. Assignee *User `xorm:"-"`
  44. IsRead bool `xorm:"-"`
  45. IsPull bool // Indicates whether is a pull request or not.
  46. *PullRequest `xorm:"-"`
  47. IsClosed bool
  48. Content string `xorm:"TEXT"`
  49. RenderedContent string `xorm:"-"`
  50. Priority int
  51. NumComments int
  52. Deadline time.Time
  53. Created time.Time `xorm:"CREATED"`
  54. Updated time.Time `xorm:"UPDATED"`
  55. Attachments []*Attachment `xorm:"-"`
  56. Comments []*Comment `xorm:"-"`
  57. }
  58. func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
  59. var err error
  60. switch colName {
  61. case "id":
  62. i.Attachments, err = GetAttachmentsByIssueID(i.ID)
  63. if err != nil {
  64. log.Error(3, "GetAttachmentsByIssueID[%d]: %v", i.ID, err)
  65. }
  66. i.Comments, err = GetCommentsByIssueID(i.ID)
  67. if err != nil {
  68. log.Error(3, "GetCommentsByIssueID[%d]: %v", i.ID, err)
  69. }
  70. case "milestone_id":
  71. if i.MilestoneID == 0 {
  72. return
  73. }
  74. i.Milestone, err = GetMilestoneByID(i.MilestoneID)
  75. if err != nil {
  76. log.Error(3, "GetMilestoneById[%d]: %v", i.ID, err)
  77. }
  78. case "assignee_id":
  79. if i.AssigneeID == 0 {
  80. return
  81. }
  82. i.Assignee, err = GetUserByID(i.AssigneeID)
  83. if err != nil {
  84. log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
  85. }
  86. case "is_pull":
  87. if !i.IsPull {
  88. return
  89. }
  90. i.PullRequest, err = GetPullRequestByPullID(i.ID)
  91. if err != nil {
  92. log.Error(3, "GetPullRequestByPullID[%d]: %v", i.ID, err)
  93. }
  94. case "created":
  95. i.Created = regulateTimeZone(i.Created)
  96. }
  97. }
  98. // HashTag returns unique hash tag for issue.
  99. func (i *Issue) HashTag() string {
  100. return "issue-" + com.ToStr(i.ID)
  101. }
  102. // IsPoster returns true if given user by ID is the poster.
  103. func (i *Issue) IsPoster(uid int64) bool {
  104. return i.PosterID == uid
  105. }
  106. func (i *Issue) GetPoster() (err error) {
  107. i.Poster, err = GetUserByID(i.PosterID)
  108. if IsErrUserNotExist(err) {
  109. i.PosterID = -1
  110. i.Poster = NewFakeUser()
  111. return nil
  112. }
  113. return err
  114. }
  115. func (i *Issue) hasLabel(e Engine, labelID int64) bool {
  116. return hasIssueLabel(e, i.ID, labelID)
  117. }
  118. // HasLabel returns true if issue has been labeled by given ID.
  119. func (i *Issue) HasLabel(labelID int64) bool {
  120. return i.hasLabel(x, labelID)
  121. }
  122. func (i *Issue) addLabel(e *xorm.Session, label *Label) error {
  123. return newIssueLabel(e, i, label)
  124. }
  125. // AddLabel adds new label to issue by given ID.
  126. func (i *Issue) AddLabel(label *Label) (err error) {
  127. sess := x.NewSession()
  128. defer sessionRelease(sess)
  129. if err = sess.Begin(); err != nil {
  130. return err
  131. }
  132. if err = i.addLabel(sess, label); err != nil {
  133. return err
  134. }
  135. return sess.Commit()
  136. }
  137. func (i *Issue) getLabels(e Engine) (err error) {
  138. if len(i.Labels) > 0 {
  139. return nil
  140. }
  141. i.Labels, err = getLabelsByIssueID(e, i.ID)
  142. if err != nil {
  143. return fmt.Errorf("getLabelsByIssueID: %v", err)
  144. }
  145. return nil
  146. }
  147. // GetLabels retrieves all labels of issue and assign to corresponding field.
  148. func (i *Issue) GetLabels() error {
  149. return i.getLabels(x)
  150. }
  151. func (i *Issue) removeLabel(e *xorm.Session, label *Label) error {
  152. return deleteIssueLabel(e, i, label)
  153. }
  154. // RemoveLabel removes a label from issue by given ID.
  155. func (i *Issue) RemoveLabel(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.removeLabel(sess, label); err != nil {
  162. return err
  163. }
  164. return sess.Commit()
  165. }
  166. func (i *Issue) ClearLabels() (err error) {
  167. sess := x.NewSession()
  168. defer sessionRelease(sess)
  169. if err = sess.Begin(); err != nil {
  170. return err
  171. }
  172. if err = i.getLabels(sess); err != nil {
  173. return err
  174. }
  175. for idx := range i.Labels {
  176. if err = i.removeLabel(sess, i.Labels[idx]); err != nil {
  177. return err
  178. }
  179. }
  180. return sess.Commit()
  181. }
  182. func (i *Issue) GetAssignee() (err error) {
  183. if i.AssigneeID == 0 || i.Assignee != nil {
  184. return nil
  185. }
  186. i.Assignee, err = GetUserByID(i.AssigneeID)
  187. if IsErrUserNotExist(err) {
  188. return nil
  189. }
  190. return err
  191. }
  192. // ReadBy sets issue to be read by given user.
  193. func (i *Issue) ReadBy(uid int64) error {
  194. return UpdateIssueUserByRead(uid, i.ID)
  195. }
  196. func (i *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (err error) {
  197. if i.IsClosed == isClosed {
  198. return nil
  199. }
  200. i.IsClosed = isClosed
  201. if err = updateIssue(e, i); err != nil {
  202. return err
  203. } else if err = updateIssueUsersByStatus(e, i.ID, isClosed); err != nil {
  204. return err
  205. }
  206. // Update labels.
  207. if err = i.getLabels(e); err != nil {
  208. return err
  209. }
  210. for idx := range i.Labels {
  211. if i.IsClosed {
  212. i.Labels[idx].NumClosedIssues++
  213. } else {
  214. i.Labels[idx].NumClosedIssues--
  215. }
  216. if err = updateLabel(e, i.Labels[idx]); err != nil {
  217. return err
  218. }
  219. }
  220. // Update milestone.
  221. if err = changeMilestoneIssueStats(e, i); err != nil {
  222. return err
  223. }
  224. // New action comment.
  225. if _, err = createStatusComment(e, doer, i.Repo, i); err != nil {
  226. return err
  227. }
  228. return nil
  229. }
  230. // ChangeStatus changes issue status to open/closed.
  231. func (i *Issue) ChangeStatus(doer *User, isClosed bool) (err error) {
  232. sess := x.NewSession()
  233. defer sessionRelease(sess)
  234. if err = sess.Begin(); err != nil {
  235. return err
  236. }
  237. if err = i.changeStatus(sess, doer, isClosed); err != nil {
  238. return err
  239. }
  240. return sess.Commit()
  241. }
  242. // It's caller's responsibility to create action.
  243. func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string, isPull bool) (err error) {
  244. if _, err = e.Insert(issue); err != nil {
  245. return err
  246. }
  247. if isPull {
  248. _, err = e.Exec("UPDATE `repository` SET num_pulls=num_pulls+1 WHERE id=?", issue.RepoID)
  249. } else {
  250. _, err = e.Exec("UPDATE `repository` SET num_issues=num_issues+1 WHERE id=?", issue.RepoID)
  251. }
  252. if err != nil {
  253. return err
  254. }
  255. var label *Label
  256. for _, id := range labelIDs {
  257. if id == 0 {
  258. continue
  259. }
  260. label, err = getLabelByID(e, id)
  261. if err != nil {
  262. return err
  263. }
  264. if err = issue.addLabel(e, label); err != nil {
  265. return fmt.Errorf("addLabel: %v", err)
  266. }
  267. }
  268. if issue.MilestoneID > 0 {
  269. if err = changeMilestoneAssign(e, 0, issue); err != nil {
  270. return err
  271. }
  272. }
  273. if err = newIssueUsers(e, repo, issue); err != nil {
  274. return err
  275. }
  276. // Check attachments.
  277. attachments := make([]*Attachment, 0, len(uuids))
  278. for _, uuid := range uuids {
  279. attach, err := getAttachmentByUUID(e, uuid)
  280. if err != nil {
  281. if IsErrAttachmentNotExist(err) {
  282. continue
  283. }
  284. return fmt.Errorf("getAttachmentByUUID[%s]: %v", uuid, err)
  285. }
  286. attachments = append(attachments, attach)
  287. }
  288. for i := range attachments {
  289. attachments[i].IssueID = issue.ID
  290. // No assign value could be 0, so ignore AllCols().
  291. if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil {
  292. return fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
  293. }
  294. }
  295. return nil
  296. }
  297. // NewIssue creates new issue with labels for repository.
  298. func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
  299. sess := x.NewSession()
  300. defer sessionRelease(sess)
  301. if err = sess.Begin(); err != nil {
  302. return err
  303. }
  304. if err = newIssue(sess, repo, issue, labelIDs, uuids, false); err != nil {
  305. return fmt.Errorf("newIssue: %v", err)
  306. }
  307. // Notify watchers.
  308. act := &Action{
  309. ActUserID: issue.Poster.Id,
  310. ActUserName: issue.Poster.Name,
  311. ActEmail: issue.Poster.Email,
  312. OpType: CREATE_ISSUE,
  313. Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
  314. RepoID: repo.ID,
  315. RepoUserName: repo.Owner.Name,
  316. RepoName: repo.Name,
  317. IsPrivate: repo.IsPrivate,
  318. }
  319. if err = notifyWatchers(sess, act); err != nil {
  320. return err
  321. }
  322. return sess.Commit()
  323. }
  324. // GetIssueByRef returns an Issue specified by a GFM reference.
  325. // See https://help.github.com/articles/writing-on-github#references for more information on the syntax.
  326. func GetIssueByRef(ref string) (*Issue, error) {
  327. n := strings.IndexByte(ref, byte('#'))
  328. if n == -1 {
  329. return nil, ErrMissingIssueNumber
  330. }
  331. index, err := com.StrTo(ref[n+1:]).Int64()
  332. if err != nil {
  333. return nil, err
  334. }
  335. repo, err := GetRepositoryByRef(ref[:n])
  336. if err != nil {
  337. return nil, err
  338. }
  339. issue, err := GetIssueByIndex(repo.ID, index)
  340. if err != nil {
  341. return nil, err
  342. }
  343. issue.Repo = repo
  344. return issue, nil
  345. }
  346. // GetIssueByIndex returns issue by given index in repository.
  347. func GetIssueByIndex(repoID, index int64) (*Issue, error) {
  348. issue := &Issue{
  349. RepoID: repoID,
  350. Index: index,
  351. }
  352. has, err := x.Get(issue)
  353. if err != nil {
  354. return nil, err
  355. } else if !has {
  356. return nil, ErrIssueNotExist{0, repoID, index}
  357. }
  358. return issue, nil
  359. }
  360. // GetIssueByID returns an issue by given ID.
  361. func GetIssueByID(id int64) (*Issue, error) {
  362. issue := new(Issue)
  363. has, err := x.Id(id).Get(issue)
  364. if err != nil {
  365. return nil, err
  366. } else if !has {
  367. return nil, ErrIssueNotExist{id, 0, 0}
  368. }
  369. return issue, nil
  370. }
  371. type IssuesOptions struct {
  372. UserID int64
  373. AssigneeID int64
  374. RepoID int64
  375. PosterID int64
  376. MilestoneID int64
  377. RepoIDs []int64
  378. Page int
  379. IsClosed bool
  380. IsMention bool
  381. IsPull bool
  382. Labels string
  383. SortType string
  384. }
  385. // Issues returns a list of issues by given conditions.
  386. func Issues(opts *IssuesOptions) ([]*Issue, error) {
  387. sess := x.Limit(setting.IssuePagingNum, (opts.Page-1)*setting.IssuePagingNum)
  388. if opts.RepoID > 0 {
  389. sess.Where("issue.repo_id=?", opts.RepoID).And("issue.is_closed=?", opts.IsClosed)
  390. } else if opts.RepoIDs != nil {
  391. // In case repository IDs are provided but actually no repository has issue.
  392. if len(opts.RepoIDs) == 0 {
  393. return make([]*Issue, 0), nil
  394. }
  395. sess.Where("issue.repo_id IN ("+strings.Join(base.Int64sToStrings(opts.RepoIDs), ",")+")").And("issue.is_closed=?", opts.IsClosed)
  396. } else {
  397. sess.Where("issue.is_closed=?", opts.IsClosed)
  398. }
  399. if opts.AssigneeID > 0 {
  400. sess.And("issue.assignee_id=?", opts.AssigneeID)
  401. } else if opts.PosterID > 0 {
  402. sess.And("issue.poster_id=?", opts.PosterID)
  403. }
  404. if opts.MilestoneID > 0 {
  405. sess.And("issue.milestone_id=?", opts.MilestoneID)
  406. }
  407. sess.And("issue.is_pull=?", opts.IsPull)
  408. switch opts.SortType {
  409. case "oldest":
  410. sess.Asc("created")
  411. case "recentupdate":
  412. sess.Desc("updated")
  413. case "leastupdate":
  414. sess.Asc("updated")
  415. case "mostcomment":
  416. sess.Desc("num_comments")
  417. case "leastcomment":
  418. sess.Asc("num_comments")
  419. case "priority":
  420. sess.Desc("priority")
  421. default:
  422. sess.Desc("created")
  423. }
  424. labelIDs := base.StringsToInt64s(strings.Split(opts.Labels, ","))
  425. if len(labelIDs) > 0 {
  426. validJoin := false
  427. queryStr := "issue.id=issue_label.issue_id"
  428. for _, id := range labelIDs {
  429. if id == 0 {
  430. continue
  431. }
  432. validJoin = true
  433. queryStr += " AND issue_label.label_id=" + com.ToStr(id)
  434. }
  435. if validJoin {
  436. sess.Join("INNER", "issue_label", queryStr)
  437. }
  438. }
  439. if opts.IsMention {
  440. queryStr := "issue.id=issue_user.issue_id AND issue_user.is_mentioned=1"
  441. if opts.UserID > 0 {
  442. queryStr += " AND issue_user.uid=" + com.ToStr(opts.UserID)
  443. }
  444. sess.Join("INNER", "issue_user", queryStr)
  445. }
  446. issues := make([]*Issue, 0, setting.IssuePagingNum)
  447. return issues, sess.Find(&issues)
  448. }
  449. type IssueStatus int
  450. const (
  451. IS_OPEN = iota + 1
  452. IS_CLOSE
  453. )
  454. // GetIssueCountByPoster returns number of issues of repository by poster.
  455. func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
  456. count, _ := x.Where("repo_id=?", rid).And("poster_id=?", uid).And("is_closed=?", isClosed).Count(new(Issue))
  457. return count
  458. }
  459. // .___ ____ ___
  460. // | | ______ ________ __ ____ | | \______ ___________
  461. // | |/ ___// ___/ | \_/ __ \| | / ___// __ \_ __ \
  462. // | |\___ \ \___ \| | /\ ___/| | /\___ \\ ___/| | \/
  463. // |___/____ >____ >____/ \___ >______//____ >\___ >__|
  464. // \/ \/ \/ \/ \/
  465. // IssueUser represents an issue-user relation.
  466. type IssueUser struct {
  467. ID int64 `xorm:"pk autoincr"`
  468. UID int64 `xorm:"INDEX"` // User ID.
  469. IssueID int64
  470. RepoID int64 `xorm:"INDEX"`
  471. MilestoneID int64
  472. IsRead bool
  473. IsAssigned bool
  474. IsMentioned bool
  475. IsPoster bool
  476. IsClosed bool
  477. }
  478. func newIssueUsers(e *xorm.Session, repo *Repository, issue *Issue) error {
  479. users, err := repo.GetAssignees()
  480. if err != nil {
  481. return err
  482. }
  483. iu := &IssueUser{
  484. IssueID: issue.ID,
  485. RepoID: repo.ID,
  486. }
  487. // Poster can be anyone.
  488. isNeedAddPoster := true
  489. for _, u := range users {
  490. iu.ID = 0
  491. iu.UID = u.Id
  492. iu.IsPoster = iu.UID == issue.PosterID
  493. if isNeedAddPoster && iu.IsPoster {
  494. isNeedAddPoster = false
  495. }
  496. iu.IsAssigned = iu.UID == issue.AssigneeID
  497. if _, err = e.Insert(iu); err != nil {
  498. return err
  499. }
  500. }
  501. if isNeedAddPoster {
  502. iu.ID = 0
  503. iu.UID = issue.PosterID
  504. iu.IsPoster = true
  505. if _, err = e.Insert(iu); err != nil {
  506. return err
  507. }
  508. }
  509. return nil
  510. }
  511. // NewIssueUsers adds new issue-user relations for new issue of repository.
  512. func NewIssueUsers(repo *Repository, issue *Issue) (err error) {
  513. sess := x.NewSession()
  514. defer sessionRelease(sess)
  515. if err = sess.Begin(); err != nil {
  516. return err
  517. }
  518. if err = newIssueUsers(sess, repo, issue); err != nil {
  519. return err
  520. }
  521. return sess.Commit()
  522. }
  523. // PairsContains returns true when pairs list contains given issue.
  524. func PairsContains(ius []*IssueUser, issueId, uid int64) int {
  525. for i := range ius {
  526. if ius[i].IssueID == issueId &&
  527. ius[i].UID == uid {
  528. return i
  529. }
  530. }
  531. return -1
  532. }
  533. // GetIssueUsers returns issue-user pairs by given repository and user.
  534. func GetIssueUsers(rid, uid int64, isClosed bool) ([]*IssueUser, error) {
  535. ius := make([]*IssueUser, 0, 10)
  536. err := x.Where("is_closed=?", isClosed).Find(&ius, &IssueUser{RepoID: rid, UID: uid})
  537. return ius, err
  538. }
  539. // GetIssueUserPairsByRepoIds returns issue-user pairs by given repository IDs.
  540. func GetIssueUserPairsByRepoIds(rids []int64, isClosed bool, page int) ([]*IssueUser, error) {
  541. if len(rids) == 0 {
  542. return []*IssueUser{}, nil
  543. }
  544. buf := bytes.NewBufferString("")
  545. for _, rid := range rids {
  546. buf.WriteString("repo_id=")
  547. buf.WriteString(com.ToStr(rid))
  548. buf.WriteString(" OR ")
  549. }
  550. cond := strings.TrimSuffix(buf.String(), " OR ")
  551. ius := make([]*IssueUser, 0, 10)
  552. sess := x.Limit(20, (page-1)*20).Where("is_closed=?", isClosed)
  553. if len(cond) > 0 {
  554. sess.And(cond)
  555. }
  556. err := sess.Find(&ius)
  557. return ius, err
  558. }
  559. // GetIssueUserPairsByMode returns issue-user pairs by given repository and user.
  560. func GetIssueUserPairsByMode(uid, rid int64, isClosed bool, page, filterMode int) ([]*IssueUser, error) {
  561. ius := make([]*IssueUser, 0, 10)
  562. sess := x.Limit(20, (page-1)*20).Where("uid=?", uid).And("is_closed=?", isClosed)
  563. if rid > 0 {
  564. sess.And("repo_id=?", rid)
  565. }
  566. switch filterMode {
  567. case FM_ASSIGN:
  568. sess.And("is_assigned=?", true)
  569. case FM_CREATE:
  570. sess.And("is_poster=?", true)
  571. default:
  572. return ius, nil
  573. }
  574. err := sess.Find(&ius)
  575. return ius, err
  576. }
  577. // IssueStats represents issue statistic information.
  578. type IssueStats struct {
  579. OpenCount, ClosedCount int64
  580. AllCount int64
  581. AssignCount int64
  582. CreateCount int64
  583. MentionCount int64
  584. }
  585. // Filter modes.
  586. const (
  587. FM_ALL = iota
  588. FM_ASSIGN
  589. FM_CREATE
  590. FM_MENTION
  591. )
  592. func parseCountResult(results []map[string][]byte) int64 {
  593. if len(results) == 0 {
  594. return 0
  595. }
  596. for _, result := range results[0] {
  597. return com.StrTo(string(result)).MustInt64()
  598. }
  599. return 0
  600. }
  601. type IssueStatsOptions struct {
  602. RepoID int64
  603. UserID int64
  604. LabelID int64
  605. MilestoneID int64
  606. AssigneeID int64
  607. FilterMode int
  608. IsPull bool
  609. }
  610. // GetIssueStats returns issue statistic information by given conditions.
  611. func GetIssueStats(opts *IssueStatsOptions) *IssueStats {
  612. stats := &IssueStats{}
  613. queryStr := "SELECT COUNT(*) FROM `issue` "
  614. if opts.LabelID > 0 {
  615. queryStr += "INNER JOIN `issue_label` ON `issue`.id=`issue_label`.issue_id AND `issue_label`.label_id=" + com.ToStr(opts.LabelID)
  616. }
  617. baseCond := " WHERE issue.repo_id=" + com.ToStr(opts.RepoID) + " AND issue.is_closed=?"
  618. if opts.MilestoneID > 0 {
  619. baseCond += " AND issue.milestone_id=" + com.ToStr(opts.MilestoneID)
  620. }
  621. if opts.AssigneeID > 0 {
  622. baseCond += " AND assignee_id=" + com.ToStr(opts.AssigneeID)
  623. }
  624. if opts.IsPull {
  625. baseCond += " AND issue.is_pull=1"
  626. } else {
  627. baseCond += " AND issue.is_pull=0"
  628. }
  629. switch opts.FilterMode {
  630. case FM_ALL, FM_ASSIGN:
  631. results, _ := x.Query(queryStr+baseCond, false)
  632. stats.OpenCount = parseCountResult(results)
  633. results, _ = x.Query(queryStr+baseCond, true)
  634. stats.ClosedCount = parseCountResult(results)
  635. case FM_CREATE:
  636. baseCond += " AND poster_id=?"
  637. results, _ := x.Query(queryStr+baseCond, false, opts.UserID)
  638. stats.OpenCount = parseCountResult(results)
  639. results, _ = x.Query(queryStr+baseCond, true, opts.UserID)
  640. stats.ClosedCount = parseCountResult(results)
  641. case FM_MENTION:
  642. queryStr += " INNER JOIN `issue_user` ON `issue`.id=`issue_user`.issue_id"
  643. baseCond += " AND `issue_user`.uid=? AND `issue_user`.is_mentioned=?"
  644. results, _ := x.Query(queryStr+baseCond, false, opts.UserID, true)
  645. stats.OpenCount = parseCountResult(results)
  646. results, _ = x.Query(queryStr+baseCond, true, opts.UserID, true)
  647. stats.ClosedCount = parseCountResult(results)
  648. }
  649. return stats
  650. }
  651. // GetUserIssueStats returns issue statistic information for dashboard by given conditions.
  652. func GetUserIssueStats(repoID, uid int64, repoIDs []int64, filterMode int, isPull bool) *IssueStats {
  653. stats := &IssueStats{}
  654. queryStr := "SELECT COUNT(*) FROM `issue` "
  655. baseCond := " WHERE issue.is_closed=?"
  656. if repoID > 0 || len(repoIDs) == 0 {
  657. baseCond += " AND issue.repo_id=" + com.ToStr(repoID)
  658. } else {
  659. baseCond += " AND issue.repo_id IN (" + strings.Join(base.Int64sToStrings(repoIDs), ",") + ")"
  660. }
  661. if isPull {
  662. baseCond += " AND issue.is_pull=1"
  663. } else {
  664. baseCond += " AND issue.is_pull=0"
  665. }
  666. results, _ := x.Query(queryStr+baseCond+" AND assignee_id=?", false, uid)
  667. stats.AssignCount = parseCountResult(results)
  668. results, _ = x.Query(queryStr+baseCond+" AND poster_id=?", false, uid)
  669. stats.CreateCount = parseCountResult(results)
  670. switch filterMode {
  671. case FM_ASSIGN:
  672. baseCond += " AND assignee_id=" + com.ToStr(uid)
  673. case FM_CREATE:
  674. baseCond += " AND poster_id=" + com.ToStr(uid)
  675. }
  676. results, _ = x.Query(queryStr+baseCond, false)
  677. stats.OpenCount = parseCountResult(results)
  678. results, _ = x.Query(queryStr+baseCond, true)
  679. stats.ClosedCount = parseCountResult(results)
  680. return stats
  681. }
  682. // GetRepoIssueStats returns number of open and closed repository issues by given filter mode.
  683. func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen int64, numClosed int64) {
  684. queryStr := "SELECT COUNT(*) FROM `issue` "
  685. baseCond := " WHERE issue.repo_id=? AND issue.is_closed=?"
  686. if isPull {
  687. baseCond += " AND issue.is_pull=1"
  688. } else {
  689. baseCond += " AND issue.is_pull=0"
  690. }
  691. switch filterMode {
  692. case FM_ASSIGN:
  693. baseCond += " AND assignee_id=" + com.ToStr(uid)
  694. case FM_CREATE:
  695. baseCond += " AND poster_id=" + com.ToStr(uid)
  696. }
  697. results, _ := x.Query(queryStr+baseCond, repoID, false)
  698. numOpen = parseCountResult(results)
  699. results, _ = x.Query(queryStr+baseCond, repoID, true)
  700. numClosed = parseCountResult(results)
  701. return numOpen, numClosed
  702. }
  703. func updateIssue(e Engine, issue *Issue) error {
  704. _, err := e.Id(issue.ID).AllCols().Update(issue)
  705. return err
  706. }
  707. // UpdateIssue updates information of issue.
  708. func UpdateIssue(issue *Issue) error {
  709. return updateIssue(x, issue)
  710. }
  711. func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error {
  712. _, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID)
  713. return err
  714. }
  715. // UpdateIssueUsersByStatus updates issue-user relations by issue status.
  716. func UpdateIssueUsersByStatus(issueID int64, isClosed bool) error {
  717. return updateIssueUsersByStatus(x, issueID, isClosed)
  718. }
  719. func updateIssueUserByAssignee(e *xorm.Session, issue *Issue) (err error) {
  720. if _, err = e.Exec("UPDATE `issue_user` SET is_assigned=? WHERE issue_id=?", false, issue.ID); err != nil {
  721. return err
  722. }
  723. // Assignee ID equals to 0 means clear assignee.
  724. if issue.AssigneeID > 0 {
  725. if _, err = e.Exec("UPDATE `issue_user` SET is_assigned=? WHERE uid=? AND issue_id=?", true, issue.AssigneeID, issue.ID); err != nil {
  726. return err
  727. }
  728. }
  729. return updateIssue(e, issue)
  730. }
  731. // UpdateIssueUserByAssignee updates issue-user relation for assignee.
  732. func UpdateIssueUserByAssignee(issue *Issue) (err error) {
  733. sess := x.NewSession()
  734. defer sessionRelease(sess)
  735. if err = sess.Begin(); err != nil {
  736. return err
  737. }
  738. if err = updateIssueUserByAssignee(sess, issue); err != nil {
  739. return err
  740. }
  741. return sess.Commit()
  742. }
  743. // UpdateIssueUserByRead updates issue-user relation for reading.
  744. func UpdateIssueUserByRead(uid, issueID int64) error {
  745. _, err := x.Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID)
  746. return err
  747. }
  748. // UpdateIssueUsersByMentions updates issue-user pairs by mentioning.
  749. func UpdateIssueUsersByMentions(uids []int64, iid int64) error {
  750. for _, uid := range uids {
  751. iu := &IssueUser{UID: uid, IssueID: iid}
  752. has, err := x.Get(iu)
  753. if err != nil {
  754. return err
  755. }
  756. iu.IsMentioned = true
  757. if has {
  758. _, err = x.Id(iu.ID).AllCols().Update(iu)
  759. } else {
  760. _, err = x.Insert(iu)
  761. }
  762. if err != nil {
  763. return err
  764. }
  765. }
  766. return nil
  767. }
  768. // __________ .__ .__ __________ __
  769. // \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
  770. // | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
  771. // | | | | / |_| |_| | \ ___< <_| | | /\ ___/ \___ \ | |
  772. // |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__|
  773. // \/ \/ |__| \/ \/
  774. type PullRequestType int
  775. const (
  776. PULL_REQUEST_GOGS PullRequestType = iota
  777. PLLL_ERQUEST_GIT
  778. )
  779. type PullRequestStatus int
  780. const (
  781. PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota
  782. PULL_REQUEST_STATUS_CHECKING
  783. PULL_REQUEST_STATUS_MERGEABLE
  784. )
  785. // PullRequest represents relation between pull request and repositories.
  786. type PullRequest struct {
  787. ID int64 `xorm:"pk autoincr"`
  788. PullID int64 `xorm:"INDEX"`
  789. Pull *Issue `xorm:"-"`
  790. PullIndex int64
  791. HeadRepoID int64
  792. HeadRepo *Repository `xorm:"-"`
  793. BaseRepoID int64
  794. HeadUserName string
  795. HeadBarcnh string
  796. BaseBranch string
  797. MergeBase string `xorm:"VARCHAR(40)"`
  798. MergedCommitID string `xorm:"VARCHAR(40)"`
  799. Type PullRequestType
  800. Status PullRequestStatus
  801. HasMerged bool
  802. Merged time.Time
  803. MergerID int64
  804. Merger *User `xorm:"-"`
  805. }
  806. // Note: don't try to get Pull because will end up recursive querying.
  807. func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
  808. var err error
  809. switch colName {
  810. case "head_repo_id":
  811. // FIXME: shouldn't show error if it's known that head repository has been removed.
  812. pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID)
  813. if err != nil {
  814. log.Error(3, "GetRepositoryByID[%d]: %v", pr.ID, err)
  815. }
  816. case "merger_id":
  817. if !pr.HasMerged {
  818. return
  819. }
  820. pr.Merger, err = GetUserByID(pr.MergerID)
  821. if err != nil {
  822. if IsErrUserNotExist(err) {
  823. pr.MergerID = -1
  824. pr.Merger = NewFakeUser()
  825. } else {
  826. log.Error(3, "GetUserByID[%d]: %v", pr.ID, err)
  827. }
  828. }
  829. case "merged":
  830. if !pr.HasMerged {
  831. return
  832. }
  833. pr.Merged = regulateTimeZone(pr.Merged)
  834. }
  835. }
  836. func (pr *PullRequest) CanAutoMerge() bool {
  837. return pr.Status == PULL_REQUEST_STATUS_MERGEABLE
  838. }
  839. // Merge merges pull request to base repository.
  840. func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
  841. sess := x.NewSession()
  842. defer sessionRelease(sess)
  843. if err = sess.Begin(); err != nil {
  844. return err
  845. }
  846. if err = pr.Pull.changeStatus(sess, doer, true); err != nil {
  847. return fmt.Errorf("Pull.changeStatus: %v", err)
  848. }
  849. headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
  850. headGitRepo, err := git.OpenRepository(headRepoPath)
  851. if err != nil {
  852. return fmt.Errorf("OpenRepository: %v", err)
  853. }
  854. pr.MergedCommitID, err = headGitRepo.GetCommitIdOfBranch(pr.HeadBarcnh)
  855. if err != nil {
  856. return fmt.Errorf("GetCommitIdOfBranch: %v", err)
  857. }
  858. if err = mergePullRequestAction(sess, doer, pr.Pull.Repo, pr.Pull); err != nil {
  859. return fmt.Errorf("mergePullRequestAction: %v", err)
  860. }
  861. pr.HasMerged = true
  862. pr.Merged = time.Now()
  863. pr.MergerID = doer.Id
  864. if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
  865. return fmt.Errorf("update pull request: %v", err)
  866. }
  867. // Clone base repo.
  868. tmpBasePath := path.Join("data/tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
  869. os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm)
  870. defer os.RemoveAll(path.Dir(tmpBasePath))
  871. var stderr string
  872. if _, stderr, err = process.ExecTimeout(5*time.Minute,
  873. fmt.Sprintf("PullRequest.Merge(git clone): %s", tmpBasePath),
  874. "git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
  875. return fmt.Errorf("git clone: %s", stderr)
  876. }
  877. // Check out base branch.
  878. if _, stderr, err = process.ExecDir(-1, tmpBasePath,
  879. fmt.Sprintf("PullRequest.Merge(git checkout): %s", tmpBasePath),
  880. "git", "checkout", pr.BaseBranch); err != nil {
  881. return fmt.Errorf("git checkout: %s", stderr)
  882. }
  883. // Pull commits.
  884. if _, stderr, err = process.ExecDir(-1, tmpBasePath,
  885. fmt.Sprintf("PullRequest.Merge(git pull): %s", tmpBasePath),
  886. "git", "pull", headRepoPath, pr.HeadBarcnh); err != nil {
  887. return fmt.Errorf("git pull[%s / %s -> %s]: %s", headRepoPath, pr.HeadBarcnh, tmpBasePath, stderr)
  888. }
  889. // Push back to upstream.
  890. if _, stderr, err = process.ExecDir(-1, tmpBasePath,
  891. fmt.Sprintf("PullRequest.Merge(git push): %s", tmpBasePath),
  892. "git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
  893. return fmt.Errorf("git push: %s", stderr)
  894. }
  895. return sess.Commit()
  896. }
  897. // NewPullRequest creates new pull request with labels for repository.
  898. func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
  899. sess := x.NewSession()
  900. defer sessionRelease(sess)
  901. if err = sess.Begin(); err != nil {
  902. return err
  903. }
  904. if err = newIssue(sess, repo, pull, labelIDs, uuids, true); err != nil {
  905. return fmt.Errorf("newIssue: %v", err)
  906. }
  907. // Notify watchers.
  908. act := &Action{
  909. ActUserID: pull.Poster.Id,
  910. ActUserName: pull.Poster.Name,
  911. ActEmail: pull.Poster.Email,
  912. OpType: CREATE_PULL_REQUEST,
  913. Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
  914. RepoID: repo.ID,
  915. RepoUserName: repo.Owner.Name,
  916. RepoName: repo.Name,
  917. IsPrivate: repo.IsPrivate,
  918. }
  919. if err = notifyWatchers(sess, act); err != nil {
  920. return err
  921. }
  922. // Test apply patch.
  923. if err = repo.UpdateLocalCopy(); err != nil {
  924. return fmt.Errorf("UpdateLocalCopy: %v", err)
  925. }
  926. repoPath, err := repo.RepoPath()
  927. if err != nil {
  928. return fmt.Errorf("RepoPath: %v", err)
  929. }
  930. patchPath := path.Join(repoPath, "pulls", com.ToStr(pull.ID)+".patch")
  931. os.MkdirAll(path.Dir(patchPath), os.ModePerm)
  932. if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
  933. return fmt.Errorf("save patch: %v", err)
  934. }
  935. pr.Status = PULL_REQUEST_STATUS_MERGEABLE
  936. _, stderr, err := process.ExecDir(-1, repo.LocalCopyPath(),
  937. fmt.Sprintf("NewPullRequest(git apply --check): %d", repo.ID),
  938. "git", "apply", "--check", patchPath)
  939. if err != nil {
  940. if strings.Contains(stderr, "patch does not apply") {
  941. pr.Status = PULL_REQUEST_STATUS_CONFLICT
  942. } else {
  943. return fmt.Errorf("git apply --check: %v - %s", err, stderr)
  944. }
  945. }
  946. pr.PullID = pull.ID
  947. pr.PullIndex = pull.Index
  948. if _, err = sess.Insert(pr); err != nil {
  949. return fmt.Errorf("insert pull repo: %v", err)
  950. }
  951. return sess.Commit()
  952. }
  953. // GetUnmergedPullRequest returnss a pull request hasn't been merged by given info.
  954. func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
  955. pr := &PullRequest{
  956. HeadRepoID: headRepoID,
  957. BaseRepoID: baseRepoID,
  958. HeadBarcnh: headBranch,
  959. BaseBranch: baseBranch,
  960. }
  961. has, err := x.Where("has_merged=?", false).Get(pr)
  962. if err != nil {
  963. return nil, err
  964. } else if !has {
  965. return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
  966. }
  967. return pr, nil
  968. }
  969. // GetPullRequestByPullID returns pull repo by given pull ID.
  970. func GetPullRequestByPullID(pullID int64) (*PullRequest, error) {
  971. pr := new(PullRequest)
  972. has, err := x.Where("pull_id=?", pullID).Get(pr)
  973. if err != nil {
  974. return nil, err
  975. } else if !has {
  976. return nil, ErrPullRequestNotExist{0, pullID, 0, 0, "", ""}
  977. }
  978. return pr, nil
  979. }
  980. // .____ ___. .__
  981. // | | _____ \_ |__ ____ | |
  982. // | | \__ \ | __ \_/ __ \| |
  983. // | |___ / __ \| \_\ \ ___/| |__
  984. // |_______ (____ /___ /\___ >____/
  985. // \/ \/ \/ \/
  986. // Label represents a label of repository for issues.
  987. type Label struct {
  988. ID int64 `xorm:"pk autoincr"`
  989. RepoID int64 `xorm:"INDEX"`
  990. Name string
  991. Color string `xorm:"VARCHAR(7)"`
  992. NumIssues int
  993. NumClosedIssues int
  994. NumOpenIssues int `xorm:"-"`
  995. IsChecked bool `xorm:"-"`
  996. }
  997. // CalOpenIssues calculates the open issues of label.
  998. func (m *Label) CalOpenIssues() {
  999. m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
  1000. }
  1001. // NewLabel creates new label of repository.
  1002. func NewLabel(l *Label) error {
  1003. _, err := x.Insert(l)
  1004. return err
  1005. }
  1006. func getLabelByID(e Engine, id int64) (*Label, error) {
  1007. if id <= 0 {
  1008. return nil, ErrLabelNotExist{id}
  1009. }
  1010. l := &Label{ID: id}
  1011. has, err := x.Get(l)
  1012. if err != nil {
  1013. return nil, err
  1014. } else if !has {
  1015. return nil, ErrLabelNotExist{l.ID}
  1016. }
  1017. return l, nil
  1018. }
  1019. // GetLabelByID returns a label by given ID.
  1020. func GetLabelByID(id int64) (*Label, error) {
  1021. return getLabelByID(x, id)
  1022. }
  1023. // GetLabelsByRepoID returns all labels that belong to given repository by ID.
  1024. func GetLabelsByRepoID(repoID int64) ([]*Label, error) {
  1025. labels := make([]*Label, 0, 10)
  1026. return labels, x.Where("repo_id=?", repoID).Find(&labels)
  1027. }
  1028. func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) {
  1029. issueLabels, err := getIssueLabels(e, issueID)
  1030. if err != nil {
  1031. return nil, fmt.Errorf("getIssueLabels: %v", err)
  1032. }
  1033. var label *Label
  1034. labels := make([]*Label, 0, len(issueLabels))
  1035. for idx := range issueLabels {
  1036. label, err = getLabelByID(e, issueLabels[idx].LabelID)
  1037. if err != nil && !IsErrLabelNotExist(err) {
  1038. return nil, fmt.Errorf("getLabelByID: %v", err)
  1039. }
  1040. labels = append(labels, label)
  1041. }
  1042. return labels, nil
  1043. }
  1044. // GetLabelsByIssueID returns all labels that belong to given issue by ID.
  1045. func GetLabelsByIssueID(issueID int64) ([]*Label, error) {
  1046. return getLabelsByIssueID(x, issueID)
  1047. }
  1048. func updateLabel(e Engine, l *Label) error {
  1049. _, err := e.Id(l.ID).AllCols().Update(l)
  1050. return err
  1051. }
  1052. // UpdateLabel updates label information.
  1053. func UpdateLabel(l *Label) error {
  1054. return updateLabel(x, l)
  1055. }
  1056. // DeleteLabel delete a label of given repository.
  1057. func DeleteLabel(repoID, labelID int64) error {
  1058. l, err := GetLabelByID(labelID)
  1059. if err != nil {
  1060. if IsErrLabelNotExist(err) {
  1061. return nil
  1062. }
  1063. return err
  1064. }
  1065. sess := x.NewSession()
  1066. defer sessionRelease(sess)
  1067. if err = sess.Begin(); err != nil {
  1068. return err
  1069. }
  1070. if _, err = x.Where("label_id=?", labelID).Delete(new(IssueLabel)); err != nil {
  1071. return err
  1072. } else if _, err = sess.Delete(l); err != nil {
  1073. return err
  1074. }
  1075. return sess.Commit()
  1076. }
  1077. // .___ .____ ___. .__
  1078. // | | ______ ________ __ ____ | | _____ \_ |__ ____ | |
  1079. // | |/ ___// ___/ | \_/ __ \| | \__ \ | __ \_/ __ \| |
  1080. // | |\___ \ \___ \| | /\ ___/| |___ / __ \| \_\ \ ___/| |__
  1081. // |___/____ >____ >____/ \___ >_______ (____ /___ /\___ >____/
  1082. // \/ \/ \/ \/ \/ \/ \/
  1083. // IssueLabel represetns an issue-lable relation.
  1084. type IssueLabel struct {
  1085. ID int64 `xorm:"pk autoincr"`
  1086. IssueID int64 `xorm:"UNIQUE(s)"`
  1087. LabelID int64 `xorm:"UNIQUE(s)"`
  1088. }
  1089. func hasIssueLabel(e Engine, issueID, labelID int64) bool {
  1090. has, _ := e.Where("issue_id=? AND label_id=?", issueID, labelID).Get(new(IssueLabel))
  1091. return has
  1092. }
  1093. // HasIssueLabel returns true if issue has been labeled.
  1094. func HasIssueLabel(issueID, labelID int64) bool {
  1095. return hasIssueLabel(x, issueID, labelID)
  1096. }
  1097. func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
  1098. if _, err = e.Insert(&IssueLabel{
  1099. IssueID: issue.ID,
  1100. LabelID: label.ID,
  1101. }); err != nil {
  1102. return err
  1103. }
  1104. label.NumIssues++
  1105. if issue.IsClosed {
  1106. label.NumClosedIssues++
  1107. }
  1108. return updateLabel(e, label)
  1109. }
  1110. // NewIssueLabel creates a new issue-label relation.
  1111. func NewIssueLabel(issue *Issue, label *Label) (err error) {
  1112. sess := x.NewSession()
  1113. defer sessionRelease(sess)
  1114. if err = sess.Begin(); err != nil {
  1115. return err
  1116. }
  1117. if err = newIssueLabel(sess, issue, label); err != nil {
  1118. return err
  1119. }
  1120. return sess.Commit()
  1121. }
  1122. func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) {
  1123. issueLabels := make([]*IssueLabel, 0, 10)
  1124. return issueLabels, e.Where("issue_id=?", issueID).Asc("label_id").Find(&issueLabels)
  1125. }
  1126. // GetIssueLabels returns all issue-label relations of given issue by ID.
  1127. func GetIssueLabels(issueID int64) ([]*IssueLabel, error) {
  1128. return getIssueLabels(x, issueID)
  1129. }
  1130. func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
  1131. if _, err = e.Delete(&IssueLabel{
  1132. IssueID: issue.ID,
  1133. LabelID: label.ID,
  1134. }); err != nil {
  1135. return err
  1136. }
  1137. label.NumIssues--
  1138. if issue.IsClosed {
  1139. label.NumClosedIssues--
  1140. }
  1141. return updateLabel(e, label)
  1142. }
  1143. // DeleteIssueLabel deletes issue-label relation.
  1144. func DeleteIssueLabel(issue *Issue, label *Label) (err error) {
  1145. sess := x.NewSession()
  1146. defer sessionRelease(sess)
  1147. if err = sess.Begin(); err != nil {
  1148. return err
  1149. }
  1150. if err = deleteIssueLabel(sess, issue, label); err != nil {
  1151. return err
  1152. }
  1153. return sess.Commit()
  1154. }
  1155. // _____ .__.__ __
  1156. // / \ |__| | ____ _______/ |_ ____ ____ ____
  1157. // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
  1158. // / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
  1159. // \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
  1160. // \/ \/ \/ \/ \/
  1161. // Milestone represents a milestone of repository.
  1162. type Milestone struct {
  1163. ID int64 `xorm:"pk autoincr"`
  1164. RepoID int64 `xorm:"INDEX"`
  1165. Name string
  1166. Content string `xorm:"TEXT"`
  1167. RenderedContent string `xorm:"-"`
  1168. IsClosed bool
  1169. NumIssues int
  1170. NumClosedIssues int
  1171. NumOpenIssues int `xorm:"-"`
  1172. Completeness int // Percentage(1-100).
  1173. Deadline time.Time
  1174. DeadlineString string `xorm:"-"`
  1175. IsOverDue bool `xorm:"-"`
  1176. ClosedDate time.Time
  1177. }
  1178. func (m *Milestone) BeforeUpdate() {
  1179. if m.NumIssues > 0 {
  1180. m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
  1181. } else {
  1182. m.Completeness = 0
  1183. }
  1184. }
  1185. func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
  1186. if colName == "deadline" {
  1187. if m.Deadline.Year() == 9999 {
  1188. return
  1189. }
  1190. m.DeadlineString = m.Deadline.Format("2006-01-02")
  1191. if time.Now().After(m.Deadline) {
  1192. m.IsOverDue = true
  1193. }
  1194. }
  1195. }
  1196. // CalOpenIssues calculates the open issues of milestone.
  1197. func (m *Milestone) CalOpenIssues() {
  1198. m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
  1199. }
  1200. // NewMilestone creates new milestone of repository.
  1201. func NewMilestone(m *Milestone) (err error) {
  1202. sess := x.NewSession()
  1203. defer sessionRelease(sess)
  1204. if err = sess.Begin(); err != nil {
  1205. return err
  1206. }
  1207. if _, err = sess.Insert(m); err != nil {
  1208. return err
  1209. }
  1210. if _, err = sess.Exec("UPDATE `repository` SET num_milestones=num_milestones+1 WHERE id=?", m.RepoID); err != nil {
  1211. return err
  1212. }
  1213. return sess.Commit()
  1214. }
  1215. func getMilestoneByID(e Engine, id int64) (*Milestone, error) {
  1216. m := &Milestone{ID: id}
  1217. has, err := e.Get(m)
  1218. if err != nil {
  1219. return nil, err
  1220. } else if !has {
  1221. return nil, ErrMilestoneNotExist{id, 0}
  1222. }
  1223. return m, nil
  1224. }
  1225. // GetMilestoneByID returns the milestone of given ID.
  1226. func GetMilestoneByID(id int64) (*Milestone, error) {
  1227. return getMilestoneByID(x, id)
  1228. }
  1229. // GetRepoMilestoneByID returns the milestone of given ID and repository.
  1230. func GetRepoMilestoneByID(repoID, milestoneID int64) (*Milestone, error) {
  1231. m := &Milestone{ID: milestoneID, RepoID: repoID}
  1232. has, err := x.Get(m)
  1233. if err != nil {
  1234. return nil, err
  1235. } else if !has {
  1236. return nil, ErrMilestoneNotExist{milestoneID, repoID}
  1237. }
  1238. return m, nil
  1239. }
  1240. // GetAllRepoMilestones returns all milestones of given repository.
  1241. func GetAllRepoMilestones(repoID int64) ([]*Milestone, error) {
  1242. miles := make([]*Milestone, 0, 10)
  1243. return miles, x.Where("repo_id=?", repoID).Find(&miles)
  1244. }
  1245. // GetMilestones returns a list of milestones of given repository and status.
  1246. func GetMilestones(repoID int64, page int, isClosed bool) ([]*Milestone, error) {
  1247. miles := make([]*Milestone, 0, setting.IssuePagingNum)
  1248. sess := x.Where("repo_id=? AND is_closed=?", repoID, isClosed)
  1249. if page > 0 {
  1250. sess = sess.Limit(setting.IssuePagingNum, (page-1)*setting.IssuePagingNum)
  1251. }
  1252. return miles, sess.Find(&miles)
  1253. }
  1254. func updateMilestone(e Engine, m *Milestone) error {
  1255. _, err := e.Id(m.ID).AllCols().Update(m)
  1256. return err
  1257. }
  1258. // UpdateMilestone updates information of given milestone.
  1259. func UpdateMilestone(m *Milestone) error {
  1260. return updateMilestone(x, m)
  1261. }
  1262. func countRepoMilestones(e Engine, repoID int64) int64 {
  1263. count, _ := e.Where("repo_id=?", repoID).Count(new(Milestone))
  1264. return count
  1265. }
  1266. // CountRepoMilestones returns number of milestones in given repository.
  1267. func CountRepoMilestones(repoID int64) int64 {
  1268. return countRepoMilestones(x, repoID)
  1269. }
  1270. func countRepoClosedMilestones(e Engine, repoID int64) int64 {
  1271. closed, _ := e.Where("repo_id=? AND is_closed=?", repoID, true).Count(new(Milestone))
  1272. return closed
  1273. }
  1274. // CountRepoClosedMilestones returns number of closed milestones in given repository.
  1275. func CountRepoClosedMilestones(repoID int64) int64 {
  1276. return countRepoClosedMilestones(x, repoID)
  1277. }
  1278. // MilestoneStats returns number of open and closed milestones of given repository.
  1279. func MilestoneStats(repoID int64) (open int64, closed int64) {
  1280. open, _ = x.Where("repo_id=? AND is_closed=?", repoID, false).Count(new(Milestone))
  1281. return open, CountRepoClosedMilestones(repoID)
  1282. }
  1283. // ChangeMilestoneStatus changes the milestone open/closed status.
  1284. func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
  1285. repo, err := GetRepositoryByID(m.RepoID)
  1286. if err != nil {
  1287. return err
  1288. }
  1289. sess := x.NewSession()
  1290. defer sessionRelease(sess)
  1291. if err = sess.Begin(); err != nil {
  1292. return err
  1293. }
  1294. m.IsClosed = isClosed
  1295. if err = updateMilestone(sess, m); err != nil {
  1296. return err
  1297. }
  1298. repo.NumMilestones = int(countRepoMilestones(sess, repo.ID))
  1299. repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID))
  1300. if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil {
  1301. return err
  1302. }
  1303. return sess.Commit()
  1304. }
  1305. func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error {
  1306. if issue.MilestoneID == 0 {
  1307. return nil
  1308. }
  1309. m, err := getMilestoneByID(e, issue.MilestoneID)
  1310. if err != nil {
  1311. return err
  1312. }
  1313. if issue.IsClosed {
  1314. m.NumOpenIssues--
  1315. m.NumClosedIssues++
  1316. } else {
  1317. m.NumOpenIssues++
  1318. m.NumClosedIssues--
  1319. }
  1320. return updateMilestone(e, m)
  1321. }
  1322. // ChangeMilestoneIssueStats updates the open/closed issues counter and progress
  1323. // for the milestone associated witht the given issue.
  1324. func ChangeMilestoneIssueStats(issue *Issue) (err error) {
  1325. sess := x.NewSession()
  1326. defer sessionRelease(sess)
  1327. if err = sess.Begin(); err != nil {
  1328. return err
  1329. }
  1330. if err = changeMilestoneIssueStats(sess, issue); err != nil {
  1331. return err
  1332. }
  1333. return sess.Commit()
  1334. }
  1335. func changeMilestoneAssign(e *xorm.Session, oldMid int64, issue *Issue) error {
  1336. if oldMid > 0 {
  1337. m, err := getMilestoneByID(e, oldMid)
  1338. if err != nil {
  1339. return err
  1340. }
  1341. m.NumIssues--
  1342. if issue.IsClosed {
  1343. m.NumClosedIssues--
  1344. }
  1345. if err = updateMilestone(e, m); err != nil {
  1346. return err
  1347. } else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id=0 WHERE issue_id=?", issue.ID); err != nil {
  1348. return err
  1349. }
  1350. }
  1351. if issue.MilestoneID > 0 {
  1352. m, err := getMilestoneByID(e, issue.MilestoneID)
  1353. if err != nil {
  1354. return err
  1355. }
  1356. m.NumIssues++
  1357. if issue.IsClosed {
  1358. m.NumClosedIssues++
  1359. }
  1360. if m.NumIssues == 0 {
  1361. return ErrWrongIssueCounter
  1362. }
  1363. if err = updateMilestone(e, m); err != nil {
  1364. return err
  1365. } else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id=? WHERE issue_id=?", m.ID, issue.ID); err != nil {
  1366. return err
  1367. }
  1368. }
  1369. return updateIssue(e, issue)
  1370. }
  1371. // ChangeMilestoneAssign changes assignment of milestone for issue.
  1372. func ChangeMilestoneAssign(oldMid int64, issue *Issue) (err error) {
  1373. sess := x.NewSession()
  1374. defer sess.Close()
  1375. if err = sess.Begin(); err != nil {
  1376. return err
  1377. }
  1378. if err = changeMilestoneAssign(sess, oldMid, issue); err != nil {
  1379. return err
  1380. }
  1381. return sess.Commit()
  1382. }
  1383. // DeleteMilestoneByID deletes a milestone by given ID.
  1384. func DeleteMilestoneByID(mid int64) error {
  1385. m, err := GetMilestoneByID(mid)
  1386. if err != nil {
  1387. if IsErrMilestoneNotExist(err) {
  1388. return nil
  1389. }
  1390. return err
  1391. }
  1392. repo, err := GetRepositoryByID(m.RepoID)
  1393. if err != nil {
  1394. return err
  1395. }
  1396. sess := x.NewSession()
  1397. defer sessionRelease(sess)
  1398. if err = sess.Begin(); err != nil {
  1399. return err
  1400. }
  1401. if _, err = sess.Id(m.ID).Delete(new(Milestone)); err != nil {
  1402. return err
  1403. }
  1404. repo.NumMilestones = int(countRepoMilestones(sess, repo.ID))
  1405. repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID))
  1406. if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil {
  1407. return err
  1408. }
  1409. if _, err = sess.Exec("UPDATE `issue` SET milestone_id=0 WHERE milestone_id=?", m.ID); err != nil {
  1410. return err
  1411. } else if _, err = sess.Exec("UPDATE `issue_user` SET milestone_id=0 WHERE milestone_id=?", m.ID); err != nil {
  1412. return err
  1413. }
  1414. return sess.Commit()
  1415. }
  1416. // _________ __
  1417. // \_ ___ \ ____ _____ _____ ____ _____/ |_
  1418. // / \ \/ / _ \ / \ / \_/ __ \ / \ __\
  1419. // \ \___( <_> ) Y Y \ Y Y \ ___/| | \ |
  1420. // \______ /\____/|__|_| /__|_| /\___ >___| /__|
  1421. // \/ \/ \/ \/ \/
  1422. // CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
  1423. type CommentType int
  1424. const (
  1425. // Plain comment, can be associated with a commit (CommitId > 0) and a line (Line > 0)
  1426. COMMENT_TYPE_COMMENT CommentType = iota
  1427. COMMENT_TYPE_REOPEN
  1428. COMMENT_TYPE_CLOSE
  1429. // References.
  1430. COMMENT_TYPE_ISSUE_REF
  1431. // Reference from a commit (not part of a pull request)
  1432. COMMENT_TYPE_COMMIT_REF
  1433. // Reference from a comment
  1434. COMMENT_TYPE_COMMENT_REF
  1435. // Reference from a pull request
  1436. COMMENT_TYPE_PULL_REF
  1437. )
  1438. type CommentTag int
  1439. const (
  1440. COMMENT_TAG_NONE CommentTag = iota
  1441. COMMENT_TAG_POSTER
  1442. COMMENT_TAG_ADMIN
  1443. COMMENT_TAG_OWNER
  1444. )
  1445. // Comment represents a comment in commit and issue page.
  1446. type Comment struct {
  1447. ID int64 `xorm:"pk autoincr"`
  1448. Type CommentType
  1449. PosterID int64
  1450. Poster *User `xorm:"-"`
  1451. IssueID int64 `xorm:"INDEX"`
  1452. CommitID int64
  1453. Line int64
  1454. Content string `xorm:"TEXT"`
  1455. RenderedContent string `xorm:"-"`
  1456. Created time.Time `xorm:"CREATED"`
  1457. // Reference issue in commit message
  1458. CommitSHA string `xorm:"VARCHAR(40)"`
  1459. Attachments []*Attachment `xorm:"-"`
  1460. // For view issue page.
  1461. ShowTag CommentTag `xorm:"-"`
  1462. }
  1463. func (c *Comment) AfterSet(colName string, _ xorm.Cell) {
  1464. var err error
  1465. switch colName {
  1466. case "id":
  1467. c.Attachments, err = GetAttachmentsByCommentID(c.ID)
  1468. if err != nil {
  1469. log.Error(3, "GetAttachmentsByCommentID[%d]: %v", c.ID, err)
  1470. }
  1471. case "poster_id":
  1472. c.Poster, err = GetUserByID(c.PosterID)
  1473. if err != nil {
  1474. if IsErrUserNotExist(err) {
  1475. c.PosterID = -1
  1476. c.Poster = NewFakeUser()
  1477. } else {
  1478. log.Error(3, "GetUserByID[%d]: %v", c.ID, err)
  1479. }
  1480. }
  1481. case "created":
  1482. c.Created = regulateTimeZone(c.Created)
  1483. }
  1484. }
  1485. func (c *Comment) AfterDelete() {
  1486. _, err := DeleteAttachmentsByComment(c.ID, true)
  1487. if err != nil {
  1488. log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err)
  1489. }
  1490. }
  1491. // HashTag returns unique hash tag for comment.
  1492. func (c *Comment) HashTag() string {
  1493. return "issuecomment-" + com.ToStr(c.ID)
  1494. }
  1495. // EventTag returns unique event hash tag for comment.
  1496. func (c *Comment) EventTag() string {
  1497. return "event-" + com.ToStr(c.ID)
  1498. }
  1499. func createComment(e *xorm.Session, u *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content, commitSHA string, uuids []string) (_ *Comment, err error) {
  1500. comment := &Comment{
  1501. PosterID: u.Id,
  1502. Type: cmtType,
  1503. IssueID: issue.ID,
  1504. CommitID: commitID,
  1505. Line: line,
  1506. Content: content,
  1507. CommitSHA: commitSHA,
  1508. }
  1509. if _, err = e.Insert(comment); err != nil {
  1510. return nil, err
  1511. }
  1512. // Check comment type.
  1513. switch cmtType {
  1514. case COMMENT_TYPE_COMMENT:
  1515. if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", issue.ID); err != nil {
  1516. return nil, err
  1517. }
  1518. // Check attachments.
  1519. attachments := make([]*Attachment, 0, len(uuids))
  1520. for _, uuid := range uuids {
  1521. attach, err := getAttachmentByUUID(e, uuid)
  1522. if err != nil {
  1523. if IsErrAttachmentNotExist(err) {
  1524. continue
  1525. }
  1526. return nil, fmt.Errorf("getAttachmentByUUID[%s]: %v", uuid, err)
  1527. }
  1528. attachments = append(attachments, attach)
  1529. }
  1530. for i := range attachments {
  1531. attachments[i].IssueID = issue.ID
  1532. attachments[i].CommentID = comment.ID
  1533. // No assign value could be 0, so ignore AllCols().
  1534. if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil {
  1535. return nil, fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
  1536. }
  1537. }
  1538. // Notify watchers.
  1539. act := &Action{
  1540. ActUserID: u.Id,
  1541. ActUserName: u.LowerName,
  1542. ActEmail: u.Email,
  1543. OpType: COMMENT_ISSUE,
  1544. Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]),
  1545. RepoID: repo.ID,
  1546. RepoUserName: repo.Owner.LowerName,
  1547. RepoName: repo.LowerName,
  1548. IsPrivate: repo.IsPrivate,
  1549. }
  1550. if err = notifyWatchers(e, act); err != nil {
  1551. return nil, err
  1552. }
  1553. case COMMENT_TYPE_REOPEN:
  1554. if issue.IsPull {
  1555. _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", repo.ID)
  1556. } else {
  1557. _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", repo.ID)
  1558. }
  1559. if err != nil {
  1560. return nil, err
  1561. }
  1562. case COMMENT_TYPE_CLOSE:
  1563. if issue.IsPull {
  1564. _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", repo.ID)
  1565. } else {
  1566. _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", repo.ID)
  1567. }
  1568. if err != nil {
  1569. return nil, err
  1570. }
  1571. }
  1572. return comment, nil
  1573. }
  1574. func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) {
  1575. cmtType := COMMENT_TYPE_CLOSE
  1576. if !issue.IsClosed {
  1577. cmtType = COMMENT_TYPE_REOPEN
  1578. }
  1579. return createComment(e, doer, repo, issue, 0, 0, cmtType, "", "", nil)
  1580. }
  1581. // CreateComment creates comment of issue or commit.
  1582. func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content, commitSHA string, attachments []string) (comment *Comment, err error) {
  1583. sess := x.NewSession()
  1584. defer sessionRelease(sess)
  1585. if err = sess.Begin(); err != nil {
  1586. return nil, err
  1587. }
  1588. comment, err = createComment(sess, doer, repo, issue, commitID, line, cmtType, content, commitSHA, attachments)
  1589. if err != nil {
  1590. return nil, err
  1591. }
  1592. return comment, sess.Commit()
  1593. }
  1594. // CreateIssueComment creates a plain issue comment.
  1595. func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
  1596. return CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMENT, content, "", attachments)
  1597. }
  1598. // CreateRefComment creates a commit reference comment to issue.
  1599. func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error {
  1600. if len(commitSHA) == 0 {
  1601. return fmt.Errorf("cannot create reference with empty commit SHA")
  1602. }
  1603. // Check if same reference from same commit has already existed.
  1604. has, err := x.Get(&Comment{
  1605. Type: COMMENT_TYPE_COMMIT_REF,
  1606. IssueID: issue.ID,
  1607. CommitSHA: commitSHA,
  1608. })
  1609. if err != nil {
  1610. return fmt.Errorf("check reference comment: %v", err)
  1611. } else if has {
  1612. return nil
  1613. }
  1614. _, err = CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMIT_REF, content, commitSHA, nil)
  1615. return err
  1616. }
  1617. // GetCommentByID returns the comment by given ID.
  1618. func GetCommentByID(id int64) (*Comment, error) {
  1619. c := new(Comment)
  1620. has, err := x.Id(id).Get(c)
  1621. if err != nil {
  1622. return nil, err
  1623. } else if !has {
  1624. return nil, ErrCommentNotExist{id}
  1625. }
  1626. return c, nil
  1627. }
  1628. // GetCommentsByIssueID returns all comments of issue by given ID.
  1629. func GetCommentsByIssueID(issueID int64) ([]*Comment, error) {
  1630. comments := make([]*Comment, 0, 10)
  1631. return comments, x.Where("issue_id=?", issueID).Asc("created").Find(&comments)
  1632. }
  1633. // UpdateComment updates information of comment.
  1634. func UpdateComment(c *Comment) error {
  1635. _, err := x.Id(c.ID).AllCols().Update(c)
  1636. return err
  1637. }
  1638. // Attachment represent a attachment of issue/comment/release.
  1639. type Attachment struct {
  1640. ID int64 `xorm:"pk autoincr"`
  1641. UUID string `xorm:"uuid UNIQUE"`
  1642. IssueID int64 `xorm:"INDEX"`
  1643. CommentID int64
  1644. ReleaseID int64 `xorm:"INDEX"`
  1645. Name string
  1646. Created time.Time `xorm:"CREATED"`
  1647. }
  1648. // AttachmentLocalPath returns where attachment is stored in local file system based on given UUID.
  1649. func AttachmentLocalPath(uuid string) string {
  1650. return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid)
  1651. }
  1652. // LocalPath returns where attachment is stored in local file system.
  1653. func (attach *Attachment) LocalPath() string {
  1654. return AttachmentLocalPath(attach.UUID)
  1655. }
  1656. // NewAttachment creates a new attachment object.
  1657. func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment, err error) {
  1658. attach := &Attachment{
  1659. UUID: gouuid.NewV4().String(),
  1660. Name: name,
  1661. }
  1662. if err = os.MkdirAll(path.Dir(attach.LocalPath()), os.ModePerm); err != nil {
  1663. return nil, fmt.Errorf("MkdirAll: %v", err)
  1664. }
  1665. fw, err := os.Create(attach.LocalPath())
  1666. if err != nil {
  1667. return nil, fmt.Errorf("Create: %v", err)
  1668. }
  1669. defer fw.Close()
  1670. if _, err = fw.Write(buf); err != nil {
  1671. return nil, fmt.Errorf("Write: %v", err)
  1672. } else if _, err = io.Copy(fw, file); err != nil {
  1673. return nil, fmt.Errorf("Copy: %v", err)
  1674. }
  1675. sess := x.NewSession()
  1676. defer sessionRelease(sess)
  1677. if err := sess.Begin(); err != nil {
  1678. return nil, err
  1679. }
  1680. if _, err := sess.Insert(attach); err != nil {
  1681. return nil, err
  1682. }
  1683. return attach, sess.Commit()
  1684. }
  1685. func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) {
  1686. attach := &Attachment{UUID: uuid}
  1687. has, err := x.Get(attach)
  1688. if err != nil {
  1689. return nil, err
  1690. } else if !has {
  1691. return nil, ErrAttachmentNotExist{0, uuid}
  1692. }
  1693. return attach, nil
  1694. }
  1695. // GetAttachmentByUUID returns attachment by given UUID.
  1696. func GetAttachmentByUUID(uuid string) (*Attachment, error) {
  1697. return getAttachmentByUUID(x, uuid)
  1698. }
  1699. // GetAttachmentsByIssueID returns all attachments for given issue by ID.
  1700. func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) {
  1701. attachments := make([]*Attachment, 0, 10)
  1702. return attachments, x.Where("issue_id=? AND comment_id=0", issueID).Find(&attachments)
  1703. }
  1704. // GetAttachmentsByCommentID returns all attachments if comment by given ID.
  1705. func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) {
  1706. attachments := make([]*Attachment, 0, 10)
  1707. return attachments, x.Where("comment_id=?", commentID).Find(&attachments)
  1708. }
  1709. // DeleteAttachment deletes the given attachment and optionally the associated file.
  1710. func DeleteAttachment(a *Attachment, remove bool) error {
  1711. _, err := DeleteAttachments([]*Attachment{a}, remove)
  1712. return err
  1713. }
  1714. // DeleteAttachments deletes the given attachments and optionally the associated files.
  1715. func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
  1716. for i, a := range attachments {
  1717. if remove {
  1718. if err := os.Remove(a.LocalPath()); err != nil {
  1719. return i, err
  1720. }
  1721. }
  1722. if _, err := x.Delete(a.ID); err != nil {
  1723. return i, err
  1724. }
  1725. }
  1726. return len(attachments), nil
  1727. }
  1728. // DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
  1729. func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) {
  1730. attachments, err := GetAttachmentsByIssueID(issueId)
  1731. if err != nil {
  1732. return 0, err
  1733. }
  1734. return DeleteAttachments(attachments, remove)
  1735. }
  1736. // DeleteAttachmentsByComment deletes all attachments associated with the given comment.
  1737. func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) {
  1738. attachments, err := GetAttachmentsByCommentID(commentId)
  1739. if err != nil {
  1740. return 0, err
  1741. }
  1742. return DeleteAttachments(attachments, remove)
  1743. }