You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

pull_list.go 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // Copyright 2019 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. access_model "code.gitea.io/gitea/models/perm/access"
  9. "code.gitea.io/gitea/models/unit"
  10. user_model "code.gitea.io/gitea/models/user"
  11. "code.gitea.io/gitea/modules/base"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/util"
  14. "xorm.io/xorm"
  15. )
  16. // PullRequestsOptions holds the options for PRs
  17. type PullRequestsOptions struct {
  18. db.ListOptions
  19. State string
  20. SortType string
  21. Labels []string
  22. MilestoneID int64
  23. }
  24. func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) (*xorm.Session, error) {
  25. sess := db.GetEngine(ctx).Where("pull_request.base_repo_id=?", baseRepoID)
  26. sess.Join("INNER", "issue", "pull_request.issue_id = issue.id")
  27. switch opts.State {
  28. case "closed", "open":
  29. sess.And("issue.is_closed=?", opts.State == "closed")
  30. }
  31. if labelIDs, err := base.StringsToInt64s(opts.Labels); err != nil {
  32. return nil, err
  33. } else if len(labelIDs) > 0 {
  34. sess.Join("INNER", "issue_label", "issue.id = issue_label.issue_id").
  35. In("issue_label.label_id", labelIDs)
  36. }
  37. if opts.MilestoneID > 0 {
  38. sess.And("issue.milestone_id=?", opts.MilestoneID)
  39. }
  40. return sess, nil
  41. }
  42. // GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
  43. func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) {
  44. prs := make([]*PullRequest, 0, 2)
  45. sess := db.GetEngine(ctx).
  46. Join("INNER", "issue", "issue.id = pull_request.issue_id").
  47. Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ? AND flow = ?", repoID, branch, false, false, PullRequestFlowGithub)
  48. return prs, sess.Find(&prs)
  49. }
  50. // CanMaintainerWriteToBranch check whether user is a maintainer and could write to the branch
  51. func CanMaintainerWriteToBranch(ctx context.Context, p access_model.Permission, branch string, user *user_model.User) bool {
  52. if p.CanWrite(unit.TypeCode) {
  53. return true
  54. }
  55. if len(p.Units) < 1 {
  56. return false
  57. }
  58. prs, err := GetUnmergedPullRequestsByHeadInfo(ctx, p.Units[0].RepoID, branch)
  59. if err != nil {
  60. return false
  61. }
  62. for _, pr := range prs {
  63. if pr.AllowMaintainerEdit {
  64. err = pr.LoadBaseRepo(ctx)
  65. if err != nil {
  66. continue
  67. }
  68. prPerm, err := access_model.GetUserRepoPermission(ctx, pr.BaseRepo, user)
  69. if err != nil {
  70. continue
  71. }
  72. if prPerm.CanWrite(unit.TypeCode) {
  73. return true
  74. }
  75. }
  76. }
  77. return false
  78. }
  79. // HasUnmergedPullRequestsByHeadInfo checks if there are open and not merged pull request
  80. // by given head information (repo and branch)
  81. func HasUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) (bool, error) {
  82. return db.GetEngine(ctx).
  83. Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ? AND flow = ?",
  84. repoID, branch, false, false, PullRequestFlowGithub).
  85. Join("INNER", "issue", "issue.id = pull_request.issue_id").
  86. Exist(&PullRequest{})
  87. }
  88. // GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged
  89. // by given base information (repo and branch).
  90. func GetUnmergedPullRequestsByBaseInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) {
  91. prs := make([]*PullRequest, 0, 2)
  92. return prs, db.GetEngine(ctx).
  93. Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
  94. repoID, branch, false, false).
  95. OrderBy("issue.updated_unix DESC").
  96. Join("INNER", "issue", "issue.id=pull_request.issue_id").
  97. Find(&prs)
  98. }
  99. // GetPullRequestIDsByCheckStatus returns all pull requests according the special checking status.
  100. func GetPullRequestIDsByCheckStatus(ctx context.Context, status PullRequestStatus) ([]int64, error) {
  101. prs := make([]int64, 0, 10)
  102. return prs, db.GetEngine(ctx).Table("pull_request").
  103. Where("status=?", status).
  104. Cols("pull_request.id").
  105. Find(&prs)
  106. }
  107. // PullRequests returns all pull requests for a base Repo by the given conditions
  108. func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) ([]*PullRequest, int64, error) {
  109. if opts.Page <= 0 {
  110. opts.Page = 1
  111. }
  112. countSession, err := listPullRequestStatement(ctx, baseRepoID, opts)
  113. if err != nil {
  114. log.Error("listPullRequestStatement: %v", err)
  115. return nil, 0, err
  116. }
  117. maxResults, err := countSession.Count(new(PullRequest))
  118. if err != nil {
  119. log.Error("Count PRs: %v", err)
  120. return nil, maxResults, err
  121. }
  122. findSession, err := listPullRequestStatement(ctx, baseRepoID, opts)
  123. applySorts(findSession, opts.SortType, 0)
  124. if err != nil {
  125. log.Error("listPullRequestStatement: %v", err)
  126. return nil, maxResults, err
  127. }
  128. findSession = db.SetSessionPagination(findSession, opts)
  129. prs := make([]*PullRequest, 0, opts.PageSize)
  130. return prs, maxResults, findSession.Find(&prs)
  131. }
  132. // PullRequestList defines a list of pull requests
  133. type PullRequestList []*PullRequest
  134. func (prs PullRequestList) LoadAttributes(ctx context.Context) error {
  135. if len(prs) == 0 {
  136. return nil
  137. }
  138. // Load issues.
  139. issueIDs := prs.GetIssueIDs()
  140. issues := make([]*Issue, 0, len(issueIDs))
  141. if err := db.GetEngine(ctx).
  142. Where("id > 0").
  143. In("id", issueIDs).
  144. Find(&issues); err != nil {
  145. return fmt.Errorf("find issues: %w", err)
  146. }
  147. set := make(map[int64]*Issue)
  148. for i := range issues {
  149. set[issues[i].ID] = issues[i]
  150. }
  151. for _, pr := range prs {
  152. pr.Issue = set[pr.IssueID]
  153. /*
  154. Old code:
  155. pr.Issue.PullRequest = pr // panic here means issueIDs and prs are not in sync
  156. It's worth panic because it's almost impossible to happen under normal use.
  157. But in integration testing, an asynchronous task could read a database that has been reset.
  158. So returning an error would make more sense, let the caller has a choice to ignore it.
  159. */
  160. if pr.Issue == nil {
  161. return fmt.Errorf("issues and prs may be not in sync: cannot find issue %v for pr %v: %w", pr.IssueID, pr.ID, util.ErrNotExist)
  162. }
  163. pr.Issue.PullRequest = pr
  164. }
  165. return nil
  166. }
  167. // GetIssueIDs returns all issue ids
  168. func (prs PullRequestList) GetIssueIDs() []int64 {
  169. issueIDs := make([]int64, 0, len(prs))
  170. for i := range prs {
  171. issueIDs = append(issueIDs, prs[i].IssueID)
  172. }
  173. return issueIDs
  174. }
  175. // HasMergedPullRequestInRepo returns whether the user(poster) has merged pull-request in the repo
  176. func HasMergedPullRequestInRepo(ctx context.Context, repoID, posterID int64) (bool, error) {
  177. return db.GetEngine(ctx).
  178. Join("INNER", "pull_request", "pull_request.issue_id = issue.id").
  179. Where("repo_id=?", repoID).
  180. And("poster_id=?", posterID).
  181. And("is_pull=?", true).
  182. And("pull_request.has_merged=?", true).
  183. Select("issue.id").
  184. Limit(1).
  185. Get(new(Issue))
  186. }
  187. // GetPullRequestByIssueIDs returns all pull requests by issue ids
  188. func GetPullRequestByIssueIDs(ctx context.Context, issueIDs []int64) (PullRequestList, error) {
  189. prs := make([]*PullRequest, 0, len(issueIDs))
  190. return prs, db.GetEngine(ctx).
  191. Where("issue_id > 0").
  192. In("issue_id", issueIDs).
  193. Find(&prs)
  194. }