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  }