code.gitea.io/gitea@v1.22.3/services/issue/pull.go (about) 1 // Copyright 2024 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package issue 5 6 import ( 7 "context" 8 "fmt" 9 "time" 10 11 issues_model "code.gitea.io/gitea/models/issues" 12 org_model "code.gitea.io/gitea/models/organization" 13 user_model "code.gitea.io/gitea/models/user" 14 "code.gitea.io/gitea/modules/git" 15 "code.gitea.io/gitea/modules/gitrepo" 16 "code.gitea.io/gitea/modules/log" 17 "code.gitea.io/gitea/modules/setting" 18 ) 19 20 func getMergeBase(repo *git.Repository, pr *issues_model.PullRequest, baseBranch, headBranch string) (string, error) { 21 // Add a temporary remote 22 tmpRemote := fmt.Sprintf("mergebase-%d-%d", pr.ID, time.Now().UnixNano()) 23 if err := repo.AddRemote(tmpRemote, repo.Path, false); err != nil { 24 return "", fmt.Errorf("AddRemote: %w", err) 25 } 26 defer func() { 27 if err := repo.RemoveRemote(tmpRemote); err != nil { 28 log.Error("getMergeBase: RemoveRemote: %v", err) 29 } 30 }() 31 32 mergeBase, _, err := repo.GetMergeBase(tmpRemote, baseBranch, headBranch) 33 return mergeBase, err 34 } 35 36 type ReviewRequestNotifier struct { 37 Comment *issues_model.Comment 38 IsAdd bool 39 Reviewer *user_model.User 40 ReviewTeam *org_model.Team 41 } 42 43 func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) { 44 files := []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"} 45 46 if pr.IsWorkInProgress(ctx) { 47 return nil, nil 48 } 49 50 if err := pr.LoadHeadRepo(ctx); err != nil { 51 return nil, err 52 } 53 54 if err := pr.LoadBaseRepo(ctx); err != nil { 55 return nil, err 56 } 57 58 if pr.BaseRepo.IsFork { 59 return nil, nil 60 } 61 62 repo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo) 63 if err != nil { 64 return nil, err 65 } 66 defer repo.Close() 67 68 commit, err := repo.GetBranchCommit(pr.BaseRepo.DefaultBranch) 69 if err != nil { 70 return nil, err 71 } 72 73 var data string 74 for _, file := range files { 75 if blob, err := commit.GetBlobByPath(file); err == nil { 76 data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize) 77 if err == nil { 78 break 79 } 80 } 81 } 82 83 rules, _ := issues_model.GetCodeOwnersFromContent(ctx, data) 84 85 // get the mergebase 86 mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName()) 87 if err != nil { 88 return nil, err 89 } 90 91 // https://github.com/go-gitea/gitea/issues/29763, we need to get the files changed 92 // between the merge base and the head commit but not the base branch and the head commit 93 changedFiles, err := repo.GetFilesChangedBetween(mergeBase, pr.GetGitRefName()) 94 if err != nil { 95 return nil, err 96 } 97 98 uniqUsers := make(map[int64]*user_model.User) 99 uniqTeams := make(map[string]*org_model.Team) 100 for _, rule := range rules { 101 for _, f := range changedFiles { 102 if (rule.Rule.MatchString(f) && !rule.Negative) || (!rule.Rule.MatchString(f) && rule.Negative) { 103 for _, u := range rule.Users { 104 uniqUsers[u.ID] = u 105 } 106 for _, t := range rule.Teams { 107 uniqTeams[fmt.Sprintf("%d/%d", t.OrgID, t.ID)] = t 108 } 109 } 110 } 111 } 112 113 notifiers := make([]*ReviewRequestNotifier, 0, len(uniqUsers)+len(uniqTeams)) 114 115 if err := issue.LoadPoster(ctx); err != nil { 116 return nil, err 117 } 118 119 for _, u := range uniqUsers { 120 if u.ID != issue.Poster.ID { 121 comment, err := issues_model.AddReviewRequest(ctx, issue, u, issue.Poster) 122 if err != nil { 123 log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err) 124 return nil, err 125 } 126 notifiers = append(notifiers, &ReviewRequestNotifier{ 127 Comment: comment, 128 IsAdd: true, 129 Reviewer: u, 130 }) 131 } 132 } 133 for _, t := range uniqTeams { 134 comment, err := issues_model.AddTeamReviewRequest(ctx, issue, t, issue.Poster) 135 if err != nil { 136 log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err) 137 return nil, err 138 } 139 notifiers = append(notifiers, &ReviewRequestNotifier{ 140 Comment: comment, 141 IsAdd: true, 142 ReviewTeam: t, 143 }) 144 } 145 146 return notifiers, nil 147 }