gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package issues
  4. import (
  5. "context"
  6. "fmt"
  7. "code.gitea.io/gitea/models/db"
  8. project_model "code.gitea.io/gitea/models/project"
  9. repo_model "code.gitea.io/gitea/models/repo"
  10. user_model "code.gitea.io/gitea/models/user"
  11. "code.gitea.io/gitea/modules/container"
  12. "xorm.io/builder"
  13. )
  14. // IssueList defines a list of issues
  15. type IssueList []*Issue
  16. // get the repo IDs to be loaded later, these IDs are for issue.Repo and issue.PullRequest.HeadRepo
  17. func (issues IssueList) getRepoIDs() []int64 {
  18. return container.FilterSlice(issues, func(issue *Issue) (int64, bool) {
  19. if issue.Repo == nil {
  20. return issue.RepoID, true
  21. }
  22. if issue.PullRequest != nil && issue.PullRequest.HeadRepo == nil {
  23. return issue.PullRequest.HeadRepoID, true
  24. }
  25. return 0, false
  26. })
  27. }
  28. // LoadRepositories loads issues' all repositories
  29. func (issues IssueList) LoadRepositories(ctx context.Context) (repo_model.RepositoryList, error) {
  30. if len(issues) == 0 {
  31. return nil, nil
  32. }
  33. repoIDs := issues.getRepoIDs()
  34. repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs))
  35. left := len(repoIDs)
  36. for left > 0 {
  37. limit := min(left, db.DefaultMaxInSize)
  38. err := db.GetEngine(ctx).
  39. In("id", repoIDs[:limit]).
  40. Find(&repoMaps)
  41. if err != nil {
  42. return nil, fmt.Errorf("find repository: %w", err)
  43. }
  44. left -= limit
  45. repoIDs = repoIDs[limit:]
  46. }
  47. for _, issue := range issues {
  48. if issue.Repo == nil {
  49. issue.Repo = repoMaps[issue.RepoID]
  50. } else {
  51. repoMaps[issue.RepoID] = issue.Repo
  52. }
  53. if issue.PullRequest != nil {
  54. issue.PullRequest.BaseRepo = issue.Repo
  55. if issue.PullRequest.HeadRepo == nil {
  56. issue.PullRequest.HeadRepo = repoMaps[issue.PullRequest.HeadRepoID]
  57. }
  58. }
  59. }
  60. return repo_model.ValuesRepository(repoMaps), nil
  61. }
  62. func (issues IssueList) LoadPosters(ctx context.Context) error {
  63. if len(issues) == 0 {
  64. return nil
  65. }
  66. posterIDs := container.FilterSlice(issues, func(issue *Issue) (int64, bool) {
  67. return issue.PosterID, issue.Poster == nil && issue.PosterID > 0
  68. })
  69. posterMaps, err := user_model.GetUsersMapByIDs(ctx, posterIDs)
  70. if err != nil {
  71. return err
  72. }
  73. for _, issue := range issues {
  74. if issue.Poster == nil {
  75. issue.Poster = user_model.GetPossibleUserFromMap(issue.PosterID, posterMaps)
  76. }
  77. }
  78. return nil
  79. }
  80. func (issues IssueList) getIssueIDs() []int64 {
  81. ids := make([]int64, 0, len(issues))
  82. for _, issue := range issues {
  83. ids = append(ids, issue.ID)
  84. }
  85. return ids
  86. }
  87. func (issues IssueList) LoadLabels(ctx context.Context) error {
  88. if len(issues) == 0 {
  89. return nil
  90. }
  91. type LabelIssue struct {
  92. Label *Label `xorm:"extends"`
  93. IssueLabel *IssueLabel `xorm:"extends"`
  94. }
  95. issueLabels := make(map[int64][]*Label, len(issues)*3)
  96. issueIDs := issues.getIssueIDs()
  97. left := len(issueIDs)
  98. for left > 0 {
  99. limit := min(left, db.DefaultMaxInSize)
  100. rows, err := db.GetEngine(ctx).Table("label").
  101. Join("LEFT", "issue_label", "issue_label.label_id = label.id").
  102. In("issue_label.issue_id", issueIDs[:limit]).
  103. Asc("label.name").
  104. Rows(new(LabelIssue))
  105. if err != nil {
  106. return err
  107. }
  108. for rows.Next() {
  109. var labelIssue LabelIssue
  110. err = rows.Scan(&labelIssue)
  111. if err != nil {
  112. if err1 := rows.Close(); err1 != nil {
  113. return fmt.Errorf("IssueList.LoadLabels: Close: %w", err1)
  114. }
  115. return err
  116. }
  117. issueLabels[labelIssue.IssueLabel.IssueID] = append(issueLabels[labelIssue.IssueLabel.IssueID], labelIssue.Label)
  118. }
  119. // When there are no rows left and we try to close it.
  120. // Since that is not relevant for us, we can safely ignore it.
  121. if err1 := rows.Close(); err1 != nil {
  122. return fmt.Errorf("IssueList.LoadLabels: Close: %w", err1)
  123. }
  124. left -= limit
  125. issueIDs = issueIDs[limit:]
  126. }
  127. for _, issue := range issues {
  128. issue.Labels = issueLabels[issue.ID]
  129. issue.isLabelsLoaded = true
  130. }
  131. return nil
  132. }
  133. func (issues IssueList) getMilestoneIDs() []int64 {
  134. return container.FilterSlice(issues, func(issue *Issue) (int64, bool) {
  135. return issue.MilestoneID, true
  136. })
  137. }
  138. func (issues IssueList) LoadMilestones(ctx context.Context) error {
  139. milestoneIDs := issues.getMilestoneIDs()
  140. if len(milestoneIDs) == 0 {
  141. return nil
  142. }
  143. milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
  144. left := len(milestoneIDs)
  145. for left > 0 {
  146. limit := min(left, db.DefaultMaxInSize)
  147. err := db.GetEngine(ctx).
  148. In("id", milestoneIDs[:limit]).
  149. Find(&milestoneMaps)
  150. if err != nil {
  151. return err
  152. }
  153. left -= limit
  154. milestoneIDs = milestoneIDs[limit:]
  155. }
  156. for _, issue := range issues {
  157. issue.Milestone = milestoneMaps[issue.MilestoneID]
  158. issue.isMilestoneLoaded = true
  159. }
  160. return nil
  161. }
  162. func (issues IssueList) LoadProjects(ctx context.Context) error {
  163. issueIDs := issues.getIssueIDs()
  164. projectMaps := make(map[int64]*project_model.Project, len(issues))
  165. left := len(issueIDs)
  166. type projectWithIssueID struct {
  167. *project_model.Project `xorm:"extends"`
  168. IssueID int64
  169. }
  170. for left > 0 {
  171. limit := min(left, db.DefaultMaxInSize)
  172. projects := make([]*projectWithIssueID, 0, limit)
  173. err := db.GetEngine(ctx).
  174. Table("project").
  175. Select("project.*, project_issue.issue_id").
  176. Join("INNER", "project_issue", "project.id = project_issue.project_id").
  177. In("project_issue.issue_id", issueIDs[:limit]).
  178. Find(&projects)
  179. if err != nil {
  180. return err
  181. }
  182. for _, project := range projects {
  183. projectMaps[project.IssueID] = project.Project
  184. }
  185. left -= limit
  186. issueIDs = issueIDs[limit:]
  187. }
  188. for _, issue := range issues {
  189. issue.Project = projectMaps[issue.ID]
  190. }
  191. return nil
  192. }
  193. func (issues IssueList) LoadAssignees(ctx context.Context) error {
  194. if len(issues) == 0 {
  195. return nil
  196. }
  197. type AssigneeIssue struct {
  198. IssueAssignee *IssueAssignees `xorm:"extends"`
  199. Assignee *user_model.User `xorm:"extends"`
  200. }
  201. assignees := make(map[int64][]*user_model.User, len(issues))
  202. issueIDs := issues.getIssueIDs()
  203. left := len(issueIDs)
  204. for left > 0 {
  205. limit := min(left, db.DefaultMaxInSize)
  206. rows, err := db.GetEngine(ctx).Table("issue_assignees").
  207. Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
  208. In("`issue_assignees`.issue_id", issueIDs[:limit]).OrderBy(user_model.GetOrderByName()).
  209. Rows(new(AssigneeIssue))
  210. if err != nil {
  211. return err
  212. }
  213. for rows.Next() {
  214. var assigneeIssue AssigneeIssue
  215. err = rows.Scan(&assigneeIssue)
  216. if err != nil {
  217. if err1 := rows.Close(); err1 != nil {
  218. return fmt.Errorf("IssueList.loadAssignees: Close: %w", err1)
  219. }
  220. return err
  221. }
  222. assignees[assigneeIssue.IssueAssignee.IssueID] = append(assignees[assigneeIssue.IssueAssignee.IssueID], assigneeIssue.Assignee)
  223. }
  224. if err1 := rows.Close(); err1 != nil {
  225. return fmt.Errorf("IssueList.loadAssignees: Close: %w", err1)
  226. }
  227. left -= limit
  228. issueIDs = issueIDs[limit:]
  229. }
  230. for _, issue := range issues {
  231. issue.Assignees = assignees[issue.ID]
  232. if len(issue.Assignees) > 0 {
  233. issue.Assignee = issue.Assignees[0]
  234. }
  235. issue.isAssigneeLoaded = true
  236. }
  237. return nil
  238. }
  239. func (issues IssueList) getPullIssueIDs() []int64 {
  240. ids := make([]int64, 0, len(issues))
  241. for _, issue := range issues {
  242. if issue.IsPull && issue.PullRequest == nil {
  243. ids = append(ids, issue.ID)
  244. }
  245. }
  246. return ids
  247. }
  248. // LoadPullRequests loads pull requests
  249. func (issues IssueList) LoadPullRequests(ctx context.Context) error {
  250. issuesIDs := issues.getPullIssueIDs()
  251. if len(issuesIDs) == 0 {
  252. return nil
  253. }
  254. pullRequestMaps := make(map[int64]*PullRequest, len(issuesIDs))
  255. left := len(issuesIDs)
  256. for left > 0 {
  257. limit := min(left, db.DefaultMaxInSize)
  258. rows, err := db.GetEngine(ctx).
  259. In("issue_id", issuesIDs[:limit]).
  260. Rows(new(PullRequest))
  261. if err != nil {
  262. return err
  263. }
  264. for rows.Next() {
  265. var pr PullRequest
  266. err = rows.Scan(&pr)
  267. if err != nil {
  268. if err1 := rows.Close(); err1 != nil {
  269. return fmt.Errorf("IssueList.loadPullRequests: Close: %w", err1)
  270. }
  271. return err
  272. }
  273. pullRequestMaps[pr.IssueID] = &pr
  274. }
  275. if err1 := rows.Close(); err1 != nil {
  276. return fmt.Errorf("IssueList.loadPullRequests: Close: %w", err1)
  277. }
  278. left -= limit
  279. issuesIDs = issuesIDs[limit:]
  280. }
  281. for _, issue := range issues {
  282. issue.PullRequest = pullRequestMaps[issue.ID]
  283. if issue.PullRequest != nil {
  284. issue.PullRequest.Issue = issue
  285. }
  286. }
  287. return nil
  288. }
  289. // LoadAttachments loads attachments
  290. func (issues IssueList) LoadAttachments(ctx context.Context) (err error) {
  291. if len(issues) == 0 {
  292. return nil
  293. }
  294. attachments := make(map[int64][]*repo_model.Attachment, len(issues))
  295. issuesIDs := issues.getIssueIDs()
  296. left := len(issuesIDs)
  297. for left > 0 {
  298. limit := min(left, db.DefaultMaxInSize)
  299. rows, err := db.GetEngine(ctx).
  300. In("issue_id", issuesIDs[:limit]).
  301. Rows(new(repo_model.Attachment))
  302. if err != nil {
  303. return err
  304. }
  305. for rows.Next() {
  306. var attachment repo_model.Attachment
  307. err = rows.Scan(&attachment)
  308. if err != nil {
  309. if err1 := rows.Close(); err1 != nil {
  310. return fmt.Errorf("IssueList.loadAttachments: Close: %w", err1)
  311. }
  312. return err
  313. }
  314. attachments[attachment.IssueID] = append(attachments[attachment.IssueID], &attachment)
  315. }
  316. if err1 := rows.Close(); err1 != nil {
  317. return fmt.Errorf("IssueList.loadAttachments: Close: %w", err1)
  318. }
  319. left -= limit
  320. issuesIDs = issuesIDs[limit:]
  321. }
  322. for _, issue := range issues {
  323. issue.Attachments = attachments[issue.ID]
  324. issue.isAttachmentsLoaded = true
  325. }
  326. return nil
  327. }
  328. func (issues IssueList) loadComments(ctx context.Context, cond builder.Cond) (err error) {
  329. if len(issues) == 0 {
  330. return nil
  331. }
  332. comments := make(map[int64][]*Comment, len(issues))
  333. issuesIDs := issues.getIssueIDs()
  334. left := len(issuesIDs)
  335. for left > 0 {
  336. limit := min(left, db.DefaultMaxInSize)
  337. rows, err := db.GetEngine(ctx).Table("comment").
  338. Join("INNER", "issue", "issue.id = comment.issue_id").
  339. In("issue.id", issuesIDs[:limit]).
  340. Where(cond).
  341. NoAutoCondition().
  342. Rows(new(Comment))
  343. if err != nil {
  344. return err
  345. }
  346. for rows.Next() {
  347. var comment Comment
  348. err = rows.Scan(&comment)
  349. if err != nil {
  350. if err1 := rows.Close(); err1 != nil {
  351. return fmt.Errorf("IssueList.loadComments: Close: %w", err1)
  352. }
  353. return err
  354. }
  355. comments[comment.IssueID] = append(comments[comment.IssueID], &comment)
  356. }
  357. if err1 := rows.Close(); err1 != nil {
  358. return fmt.Errorf("IssueList.loadComments: Close: %w", err1)
  359. }
  360. left -= limit
  361. issuesIDs = issuesIDs[limit:]
  362. }
  363. for _, issue := range issues {
  364. issue.Comments = comments[issue.ID]
  365. }
  366. return nil
  367. }
  368. func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) {
  369. type totalTimesByIssue struct {
  370. IssueID int64
  371. Time int64
  372. }
  373. if len(issues) == 0 {
  374. return nil
  375. }
  376. trackedTimes := make(map[int64]int64, len(issues))
  377. reposMap := make(map[int64]*repo_model.Repository, len(issues))
  378. for _, issue := range issues {
  379. reposMap[issue.RepoID] = issue.Repo
  380. }
  381. repos := repo_model.RepositoryListOfMap(reposMap)
  382. if err := repos.LoadUnits(ctx); err != nil {
  383. return err
  384. }
  385. ids := make([]int64, 0, len(issues))
  386. for _, issue := range issues {
  387. if issue.Repo.IsTimetrackerEnabled(ctx) {
  388. ids = append(ids, issue.ID)
  389. }
  390. }
  391. left := len(ids)
  392. for left > 0 {
  393. limit := min(left, db.DefaultMaxInSize)
  394. // select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id
  395. rows, err := db.GetEngine(ctx).Table("tracked_time").
  396. Where("deleted = ?", false).
  397. Select("issue_id, sum(time) as time").
  398. In("issue_id", ids[:limit]).
  399. GroupBy("issue_id").
  400. Rows(new(totalTimesByIssue))
  401. if err != nil {
  402. return err
  403. }
  404. for rows.Next() {
  405. var totalTime totalTimesByIssue
  406. err = rows.Scan(&totalTime)
  407. if err != nil {
  408. if err1 := rows.Close(); err1 != nil {
  409. return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %w", err1)
  410. }
  411. return err
  412. }
  413. trackedTimes[totalTime.IssueID] = totalTime.Time
  414. }
  415. if err1 := rows.Close(); err1 != nil {
  416. return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %w", err1)
  417. }
  418. left -= limit
  419. ids = ids[limit:]
  420. }
  421. for _, issue := range issues {
  422. issue.TotalTrackedTime = trackedTimes[issue.ID]
  423. }
  424. return nil
  425. }
  426. func (issues IssueList) LoadPinOrder(ctx context.Context) error {
  427. if len(issues) == 0 {
  428. return nil
  429. }
  430. issueIDs := container.FilterSlice(issues, func(issue *Issue) (int64, bool) {
  431. return issue.ID, issue.PinOrder == 0
  432. })
  433. if len(issueIDs) == 0 {
  434. return nil
  435. }
  436. issuePins, err := GetIssuePinsByIssueIDs(ctx, issueIDs)
  437. if err != nil {
  438. return err
  439. }
  440. for _, issue := range issues {
  441. if issue.PinOrder != 0 {
  442. continue
  443. }
  444. for _, pin := range issuePins {
  445. if pin.IssueID == issue.ID {
  446. issue.PinOrder = pin.PinOrder
  447. break
  448. }
  449. }
  450. if issue.PinOrder == 0 {
  451. issue.PinOrder = -1
  452. }
  453. }
  454. return nil
  455. }
  456. // loadAttributes loads all attributes, expect for attachments and comments
  457. func (issues IssueList) LoadAttributes(ctx context.Context) error {
  458. if _, err := issues.LoadRepositories(ctx); err != nil {
  459. return fmt.Errorf("issue.loadAttributes: LoadRepositories: %w", err)
  460. }
  461. if err := issues.LoadPosters(ctx); err != nil {
  462. return fmt.Errorf("issue.loadAttributes: LoadPosters: %w", err)
  463. }
  464. if err := issues.LoadLabels(ctx); err != nil {
  465. return fmt.Errorf("issue.loadAttributes: LoadLabels: %w", err)
  466. }
  467. if err := issues.LoadMilestones(ctx); err != nil {
  468. return fmt.Errorf("issue.loadAttributes: LoadMilestones: %w", err)
  469. }
  470. if err := issues.LoadProjects(ctx); err != nil {
  471. return fmt.Errorf("issue.loadAttributes: loadProjects: %w", err)
  472. }
  473. if err := issues.LoadAssignees(ctx); err != nil {
  474. return fmt.Errorf("issue.loadAttributes: loadAssignees: %w", err)
  475. }
  476. if err := issues.LoadPullRequests(ctx); err != nil {
  477. return fmt.Errorf("issue.loadAttributes: loadPullRequests: %w", err)
  478. }
  479. if err := issues.loadTotalTrackedTimes(ctx); err != nil {
  480. return fmt.Errorf("issue.loadAttributes: loadTotalTrackedTimes: %w", err)
  481. }
  482. return nil
  483. }
  484. // LoadComments loads comments
  485. func (issues IssueList) LoadComments(ctx context.Context) error {
  486. return issues.loadComments(ctx, builder.NewCond())
  487. }
  488. // LoadDiscussComments loads discuss comments
  489. func (issues IssueList) LoadDiscussComments(ctx context.Context) error {
  490. return issues.loadComments(ctx, builder.Eq{"comment.type": CommentTypeComment})
  491. }
  492. // GetApprovalCounts returns a map of issue ID to slice of approval counts
  493. // FIXME: only returns official counts due to double counting of non-official approvals
  494. func (issues IssueList) GetApprovalCounts(ctx context.Context) (map[int64][]*ReviewCount, error) {
  495. rCounts := make([]*ReviewCount, 0, 2*len(issues))
  496. ids := make([]int64, len(issues))
  497. for i, issue := range issues {
  498. ids[i] = issue.ID
  499. }
  500. sess := db.GetEngine(ctx).In("issue_id", ids)
  501. err := sess.Select("issue_id, type, count(id) as `count`").
  502. Where("official = ? AND dismissed = ?", true, false).
  503. GroupBy("issue_id, type").
  504. OrderBy("issue_id").
  505. Table("review").
  506. Find(&rCounts)
  507. if err != nil {
  508. return nil, err
  509. }
  510. approvalCountMap := make(map[int64][]*ReviewCount, len(issues))
  511. for _, c := range rCounts {
  512. approvalCountMap[c.IssueID] = append(approvalCountMap[c.IssueID], c)
  513. }
  514. return approvalCountMap, nil
  515. }
  516. func (issues IssueList) LoadIsRead(ctx context.Context, userID int64) error {
  517. issueIDs := issues.getIssueIDs()
  518. issueUsers := make([]*IssueUser, 0, len(issueIDs))
  519. if err := db.GetEngine(ctx).Where("uid =?", userID).
  520. In("issue_id").
  521. Find(&issueUsers); err != nil {
  522. return err
  523. }
  524. for _, issueUser := range issueUsers {
  525. for _, issue := range issues {
  526. if issue.ID == issueUser.IssueID {
  527. issue.IsRead = issueUser.IsRead
  528. }
  529. }
  530. }
  531. return nil
  532. }