gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package issues
  4. import (
  5. "context"
  6. "fmt"
  7. "sort"
  8. "code.gitea.io/gitea/models/db"
  9. access_model "code.gitea.io/gitea/models/perm/access"
  10. user_model "code.gitea.io/gitea/models/user"
  11. "xorm.io/builder"
  12. )
  13. // IssueLabel represents an issue-label relation.
  14. type IssueLabel struct {
  15. ID int64 `xorm:"pk autoincr"`
  16. IssueID int64 `xorm:"UNIQUE(s)"`
  17. LabelID int64 `xorm:"UNIQUE(s)"`
  18. }
  19. // HasIssueLabel returns true if issue has been labeled.
  20. func HasIssueLabel(ctx context.Context, issueID, labelID int64) bool {
  21. has, _ := db.GetEngine(ctx).Where("issue_id = ? AND label_id = ?", issueID, labelID).Get(new(IssueLabel))
  22. return has
  23. }
  24. // newIssueLabel this function creates a new label it does not check if the label is valid for the issue
  25. // YOU MUST CHECK THIS BEFORE THIS FUNCTION
  26. func newIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_model.User) (err error) {
  27. if err = db.Insert(ctx, &IssueLabel{
  28. IssueID: issue.ID,
  29. LabelID: label.ID,
  30. }); err != nil {
  31. return err
  32. }
  33. if err = issue.LoadRepo(ctx); err != nil {
  34. return err
  35. }
  36. opts := &CreateCommentOptions{
  37. Type: CommentTypeLabel,
  38. Doer: doer,
  39. Repo: issue.Repo,
  40. Issue: issue,
  41. Label: label,
  42. Content: "1",
  43. }
  44. if _, err = CreateComment(ctx, opts); err != nil {
  45. return err
  46. }
  47. issue.Labels = append(issue.Labels, label)
  48. return updateLabelCols(ctx, label, "num_issues", "num_closed_issue")
  49. }
  50. // Remove all issue labels in the given exclusive scope
  51. func RemoveDuplicateExclusiveIssueLabels(ctx context.Context, issue *Issue, label *Label, doer *user_model.User) (err error) {
  52. scope := label.ExclusiveScope()
  53. if scope == "" {
  54. return nil
  55. }
  56. var toRemove []*Label
  57. for _, issueLabel := range issue.Labels {
  58. if label.ID != issueLabel.ID && issueLabel.ExclusiveScope() == scope {
  59. toRemove = append(toRemove, issueLabel)
  60. }
  61. }
  62. for _, issueLabel := range toRemove {
  63. if err = deleteIssueLabel(ctx, issue, issueLabel, doer); err != nil {
  64. return err
  65. }
  66. }
  67. return nil
  68. }
  69. // NewIssueLabel creates a new issue-label relation.
  70. func NewIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_model.User) (err error) {
  71. if HasIssueLabel(ctx, issue.ID, label.ID) {
  72. return nil
  73. }
  74. return db.WithTx(ctx, func(ctx context.Context) error {
  75. if err = issue.LoadRepo(ctx); err != nil {
  76. return err
  77. }
  78. // Do NOT add invalid labels
  79. if issue.RepoID != label.RepoID && issue.Repo.OwnerID != label.OrgID {
  80. return nil
  81. }
  82. if err = RemoveDuplicateExclusiveIssueLabels(ctx, issue, label, doer); err != nil {
  83. return nil
  84. }
  85. if err = newIssueLabel(ctx, issue, label, doer); err != nil {
  86. return err
  87. }
  88. issue.isLabelsLoaded = false
  89. issue.Labels = nil
  90. return issue.LoadLabels(ctx)
  91. })
  92. }
  93. // newIssueLabels add labels to an issue. It will check if the labels are valid for the issue
  94. func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *user_model.User) (err error) {
  95. if err = issue.LoadRepo(ctx); err != nil {
  96. return err
  97. }
  98. if err = issue.LoadLabels(ctx); err != nil {
  99. return err
  100. }
  101. for _, l := range labels {
  102. // Don't add already present labels and invalid labels
  103. if HasIssueLabel(ctx, issue.ID, l.ID) ||
  104. (l.RepoID != issue.RepoID && l.OrgID != issue.Repo.OwnerID) {
  105. continue
  106. }
  107. if err = RemoveDuplicateExclusiveIssueLabels(ctx, issue, l, doer); err != nil {
  108. return err
  109. }
  110. if err = newIssueLabel(ctx, issue, l, doer); err != nil {
  111. return fmt.Errorf("newIssueLabel: %w", err)
  112. }
  113. }
  114. return nil
  115. }
  116. // NewIssueLabels creates a list of issue-label relations.
  117. func NewIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *user_model.User) (err error) {
  118. return db.WithTx(ctx, func(ctx context.Context) error {
  119. if err = newIssueLabels(ctx, issue, labels, doer); err != nil {
  120. return err
  121. }
  122. // reload all labels
  123. issue.isLabelsLoaded = false
  124. issue.Labels = nil
  125. return issue.LoadLabels(ctx)
  126. })
  127. }
  128. func deleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_model.User) (err error) {
  129. if count, err := db.DeleteByBean(ctx, &IssueLabel{
  130. IssueID: issue.ID,
  131. LabelID: label.ID,
  132. }); err != nil {
  133. return err
  134. } else if count == 0 {
  135. return nil
  136. }
  137. if err = issue.LoadRepo(ctx); err != nil {
  138. return err
  139. }
  140. opts := &CreateCommentOptions{
  141. Type: CommentTypeLabel,
  142. Doer: doer,
  143. Repo: issue.Repo,
  144. Issue: issue,
  145. Label: label,
  146. }
  147. if _, err = CreateComment(ctx, opts); err != nil {
  148. return err
  149. }
  150. return updateLabelCols(ctx, label, "num_issues", "num_closed_issue")
  151. }
  152. // DeleteIssueLabel deletes issue-label relation.
  153. func DeleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_model.User) error {
  154. if err := deleteIssueLabel(ctx, issue, label, doer); err != nil {
  155. return err
  156. }
  157. issue.Labels = nil
  158. issue.isLabelsLoaded = false
  159. return issue.LoadLabels(ctx)
  160. }
  161. // DeleteLabelsByRepoID deletes labels of some repository
  162. func DeleteLabelsByRepoID(ctx context.Context, repoID int64) error {
  163. deleteCond := builder.Select("id").From("label").Where(builder.Eq{"label.repo_id": repoID})
  164. if _, err := db.GetEngine(ctx).In("label_id", deleteCond).
  165. Delete(&IssueLabel{}); err != nil {
  166. return err
  167. }
  168. _, err := db.DeleteByBean(ctx, &Label{RepoID: repoID})
  169. return err
  170. }
  171. // CountOrphanedLabels return count of labels witch are broken and not accessible via ui anymore
  172. func CountOrphanedLabels(ctx context.Context) (int64, error) {
  173. noref, err := db.GetEngine(ctx).Table("label").Where("repo_id=? AND org_id=?", 0, 0).Count()
  174. if err != nil {
  175. return 0, err
  176. }
  177. norepo, err := db.GetEngine(ctx).Table("label").
  178. Where(builder.And(
  179. builder.Gt{"repo_id": 0},
  180. builder.NotIn("repo_id", builder.Select("id").From("`repository`")),
  181. )).
  182. Count()
  183. if err != nil {
  184. return 0, err
  185. }
  186. noorg, err := db.GetEngine(ctx).Table("label").
  187. Where(builder.And(
  188. builder.Gt{"org_id": 0},
  189. builder.NotIn("org_id", builder.Select("id").From("`user`")),
  190. )).
  191. Count()
  192. if err != nil {
  193. return 0, err
  194. }
  195. return noref + norepo + noorg, nil
  196. }
  197. // DeleteOrphanedLabels delete labels witch are broken and not accessible via ui anymore
  198. func DeleteOrphanedLabels(ctx context.Context) error {
  199. // delete labels with no reference
  200. if _, err := db.GetEngine(ctx).Table("label").Where("repo_id=? AND org_id=?", 0, 0).Delete(new(Label)); err != nil {
  201. return err
  202. }
  203. // delete labels with none existing repos
  204. if _, err := db.GetEngine(ctx).
  205. Where(builder.And(
  206. builder.Gt{"repo_id": 0},
  207. builder.NotIn("repo_id", builder.Select("id").From("`repository`")),
  208. )).
  209. Delete(Label{}); err != nil {
  210. return err
  211. }
  212. // delete labels with none existing orgs
  213. if _, err := db.GetEngine(ctx).
  214. Where(builder.And(
  215. builder.Gt{"org_id": 0},
  216. builder.NotIn("org_id", builder.Select("id").From("`user`")),
  217. )).
  218. Delete(Label{}); err != nil {
  219. return err
  220. }
  221. return nil
  222. }
  223. // CountOrphanedIssueLabels return count of IssueLabels witch have no label behind anymore
  224. func CountOrphanedIssueLabels(ctx context.Context) (int64, error) {
  225. return db.GetEngine(ctx).Table("issue_label").
  226. NotIn("label_id", builder.Select("id").From("label")).
  227. Count()
  228. }
  229. // DeleteOrphanedIssueLabels delete IssueLabels witch have no label behind anymore
  230. func DeleteOrphanedIssueLabels(ctx context.Context) error {
  231. _, err := db.GetEngine(ctx).
  232. NotIn("label_id", builder.Select("id").From("label")).
  233. Delete(IssueLabel{})
  234. return err
  235. }
  236. // CountIssueLabelWithOutsideLabels count label comments with outside label
  237. func CountIssueLabelWithOutsideLabels(ctx context.Context) (int64, error) {
  238. return db.GetEngine(ctx).Where(builder.Expr("(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)")).
  239. Table("issue_label").
  240. Join("inner", "label", "issue_label.label_id = label.id ").
  241. Join("inner", "issue", "issue.id = issue_label.issue_id ").
  242. Join("inner", "repository", "issue.repo_id = repository.id").
  243. Count(new(IssueLabel))
  244. }
  245. // FixIssueLabelWithOutsideLabels fix label comments with outside label
  246. func FixIssueLabelWithOutsideLabels(ctx context.Context) (int64, error) {
  247. res, err := db.GetEngine(ctx).Exec(`DELETE FROM issue_label WHERE issue_label.id IN (
  248. SELECT il_too.id FROM (
  249. SELECT il_too_too.id
  250. FROM issue_label AS il_too_too
  251. INNER JOIN label ON il_too_too.label_id = label.id
  252. INNER JOIN issue on issue.id = il_too_too.issue_id
  253. INNER JOIN repository on repository.id = issue.repo_id
  254. WHERE
  255. (label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
  256. ) AS il_too )`)
  257. if err != nil {
  258. return 0, err
  259. }
  260. return res.RowsAffected()
  261. }
  262. // LoadLabels loads labels
  263. func (issue *Issue) LoadLabels(ctx context.Context) (err error) {
  264. if !issue.isLabelsLoaded && issue.Labels == nil && issue.ID != 0 {
  265. issue.Labels, err = GetLabelsByIssueID(ctx, issue.ID)
  266. if err != nil {
  267. return fmt.Errorf("getLabelsByIssueID [%d]: %w", issue.ID, err)
  268. }
  269. issue.isLabelsLoaded = true
  270. }
  271. return nil
  272. }
  273. // GetLabelsByIssueID returns all labels that belong to given issue by ID.
  274. func GetLabelsByIssueID(ctx context.Context, issueID int64) ([]*Label, error) {
  275. var labels []*Label
  276. return labels, db.GetEngine(ctx).Where("issue_label.issue_id = ?", issueID).
  277. Join("LEFT", "issue_label", "issue_label.label_id = label.id").
  278. Asc("label.name").
  279. Find(&labels)
  280. }
  281. func clearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User) (err error) {
  282. if err = issue.LoadLabels(ctx); err != nil {
  283. return fmt.Errorf("getLabels: %w", err)
  284. }
  285. for i := range issue.Labels {
  286. if err = deleteIssueLabel(ctx, issue, issue.Labels[i], doer); err != nil {
  287. return fmt.Errorf("removeLabel: %w", err)
  288. }
  289. }
  290. return nil
  291. }
  292. // ClearIssueLabels removes all issue labels as the given user.
  293. // Triggers appropriate WebHooks, if any.
  294. func ClearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User) (err error) {
  295. return db.WithTx(ctx, func(ctx context.Context) error {
  296. if err := issue.LoadRepo(ctx); err != nil {
  297. return err
  298. } else if err = issue.LoadPullRequest(ctx); err != nil {
  299. return err
  300. }
  301. perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
  302. if err != nil {
  303. return err
  304. }
  305. if !perm.CanWriteIssuesOrPulls(issue.IsPull) {
  306. return ErrRepoLabelNotExist{}
  307. }
  308. return clearIssueLabels(ctx, issue, doer)
  309. })
  310. }
  311. type labelSorter []*Label
  312. func (ts labelSorter) Len() int {
  313. return len([]*Label(ts))
  314. }
  315. func (ts labelSorter) Less(i, j int) bool {
  316. return []*Label(ts)[i].ID < []*Label(ts)[j].ID
  317. }
  318. func (ts labelSorter) Swap(i, j int) {
  319. []*Label(ts)[i], []*Label(ts)[j] = []*Label(ts)[j], []*Label(ts)[i]
  320. }
  321. // Ensure only one label of a given scope exists, with labels at the end of the
  322. // array getting preference over earlier ones.
  323. func RemoveDuplicateExclusiveLabels(labels []*Label) []*Label {
  324. validLabels := make([]*Label, 0, len(labels))
  325. for i, label := range labels {
  326. scope := label.ExclusiveScope()
  327. if scope != "" {
  328. foundOther := false
  329. for _, otherLabel := range labels[i+1:] {
  330. if otherLabel.ExclusiveScope() == scope {
  331. foundOther = true
  332. break
  333. }
  334. }
  335. if foundOther {
  336. continue
  337. }
  338. }
  339. validLabels = append(validLabels, label)
  340. }
  341. return validLabels
  342. }
  343. // ReplaceIssueLabels removes all current labels and add new labels to the issue.
  344. // Triggers appropriate WebHooks, if any.
  345. func ReplaceIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *user_model.User) (err error) {
  346. return db.WithTx(ctx, func(ctx context.Context) error {
  347. if err = issue.LoadRepo(ctx); err != nil {
  348. return err
  349. }
  350. if err = issue.LoadLabels(ctx); err != nil {
  351. return err
  352. }
  353. labels = RemoveDuplicateExclusiveLabels(labels)
  354. sort.Sort(labelSorter(labels))
  355. sort.Sort(labelSorter(issue.Labels))
  356. var toAdd, toRemove []*Label
  357. addIndex, removeIndex := 0, 0
  358. for addIndex < len(labels) && removeIndex < len(issue.Labels) {
  359. addLabel := labels[addIndex]
  360. removeLabel := issue.Labels[removeIndex]
  361. if addLabel.ID == removeLabel.ID {
  362. // Silently drop invalid labels
  363. if removeLabel.RepoID != issue.RepoID && removeLabel.OrgID != issue.Repo.OwnerID {
  364. toRemove = append(toRemove, removeLabel)
  365. }
  366. addIndex++
  367. removeIndex++
  368. } else if addLabel.ID < removeLabel.ID {
  369. // Only add if the label is valid
  370. if addLabel.RepoID == issue.RepoID || addLabel.OrgID == issue.Repo.OwnerID {
  371. toAdd = append(toAdd, addLabel)
  372. }
  373. addIndex++
  374. } else {
  375. toRemove = append(toRemove, removeLabel)
  376. removeIndex++
  377. }
  378. }
  379. toAdd = append(toAdd, labels[addIndex:]...)
  380. toRemove = append(toRemove, issue.Labels[removeIndex:]...)
  381. if len(toAdd) > 0 {
  382. if err = newIssueLabels(ctx, issue, toAdd, doer); err != nil {
  383. return fmt.Errorf("addLabels: %w", err)
  384. }
  385. }
  386. for _, l := range toRemove {
  387. if err = deleteIssueLabel(ctx, issue, l, doer); err != nil {
  388. return fmt.Errorf("removeLabel: %w", err)
  389. }
  390. }
  391. issue.Labels = nil
  392. return issue.LoadLabels(ctx)
  393. })
  394. }