gitea源码

pull.go 29KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package issues
  5. import (
  6. "context"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "regexp"
  11. "strings"
  12. "code.gitea.io/gitea/models/db"
  13. git_model "code.gitea.io/gitea/models/git"
  14. org_model "code.gitea.io/gitea/models/organization"
  15. pull_model "code.gitea.io/gitea/models/pull"
  16. repo_model "code.gitea.io/gitea/models/repo"
  17. user_model "code.gitea.io/gitea/models/user"
  18. "code.gitea.io/gitea/modules/git"
  19. "code.gitea.io/gitea/modules/log"
  20. "code.gitea.io/gitea/modules/setting"
  21. "code.gitea.io/gitea/modules/timeutil"
  22. "code.gitea.io/gitea/modules/util"
  23. "xorm.io/builder"
  24. )
  25. var ErrMustCollaborator = util.NewPermissionDeniedErrorf("user must be a collaborator")
  26. const reviewedBy = "Reviewed-by: "
  27. // ErrPullRequestNotExist represents a "PullRequestNotExist" kind of error.
  28. type ErrPullRequestNotExist struct {
  29. ID int64
  30. IssueID int64
  31. HeadRepoID int64
  32. BaseRepoID int64
  33. HeadBranch string
  34. BaseBranch string
  35. }
  36. // IsErrPullRequestNotExist checks if an error is a ErrPullRequestNotExist.
  37. func IsErrPullRequestNotExist(err error) bool {
  38. _, ok := err.(ErrPullRequestNotExist)
  39. return ok
  40. }
  41. func (err ErrPullRequestNotExist) Error() string {
  42. return fmt.Sprintf("pull request does not exist [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
  43. err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch)
  44. }
  45. func (err ErrPullRequestNotExist) Unwrap() error {
  46. return util.ErrNotExist
  47. }
  48. // ErrPullRequestAlreadyExists represents a "PullRequestAlreadyExists"-error
  49. type ErrPullRequestAlreadyExists struct {
  50. ID int64
  51. IssueID int64
  52. HeadRepoID int64
  53. BaseRepoID int64
  54. HeadBranch string
  55. BaseBranch string
  56. }
  57. // IsErrPullRequestAlreadyExists checks if an error is a ErrPullRequestAlreadyExists.
  58. func IsErrPullRequestAlreadyExists(err error) bool {
  59. _, ok := err.(ErrPullRequestAlreadyExists)
  60. return ok
  61. }
  62. // Error does pretty-printing :D
  63. func (err ErrPullRequestAlreadyExists) Error() string {
  64. return fmt.Sprintf("pull request already exists for these targets [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
  65. err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch)
  66. }
  67. func (err ErrPullRequestAlreadyExists) Unwrap() error {
  68. return util.ErrAlreadyExist
  69. }
  70. // PullRequestType defines pull request type
  71. type PullRequestType int
  72. // Enumerate all the pull request types
  73. const (
  74. PullRequestGitea PullRequestType = iota
  75. PullRequestGit
  76. )
  77. // PullRequestStatus defines pull request status
  78. type PullRequestStatus int
  79. // Enumerate all the pull request status
  80. const (
  81. PullRequestStatusConflict PullRequestStatus = iota
  82. PullRequestStatusChecking
  83. PullRequestStatusMergeable
  84. PullRequestStatusManuallyMerged
  85. PullRequestStatusError
  86. PullRequestStatusEmpty
  87. PullRequestStatusAncestor
  88. )
  89. // PullRequestFlow the flow of pull request
  90. type PullRequestFlow int
  91. const (
  92. // PullRequestFlowGithub github flow from head branch to base branch
  93. PullRequestFlowGithub PullRequestFlow = iota
  94. // PullRequestFlowAGit Agit flow pull request, head branch is not exist
  95. PullRequestFlowAGit
  96. )
  97. // PullRequest represents relation between pull request and repositories.
  98. type PullRequest struct {
  99. ID int64 `xorm:"pk autoincr"`
  100. Type PullRequestType
  101. Status PullRequestStatus
  102. ConflictedFiles []string `xorm:"TEXT JSON"`
  103. CommitsAhead int
  104. CommitsBehind int
  105. ChangedProtectedFiles []string `xorm:"TEXT JSON"`
  106. IssueID int64 `xorm:"INDEX"`
  107. Issue *Issue `xorm:"-"`
  108. Index int64
  109. RequestedReviewers []*user_model.User `xorm:"-"`
  110. RequestedReviewersTeams []*org_model.Team `xorm:"-"`
  111. isRequestedReviewersLoaded bool `xorm:"-"`
  112. HeadRepoID int64 `xorm:"INDEX"`
  113. HeadRepo *repo_model.Repository `xorm:"-"`
  114. BaseRepoID int64 `xorm:"INDEX"`
  115. BaseRepo *repo_model.Repository `xorm:"-"`
  116. HeadBranch string
  117. HeadCommitID string `xorm:"-"`
  118. BaseBranch string
  119. MergeBase string `xorm:"VARCHAR(64)"`
  120. AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"`
  121. HasMerged bool `xorm:"INDEX"`
  122. MergedCommitID string `xorm:"VARCHAR(64)"`
  123. MergerID int64 `xorm:"INDEX"`
  124. Merger *user_model.User `xorm:"-"`
  125. MergedUnix timeutil.TimeStamp `xorm:"updated INDEX"`
  126. isHeadRepoLoaded bool `xorm:"-"`
  127. Flow PullRequestFlow `xorm:"NOT NULL DEFAULT 0"`
  128. }
  129. func init() {
  130. db.RegisterModel(new(PullRequest))
  131. }
  132. // DeletePullsByBaseRepoID deletes all pull requests by the base repository ID
  133. func DeletePullsByBaseRepoID(ctx context.Context, repoID int64) error {
  134. deleteCond := builder.Select("id").From("pull_request").Where(builder.Eq{"pull_request.base_repo_id": repoID})
  135. // Delete scheduled auto merges
  136. if _, err := db.GetEngine(ctx).In("pull_id", deleteCond).
  137. Delete(&pull_model.AutoMerge{}); err != nil {
  138. return err
  139. }
  140. // Delete review states
  141. if _, err := db.GetEngine(ctx).In("pull_id", deleteCond).
  142. Delete(&pull_model.ReviewState{}); err != nil {
  143. return err
  144. }
  145. _, err := db.DeleteByBean(ctx, &PullRequest{BaseRepoID: repoID})
  146. return err
  147. }
  148. func (pr *PullRequest) String() string {
  149. if pr == nil {
  150. return "<PullRequest nil>"
  151. }
  152. s := new(strings.Builder)
  153. fmt.Fprintf(s, "<PullRequest [%d]", pr.ID)
  154. if pr.BaseRepo != nil {
  155. fmt.Fprintf(s, "%s#%d[%s...", pr.BaseRepo.FullName(), pr.Index, pr.BaseBranch)
  156. } else {
  157. fmt.Fprintf(s, "Repo[%d]#%d[%s...", pr.BaseRepoID, pr.Index, pr.BaseBranch)
  158. }
  159. if pr.HeadRepoID == pr.BaseRepoID {
  160. fmt.Fprintf(s, "%s]", pr.HeadBranch)
  161. } else if pr.HeadRepo != nil {
  162. fmt.Fprintf(s, "%s:%s]", pr.HeadRepo.FullName(), pr.HeadBranch)
  163. } else {
  164. fmt.Fprintf(s, "Repo[%d]:%s]", pr.HeadRepoID, pr.HeadBranch)
  165. }
  166. s.WriteByte('>')
  167. return s.String()
  168. }
  169. // MustHeadUserName returns the HeadRepo's username if failed return blank
  170. func (pr *PullRequest) MustHeadUserName(ctx context.Context) string {
  171. if err := pr.LoadHeadRepo(ctx); err != nil {
  172. if !repo_model.IsErrRepoNotExist(err) {
  173. log.Error("LoadHeadRepo: %v", err)
  174. } else {
  175. log.Warn("LoadHeadRepo %d but repository does not exist: %v", pr.HeadRepoID, err)
  176. }
  177. return ""
  178. }
  179. if pr.HeadRepo == nil {
  180. return ""
  181. }
  182. return pr.HeadRepo.OwnerName
  183. }
  184. // LoadAttributes loads pull request attributes from database
  185. // Note: don't try to get Issue because will end up recursive querying.
  186. func (pr *PullRequest) LoadAttributes(ctx context.Context) (err error) {
  187. if pr.HasMerged && pr.Merger == nil {
  188. pr.Merger, err = user_model.GetUserByID(ctx, pr.MergerID)
  189. if user_model.IsErrUserNotExist(err) {
  190. pr.MergerID = user_model.GhostUserID
  191. pr.Merger = user_model.NewGhostUser()
  192. } else if err != nil {
  193. return fmt.Errorf("getUserByID [%d]: %w", pr.MergerID, err)
  194. }
  195. }
  196. return nil
  197. }
  198. func (pr *PullRequest) IsAgitFlow() bool {
  199. return pr.Flow == PullRequestFlowAGit
  200. }
  201. // LoadHeadRepo loads the head repository, pr.HeadRepo will remain nil if it does not exist
  202. // and thus ErrRepoNotExist will never be returned
  203. func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) {
  204. if !pr.isHeadRepoLoaded && pr.HeadRepo == nil && pr.HeadRepoID > 0 {
  205. if pr.HeadRepoID == pr.BaseRepoID {
  206. if pr.BaseRepo != nil {
  207. pr.HeadRepo = pr.BaseRepo
  208. return nil
  209. } else if pr.Issue != nil && pr.Issue.Repo != nil {
  210. pr.HeadRepo = pr.Issue.Repo
  211. return nil
  212. }
  213. }
  214. pr.HeadRepo, err = repo_model.GetRepositoryByID(ctx, pr.HeadRepoID)
  215. if err != nil && !repo_model.IsErrRepoNotExist(err) { // Head repo maybe deleted, but it should still work
  216. return fmt.Errorf("pr[%d].LoadHeadRepo[%d]: %w", pr.ID, pr.HeadRepoID, err)
  217. }
  218. pr.isHeadRepoLoaded = true
  219. }
  220. return nil
  221. }
  222. // LoadRequestedReviewers loads the requested reviewers.
  223. func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
  224. if pr.isRequestedReviewersLoaded || len(pr.RequestedReviewers) > 0 {
  225. return nil
  226. }
  227. reviews, _, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
  228. if err != nil {
  229. return err
  230. }
  231. if err = reviews.LoadReviewers(ctx); err != nil {
  232. return err
  233. }
  234. pr.isRequestedReviewersLoaded = true
  235. for _, review := range reviews {
  236. if review.ReviewerID != 0 {
  237. pr.RequestedReviewers = append(pr.RequestedReviewers, review.Reviewer)
  238. }
  239. }
  240. return nil
  241. }
  242. // LoadRequestedReviewersTeams loads the requested reviewers teams.
  243. func (pr *PullRequest) LoadRequestedReviewersTeams(ctx context.Context) error {
  244. reviews, _, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
  245. if err != nil {
  246. return err
  247. }
  248. if err = reviews.LoadReviewersTeams(ctx); err != nil {
  249. return err
  250. }
  251. for _, review := range reviews {
  252. if review.ReviewerTeamID != 0 {
  253. pr.RequestedReviewersTeams = append(pr.RequestedReviewersTeams, review.ReviewerTeam)
  254. }
  255. }
  256. return nil
  257. }
  258. // LoadBaseRepo loads the target repository. ErrRepoNotExist may be returned.
  259. func (pr *PullRequest) LoadBaseRepo(ctx context.Context) (err error) {
  260. if pr.BaseRepo != nil {
  261. return nil
  262. }
  263. if pr.HeadRepoID == pr.BaseRepoID && pr.HeadRepo != nil {
  264. pr.BaseRepo = pr.HeadRepo
  265. return nil
  266. }
  267. if pr.Issue != nil && pr.Issue.Repo != nil {
  268. pr.BaseRepo = pr.Issue.Repo
  269. return nil
  270. }
  271. pr.BaseRepo, err = repo_model.GetRepositoryByID(ctx, pr.BaseRepoID)
  272. if err != nil {
  273. return fmt.Errorf("pr[%d].LoadBaseRepo[%d]: %w", pr.ID, pr.BaseRepoID, err)
  274. }
  275. return nil
  276. }
  277. // LoadIssue loads issue information from database
  278. func (pr *PullRequest) LoadIssue(ctx context.Context) (err error) {
  279. if pr.Issue != nil {
  280. return nil
  281. }
  282. pr.Issue, err = GetIssueByID(ctx, pr.IssueID)
  283. if err == nil {
  284. pr.Issue.PullRequest = pr
  285. }
  286. return err
  287. }
  288. // ReviewCount represents a count of Reviews
  289. type ReviewCount struct {
  290. IssueID int64
  291. Type ReviewType
  292. Count int64
  293. }
  294. // GetApprovalCounts returns the approval counts by type
  295. // FIXME: Only returns official counts due to double counting of non-official counts
  296. func (pr *PullRequest) GetApprovalCounts(ctx context.Context) ([]*ReviewCount, error) {
  297. rCounts := make([]*ReviewCount, 0, 6)
  298. sess := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID)
  299. return rCounts, sess.Select("issue_id, type, count(id) as `count`").
  300. Where(builder.Eq{"official": true, "dismissed": false}).
  301. GroupBy("issue_id, type").
  302. Table("review").
  303. Find(&rCounts)
  304. }
  305. // GetApprovers returns the approvers of the pull request
  306. func (pr *PullRequest) GetApprovers(ctx context.Context) string {
  307. stringBuilder := strings.Builder{}
  308. if err := pr.getReviewedByLines(ctx, &stringBuilder); err != nil {
  309. log.Error("Unable to getReviewedByLines: Error: %v", err)
  310. return ""
  311. }
  312. return stringBuilder.String()
  313. }
  314. func (pr *PullRequest) getReviewedByLines(ctx context.Context, writer io.Writer) error {
  315. maxReviewers := setting.Repository.PullRequest.DefaultMergeMessageMaxApprovers
  316. if maxReviewers == 0 {
  317. return nil
  318. }
  319. // Note: This doesn't page as we only expect a very limited number of reviews
  320. reviews, err := FindLatestReviews(ctx, FindReviewOptions{
  321. Types: []ReviewType{ReviewTypeApprove},
  322. IssueID: pr.IssueID,
  323. OfficialOnly: setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly,
  324. })
  325. if err != nil {
  326. log.Error("Unable to FindReviews for PR ID %d: %v", pr.ID, err)
  327. return err
  328. }
  329. reviewersWritten := 0
  330. for _, review := range reviews {
  331. if maxReviewers > 0 && reviewersWritten > maxReviewers {
  332. break
  333. }
  334. if err := review.LoadReviewer(ctx); err != nil && !user_model.IsErrUserNotExist(err) {
  335. log.Error("Unable to LoadReviewer[%d] for PR ID %d : %v", review.ReviewerID, pr.ID, err)
  336. return err
  337. } else if review.Reviewer == nil {
  338. continue
  339. }
  340. if _, err := writer.Write([]byte(reviewedBy)); err != nil {
  341. return err
  342. }
  343. if _, err := writer.Write([]byte(review.Reviewer.NewGitSig().String())); err != nil {
  344. return err
  345. }
  346. if _, err := writer.Write([]byte{'\n'}); err != nil {
  347. return err
  348. }
  349. reviewersWritten++
  350. }
  351. return nil
  352. }
  353. // GetGitHeadRefName returns git ref for hidden pull request branch
  354. func (pr *PullRequest) GetGitHeadRefName() string {
  355. return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index)
  356. }
  357. func (pr *PullRequest) GetGitHeadBranchRefName() string {
  358. return fmt.Sprintf("%s%s", git.BranchPrefix, pr.HeadBranch)
  359. }
  360. // GetReviewCommentsCount returns the number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
  361. func (pr *PullRequest) GetReviewCommentsCount(ctx context.Context) int {
  362. opts := FindCommentsOptions{
  363. Type: CommentTypeReview,
  364. IssueID: pr.IssueID,
  365. }
  366. conds := opts.ToConds()
  367. count, err := db.GetEngine(ctx).Where(conds).Count(new(Comment))
  368. if err != nil {
  369. return 0
  370. }
  371. return int(count)
  372. }
  373. // IsChecking returns true if this pull request is still checking conflict.
  374. func (pr *PullRequest) IsChecking() bool {
  375. return pr.Status == PullRequestStatusChecking
  376. }
  377. // CanAutoMerge returns true if this pull request can be merged automatically.
  378. func (pr *PullRequest) CanAutoMerge() bool {
  379. return pr.Status == PullRequestStatusMergeable
  380. }
  381. // IsEmpty returns true if this pull request is empty.
  382. func (pr *PullRequest) IsEmpty() bool {
  383. return pr.Status == PullRequestStatusEmpty
  384. }
  385. // IsAncestor returns true if the Head Commit of this PR is an ancestor of the Base Commit
  386. func (pr *PullRequest) IsAncestor() bool {
  387. return pr.Status == PullRequestStatusAncestor
  388. }
  389. // IsFromFork return true if this PR is from a fork.
  390. func (pr *PullRequest) IsFromFork() bool {
  391. return pr.HeadRepoID != pr.BaseRepoID
  392. }
  393. // NewPullRequest creates new pull request with labels for repository.
  394. func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
  395. return db.WithTx(ctx, func(ctx context.Context) error {
  396. idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
  397. if err != nil {
  398. return fmt.Errorf("generate pull request index failed: %w", err)
  399. }
  400. issue.Index = idx
  401. issue.Title = util.EllipsisDisplayString(issue.Title, 255)
  402. if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
  403. Repo: repo,
  404. Issue: issue,
  405. LabelIDs: labelIDs,
  406. Attachments: uuids,
  407. IsPull: true,
  408. }); err != nil {
  409. if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
  410. return err
  411. }
  412. return fmt.Errorf("newIssue: %w", err)
  413. }
  414. pr.Index = issue.Index
  415. pr.BaseRepo = repo
  416. pr.IssueID = issue.ID
  417. if err = db.Insert(ctx, pr); err != nil {
  418. return fmt.Errorf("insert pull repo: %w", err)
  419. }
  420. return nil
  421. })
  422. }
  423. // ErrUserMustCollaborator represents an error that the user must be a collaborator to a given repo.
  424. type ErrUserMustCollaborator struct {
  425. UserID int64
  426. RepoName string
  427. }
  428. // GetUnmergedPullRequest returns a pull request that is open and has not been merged
  429. // by given head/base and repo/branch.
  430. func GetUnmergedPullRequest(ctx context.Context, headRepoID, baseRepoID int64, headBranch, baseBranch string, flow PullRequestFlow) (*PullRequest, error) {
  431. pr := new(PullRequest)
  432. has, err := db.GetEngine(ctx).
  433. Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND flow = ? AND issue.is_closed=?",
  434. headRepoID, headBranch, baseRepoID, baseBranch, false, flow, false).
  435. Join("INNER", "issue", "issue.id=pull_request.issue_id").
  436. Get(pr)
  437. if err != nil {
  438. return nil, err
  439. } else if !has {
  440. return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
  441. }
  442. return pr, nil
  443. }
  444. // GetLatestPullRequestByHeadInfo returns the latest pull request (regardless of its status)
  445. // by given head information (repo and branch).
  446. func GetLatestPullRequestByHeadInfo(ctx context.Context, repoID int64, branch string) (*PullRequest, error) {
  447. pr := new(PullRequest)
  448. has, err := db.GetEngine(ctx).
  449. Where("head_repo_id = ? AND head_branch = ? AND flow = ?", repoID, branch, PullRequestFlowGithub).
  450. OrderBy("id DESC").
  451. Get(pr)
  452. if !has {
  453. return nil, err
  454. }
  455. return pr, err
  456. }
  457. // GetPullRequestByIndex returns a pull request by the given index
  458. func GetPullRequestByIndex(ctx context.Context, repoID, index int64) (*PullRequest, error) {
  459. if index < 1 {
  460. return nil, ErrPullRequestNotExist{}
  461. }
  462. pr := &PullRequest{
  463. BaseRepoID: repoID,
  464. Index: index,
  465. }
  466. has, err := db.GetEngine(ctx).Get(pr)
  467. if err != nil {
  468. return nil, err
  469. } else if !has {
  470. return nil, ErrPullRequestNotExist{0, 0, 0, repoID, "", ""}
  471. }
  472. if err = pr.LoadAttributes(ctx); err != nil {
  473. return nil, err
  474. }
  475. if err = pr.LoadIssue(ctx); err != nil {
  476. return nil, err
  477. }
  478. return pr, nil
  479. }
  480. // GetPullRequestByID returns a pull request by given ID.
  481. func GetPullRequestByID(ctx context.Context, id int64) (*PullRequest, error) {
  482. pr := new(PullRequest)
  483. has, err := db.GetEngine(ctx).ID(id).Get(pr)
  484. if err != nil {
  485. return nil, err
  486. } else if !has {
  487. return nil, ErrPullRequestNotExist{id, 0, 0, 0, "", ""}
  488. }
  489. return pr, pr.LoadAttributes(ctx)
  490. }
  491. // GetPullRequestByIssueIDWithNoAttributes returns pull request with no attributes loaded by given issue ID.
  492. func GetPullRequestByIssueIDWithNoAttributes(ctx context.Context, issueID int64) (*PullRequest, error) {
  493. var pr PullRequest
  494. has, err := db.GetEngine(ctx).Where("issue_id = ?", issueID).Get(&pr)
  495. if err != nil {
  496. return nil, err
  497. }
  498. if !has {
  499. return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""}
  500. }
  501. return &pr, nil
  502. }
  503. // GetPullRequestByIssueID returns pull request by given issue ID.
  504. func GetPullRequestByIssueID(ctx context.Context, issueID int64) (*PullRequest, error) {
  505. pr, exist, err := db.Get[PullRequest](ctx, builder.Eq{"issue_id": issueID})
  506. if err != nil {
  507. return nil, err
  508. } else if !exist {
  509. return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""}
  510. }
  511. return pr, pr.LoadAttributes(ctx)
  512. }
  513. // GetPullRequestByBaseHeadInfo returns the pull request by given base and head
  514. func GetPullRequestByBaseHeadInfo(ctx context.Context, baseID, headID int64, base, head string) (*PullRequest, error) {
  515. pr := &PullRequest{}
  516. sess := db.GetEngine(ctx).
  517. Join("INNER", "issue", "issue.id = pull_request.issue_id").
  518. Where("base_repo_id = ? AND base_branch = ? AND head_repo_id = ? AND head_branch = ?", baseID, base, headID, head)
  519. has, err := sess.Get(pr)
  520. if err != nil {
  521. return nil, err
  522. }
  523. if !has {
  524. return nil, ErrPullRequestNotExist{
  525. HeadRepoID: headID,
  526. BaseRepoID: baseID,
  527. HeadBranch: head,
  528. BaseBranch: base,
  529. }
  530. }
  531. if err = pr.LoadAttributes(ctx); err != nil {
  532. return nil, err
  533. }
  534. if err = pr.LoadIssue(ctx); err != nil {
  535. return nil, err
  536. }
  537. return pr, nil
  538. }
  539. // GetAllUnmergedAgitPullRequestByPoster get all unmerged agit flow pull request
  540. // By poster id.
  541. func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*PullRequest, error) {
  542. pulls := make([]*PullRequest, 0, 10)
  543. err := db.GetEngine(ctx).
  544. Where("has_merged=? AND flow = ? AND issue.is_closed=? AND issue.poster_id=?",
  545. false, PullRequestFlowAGit, false, uid).
  546. Join("INNER", "issue", "issue.id=pull_request.issue_id").
  547. Find(&pulls)
  548. return pulls, err
  549. }
  550. // UpdateCols updates specific fields of pull request.
  551. func (pr *PullRequest) UpdateCols(ctx context.Context, cols ...string) error {
  552. _, err := db.GetEngine(ctx).ID(pr.ID).Cols(cols...).Update(pr)
  553. return err
  554. }
  555. // UpdateColsIfNotMerged updates specific fields of a pull request if it has not been merged
  556. func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string) error {
  557. _, err := db.GetEngine(ctx).Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr)
  558. return err
  559. }
  560. // IsWorkInProgress determine if the Pull Request is a Work In Progress by its title
  561. // Issue must be set before this method can be called.
  562. func (pr *PullRequest) IsWorkInProgress(ctx context.Context) bool {
  563. if err := pr.LoadIssue(ctx); err != nil {
  564. log.Error("LoadIssue: %v", err)
  565. return false
  566. }
  567. return HasWorkInProgressPrefix(pr.Issue.Title)
  568. }
  569. // HasWorkInProgressPrefix determines if the given PR title has a Work In Progress prefix
  570. func HasWorkInProgressPrefix(title string) bool {
  571. for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
  572. if strings.HasPrefix(strings.ToUpper(title), strings.ToUpper(prefix)) {
  573. return true
  574. }
  575. }
  576. return false
  577. }
  578. // IsFilesConflicted determines if the Pull Request has changes conflicting with the target branch.
  579. func (pr *PullRequest) IsFilesConflicted() bool {
  580. return len(pr.ConflictedFiles) > 0
  581. }
  582. // GetWorkInProgressPrefix returns the prefix used to mark the pull request as a work in progress.
  583. // It returns an empty string when none were found
  584. func (pr *PullRequest) GetWorkInProgressPrefix(ctx context.Context) string {
  585. if err := pr.LoadIssue(ctx); err != nil {
  586. log.Error("LoadIssue: %v", err)
  587. return ""
  588. }
  589. for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
  590. if strings.HasPrefix(strings.ToUpper(pr.Issue.Title), strings.ToUpper(prefix)) {
  591. return pr.Issue.Title[0:len(prefix)]
  592. }
  593. }
  594. return ""
  595. }
  596. // UpdateCommitDivergence update Divergence of a pull request
  597. func (pr *PullRequest) UpdateCommitDivergence(ctx context.Context, ahead, behind int) error {
  598. if pr.ID == 0 {
  599. return errors.New("pull ID is 0")
  600. }
  601. pr.CommitsAhead = ahead
  602. pr.CommitsBehind = behind
  603. _, err := db.GetEngine(ctx).ID(pr.ID).Cols("commits_ahead", "commits_behind").Update(pr)
  604. return err
  605. }
  606. // IsSameRepo returns true if base repo and head repo is the same
  607. func (pr *PullRequest) IsSameRepo() bool {
  608. return pr.BaseRepoID == pr.HeadRepoID
  609. }
  610. // GetBaseBranchLink returns the relative URL of the base branch
  611. func (pr *PullRequest) GetBaseBranchLink(ctx context.Context) string {
  612. if err := pr.LoadBaseRepo(ctx); err != nil {
  613. log.Error("LoadBaseRepo: %v", err)
  614. return ""
  615. }
  616. if pr.BaseRepo == nil {
  617. return ""
  618. }
  619. return pr.BaseRepo.Link() + "/src/branch/" + util.PathEscapeSegments(pr.BaseBranch)
  620. }
  621. // GetHeadBranchLink returns the relative URL of the head branch
  622. func (pr *PullRequest) GetHeadBranchLink(ctx context.Context) string {
  623. if pr.Flow == PullRequestFlowAGit {
  624. return ""
  625. }
  626. if err := pr.LoadHeadRepo(ctx); err != nil {
  627. log.Error("LoadHeadRepo: %v", err)
  628. return ""
  629. }
  630. if pr.HeadRepo == nil {
  631. return ""
  632. }
  633. return pr.HeadRepo.Link() + "/src/branch/" + util.PathEscapeSegments(pr.HeadBranch)
  634. }
  635. // UpdateAllowEdits update if PR can be edited from maintainers
  636. func UpdateAllowEdits(ctx context.Context, pr *PullRequest) error {
  637. if _, err := db.GetEngine(ctx).ID(pr.ID).Cols("allow_maintainer_edit").Update(pr); err != nil {
  638. return err
  639. }
  640. return nil
  641. }
  642. // Mergeable returns if the pullrequest is mergeable.
  643. func (pr *PullRequest) Mergeable(ctx context.Context) bool {
  644. // If a pull request isn't mergeable if it's:
  645. // - Being conflict checked.
  646. // - Has a conflict.
  647. // - Received a error while being conflict checked.
  648. // - Is a work-in-progress pull request.
  649. return pr.Status != PullRequestStatusChecking && pr.Status != PullRequestStatusConflict &&
  650. pr.Status != PullRequestStatusError && !pr.IsWorkInProgress(ctx)
  651. }
  652. // HasEnoughApprovals returns true if pr has enough granted approvals.
  653. func HasEnoughApprovals(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
  654. if protectBranch.RequiredApprovals == 0 {
  655. return true
  656. }
  657. return GetGrantedApprovalsCount(ctx, protectBranch, pr) >= protectBranch.RequiredApprovals
  658. }
  659. // GetGrantedApprovalsCount returns the number of granted approvals for pr. A granted approval must be authored by a user in an approval whitelist.
  660. func GetGrantedApprovalsCount(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) int64 {
  661. sess := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
  662. And("type = ?", ReviewTypeApprove).
  663. And("official = ?", true).
  664. And("dismissed = ?", false)
  665. if protectBranch.IgnoreStaleApprovals {
  666. sess = sess.And("stale = ?", false)
  667. }
  668. approvals, err := sess.Count(new(Review))
  669. if err != nil {
  670. log.Error("GetGrantedApprovalsCount: %v", err)
  671. return 0
  672. }
  673. return approvals
  674. }
  675. // MergeBlockedByRejectedReview returns true if merge is blocked by rejected reviews
  676. func MergeBlockedByRejectedReview(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
  677. if !protectBranch.BlockOnRejectedReviews {
  678. return false
  679. }
  680. rejectExist, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
  681. And("type = ?", ReviewTypeReject).
  682. And("official = ?", true).
  683. And("dismissed = ?", false).
  684. Exist(new(Review))
  685. if err != nil {
  686. log.Error("MergeBlockedByRejectedReview: %v", err)
  687. return true
  688. }
  689. return rejectExist
  690. }
  691. // MergeBlockedByOfficialReviewRequests block merge because of some review request to official reviewer
  692. // of from official review
  693. func MergeBlockedByOfficialReviewRequests(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
  694. if !protectBranch.BlockOnOfficialReviewRequests {
  695. return false
  696. }
  697. has, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
  698. And("type = ?", ReviewTypeRequest).
  699. And("official = ?", true).
  700. Exist(new(Review))
  701. if err != nil {
  702. log.Error("MergeBlockedByOfficialReviewRequests: %v", err)
  703. return true
  704. }
  705. return has
  706. }
  707. // MergeBlockedByOutdatedBranch returns true if merge is blocked by an outdated head branch
  708. func MergeBlockedByOutdatedBranch(protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
  709. return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0
  710. }
  711. // GetCodeOwnersFromContent returns the code owners configuration
  712. // Return empty slice if files missing
  713. // Return warning messages on parsing errors
  714. // We're trying to do the best we can when parsing a file.
  715. // Invalid lines are skipped. Non-existent users and teams too.
  716. func GetCodeOwnersFromContent(ctx context.Context, data string) ([]*CodeOwnerRule, []string) {
  717. if len(data) == 0 {
  718. return nil, nil
  719. }
  720. rules := make([]*CodeOwnerRule, 0)
  721. lines := strings.Split(data, "\n")
  722. warnings := make([]string, 0)
  723. for i, line := range lines {
  724. tokens := TokenizeCodeOwnersLine(line)
  725. if len(tokens) == 0 {
  726. continue
  727. } else if len(tokens) < 2 {
  728. warnings = append(warnings, fmt.Sprintf("Line: %d: incorrect format", i+1))
  729. continue
  730. }
  731. rule, wr := ParseCodeOwnersLine(ctx, tokens)
  732. for _, w := range wr {
  733. warnings = append(warnings, fmt.Sprintf("Line: %d: %s", i+1, w))
  734. }
  735. if rule == nil {
  736. continue
  737. }
  738. rules = append(rules, rule)
  739. }
  740. return rules, warnings
  741. }
  742. type CodeOwnerRule struct {
  743. Rule *regexp.Regexp
  744. Negative bool
  745. Users []*user_model.User
  746. Teams []*org_model.Team
  747. }
  748. func ParseCodeOwnersLine(ctx context.Context, tokens []string) (*CodeOwnerRule, []string) {
  749. var err error
  750. rule := &CodeOwnerRule{
  751. Users: make([]*user_model.User, 0),
  752. Teams: make([]*org_model.Team, 0),
  753. Negative: strings.HasPrefix(tokens[0], "!"),
  754. }
  755. warnings := make([]string, 0)
  756. rule.Rule, err = regexp.Compile(fmt.Sprintf("^%s$", strings.TrimPrefix(tokens[0], "!")))
  757. if err != nil {
  758. warnings = append(warnings, fmt.Sprintf("incorrect codeowner regexp: %s", err))
  759. return nil, warnings
  760. }
  761. for _, user := range tokens[1:] {
  762. user = strings.TrimPrefix(user, "@")
  763. // Only @org/team can contain slashes
  764. if strings.Contains(user, "/") {
  765. s := strings.Split(user, "/")
  766. if len(s) != 2 {
  767. warnings = append(warnings, "incorrect codeowner group: "+user)
  768. continue
  769. }
  770. orgName := s[0]
  771. teamName := s[1]
  772. org, err := org_model.GetOrgByName(ctx, orgName)
  773. if err != nil {
  774. warnings = append(warnings, "incorrect codeowner organization: "+user)
  775. continue
  776. }
  777. teams, err := org.LoadTeams(ctx)
  778. if err != nil {
  779. warnings = append(warnings, "incorrect codeowner team: "+user)
  780. continue
  781. }
  782. for _, team := range teams {
  783. if team.Name == teamName {
  784. rule.Teams = append(rule.Teams, team)
  785. }
  786. }
  787. } else {
  788. u, err := user_model.GetUserByName(ctx, user)
  789. if err != nil {
  790. warnings = append(warnings, "incorrect codeowner user: "+user)
  791. continue
  792. }
  793. rule.Users = append(rule.Users, u)
  794. }
  795. }
  796. if (len(rule.Users) == 0) && (len(rule.Teams) == 0) {
  797. warnings = append(warnings, "no users/groups matched")
  798. return nil, warnings
  799. }
  800. return rule, warnings
  801. }
  802. func TokenizeCodeOwnersLine(line string) []string {
  803. if len(line) == 0 {
  804. return nil
  805. }
  806. line = strings.TrimSpace(line)
  807. line = strings.ReplaceAll(line, "\t", " ")
  808. tokens := make([]string, 0)
  809. escape := false
  810. token := ""
  811. for _, char := range line {
  812. if escape {
  813. token += string(char)
  814. escape = false
  815. } else if string(char) == "\\" {
  816. escape = true
  817. } else if string(char) == "#" {
  818. break
  819. } else if string(char) == " " {
  820. if len(token) > 0 {
  821. tokens = append(tokens, token)
  822. token = ""
  823. }
  824. } else {
  825. token += string(char)
  826. }
  827. }
  828. if len(token) > 0 {
  829. tokens = append(tokens, token)
  830. }
  831. return tokens
  832. }
  833. // InsertPullRequests inserted pull requests
  834. func InsertPullRequests(ctx context.Context, prs ...*PullRequest) error {
  835. return db.WithTx(ctx, func(ctx context.Context) error {
  836. for _, pr := range prs {
  837. if err := insertIssue(ctx, pr.Issue); err != nil {
  838. return err
  839. }
  840. pr.IssueID = pr.Issue.ID
  841. if _, err := db.GetEngine(ctx).NoAutoTime().Insert(pr); err != nil {
  842. return err
  843. }
  844. }
  845. return nil
  846. })
  847. }
  848. // GetPullRequestByMergedCommit returns a merged pull request by the given commit
  849. func GetPullRequestByMergedCommit(ctx context.Context, repoID int64, sha string) (*PullRequest, error) {
  850. pr := new(PullRequest)
  851. has, err := db.GetEngine(ctx).Where("base_repo_id = ? AND merged_commit_id = ?", repoID, sha).Get(pr)
  852. if err != nil {
  853. return nil, err
  854. } else if !has {
  855. return nil, ErrPullRequestNotExist{0, 0, 0, repoID, "", ""}
  856. }
  857. if err = pr.LoadAttributes(ctx); err != nil {
  858. return nil, err
  859. }
  860. if err = pr.LoadIssue(ctx); err != nil {
  861. return nil, err
  862. }
  863. return pr, nil
  864. }