code.gitea.io/gitea@v1.21.7/models/issues/pull.go (about)

     1  // Copyright 2015 The Gogs Authors. All rights reserved.
     2  // Copyright 2019 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package issues
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"code.gitea.io/gitea/models/db"
    16  	git_model "code.gitea.io/gitea/models/git"
    17  	org_model "code.gitea.io/gitea/models/organization"
    18  	pull_model "code.gitea.io/gitea/models/pull"
    19  	repo_model "code.gitea.io/gitea/models/repo"
    20  	user_model "code.gitea.io/gitea/models/user"
    21  	"code.gitea.io/gitea/modules/git"
    22  	"code.gitea.io/gitea/modules/log"
    23  	"code.gitea.io/gitea/modules/setting"
    24  	"code.gitea.io/gitea/modules/timeutil"
    25  	"code.gitea.io/gitea/modules/util"
    26  
    27  	"xorm.io/builder"
    28  )
    29  
    30  // ErrPullRequestNotExist represents a "PullRequestNotExist" kind of error.
    31  type ErrPullRequestNotExist struct {
    32  	ID         int64
    33  	IssueID    int64
    34  	HeadRepoID int64
    35  	BaseRepoID int64
    36  	HeadBranch string
    37  	BaseBranch string
    38  }
    39  
    40  // IsErrPullRequestNotExist checks if an error is a ErrPullRequestNotExist.
    41  func IsErrPullRequestNotExist(err error) bool {
    42  	_, ok := err.(ErrPullRequestNotExist)
    43  	return ok
    44  }
    45  
    46  func (err ErrPullRequestNotExist) Error() string {
    47  	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]",
    48  		err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch)
    49  }
    50  
    51  func (err ErrPullRequestNotExist) Unwrap() error {
    52  	return util.ErrNotExist
    53  }
    54  
    55  // ErrPullRequestAlreadyExists represents a "PullRequestAlreadyExists"-error
    56  type ErrPullRequestAlreadyExists struct {
    57  	ID         int64
    58  	IssueID    int64
    59  	HeadRepoID int64
    60  	BaseRepoID int64
    61  	HeadBranch string
    62  	BaseBranch string
    63  }
    64  
    65  // IsErrPullRequestAlreadyExists checks if an error is a ErrPullRequestAlreadyExists.
    66  func IsErrPullRequestAlreadyExists(err error) bool {
    67  	_, ok := err.(ErrPullRequestAlreadyExists)
    68  	return ok
    69  }
    70  
    71  // Error does pretty-printing :D
    72  func (err ErrPullRequestAlreadyExists) Error() string {
    73  	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]",
    74  		err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch)
    75  }
    76  
    77  func (err ErrPullRequestAlreadyExists) Unwrap() error {
    78  	return util.ErrAlreadyExist
    79  }
    80  
    81  // ErrPullRequestHeadRepoMissing represents a "ErrPullRequestHeadRepoMissing" error
    82  type ErrPullRequestHeadRepoMissing struct {
    83  	ID         int64
    84  	HeadRepoID int64
    85  }
    86  
    87  // IsErrErrPullRequestHeadRepoMissing checks if an error is a ErrPullRequestHeadRepoMissing.
    88  func IsErrErrPullRequestHeadRepoMissing(err error) bool {
    89  	_, ok := err.(ErrPullRequestHeadRepoMissing)
    90  	return ok
    91  }
    92  
    93  // Error does pretty-printing :D
    94  func (err ErrPullRequestHeadRepoMissing) Error() string {
    95  	return fmt.Sprintf("pull request head repo missing [id: %d, head_repo_id: %d]",
    96  		err.ID, err.HeadRepoID)
    97  }
    98  
    99  // ErrPullWasClosed is used close a closed pull request
   100  type ErrPullWasClosed struct {
   101  	ID    int64
   102  	Index int64
   103  }
   104  
   105  // IsErrPullWasClosed checks if an error is a ErrErrPullWasClosed.
   106  func IsErrPullWasClosed(err error) bool {
   107  	_, ok := err.(ErrPullWasClosed)
   108  	return ok
   109  }
   110  
   111  func (err ErrPullWasClosed) Error() string {
   112  	return fmt.Sprintf("Pull request [%d] %d was already closed", err.ID, err.Index)
   113  }
   114  
   115  // PullRequestType defines pull request type
   116  type PullRequestType int
   117  
   118  // Enumerate all the pull request types
   119  const (
   120  	PullRequestGitea PullRequestType = iota
   121  	PullRequestGit
   122  )
   123  
   124  // PullRequestStatus defines pull request status
   125  type PullRequestStatus int
   126  
   127  // Enumerate all the pull request status
   128  const (
   129  	PullRequestStatusConflict PullRequestStatus = iota
   130  	PullRequestStatusChecking
   131  	PullRequestStatusMergeable
   132  	PullRequestStatusManuallyMerged
   133  	PullRequestStatusError
   134  	PullRequestStatusEmpty
   135  	PullRequestStatusAncestor
   136  )
   137  
   138  func (status PullRequestStatus) String() string {
   139  	switch status {
   140  	case PullRequestStatusConflict:
   141  		return "CONFLICT"
   142  	case PullRequestStatusChecking:
   143  		return "CHECKING"
   144  	case PullRequestStatusMergeable:
   145  		return "MERGEABLE"
   146  	case PullRequestStatusManuallyMerged:
   147  		return "MANUALLY_MERGED"
   148  	case PullRequestStatusError:
   149  		return "ERROR"
   150  	case PullRequestStatusEmpty:
   151  		return "EMPTY"
   152  	case PullRequestStatusAncestor:
   153  		return "ANCESTOR"
   154  	default:
   155  		return strconv.Itoa(int(status))
   156  	}
   157  }
   158  
   159  // PullRequestFlow the flow of pull request
   160  type PullRequestFlow int
   161  
   162  const (
   163  	// PullRequestFlowGithub github flow from head branch to base branch
   164  	PullRequestFlowGithub PullRequestFlow = iota
   165  	// PullRequestFlowAGit Agit flow pull request, head branch is not exist
   166  	PullRequestFlowAGit
   167  )
   168  
   169  // PullRequest represents relation between pull request and repositories.
   170  type PullRequest struct {
   171  	ID              int64 `xorm:"pk autoincr"`
   172  	Type            PullRequestType
   173  	Status          PullRequestStatus
   174  	ConflictedFiles []string `xorm:"TEXT JSON"`
   175  	CommitsAhead    int
   176  	CommitsBehind   int
   177  
   178  	ChangedProtectedFiles []string `xorm:"TEXT JSON"`
   179  
   180  	IssueID            int64  `xorm:"INDEX"`
   181  	Issue              *Issue `xorm:"-"`
   182  	Index              int64
   183  	RequestedReviewers []*user_model.User `xorm:"-"`
   184  
   185  	HeadRepoID          int64                  `xorm:"INDEX"`
   186  	HeadRepo            *repo_model.Repository `xorm:"-"`
   187  	BaseRepoID          int64                  `xorm:"INDEX"`
   188  	BaseRepo            *repo_model.Repository `xorm:"-"`
   189  	HeadBranch          string
   190  	HeadCommitID        string `xorm:"-"`
   191  	BaseBranch          string
   192  	MergeBase           string `xorm:"VARCHAR(40)"`
   193  	AllowMaintainerEdit bool   `xorm:"NOT NULL DEFAULT false"`
   194  
   195  	HasMerged      bool               `xorm:"INDEX"`
   196  	MergedCommitID string             `xorm:"VARCHAR(40)"`
   197  	MergerID       int64              `xorm:"INDEX"`
   198  	Merger         *user_model.User   `xorm:"-"`
   199  	MergedUnix     timeutil.TimeStamp `xorm:"updated INDEX"`
   200  
   201  	isHeadRepoLoaded bool `xorm:"-"`
   202  
   203  	Flow PullRequestFlow `xorm:"NOT NULL DEFAULT 0"`
   204  }
   205  
   206  func init() {
   207  	db.RegisterModel(new(PullRequest))
   208  }
   209  
   210  // DeletePullsByBaseRepoID deletes all pull requests by the base repository ID
   211  func DeletePullsByBaseRepoID(ctx context.Context, repoID int64) error {
   212  	deleteCond := builder.Select("id").From("pull_request").Where(builder.Eq{"pull_request.base_repo_id": repoID})
   213  
   214  	// Delete scheduled auto merges
   215  	if _, err := db.GetEngine(ctx).In("pull_id", deleteCond).
   216  		Delete(&pull_model.AutoMerge{}); err != nil {
   217  		return err
   218  	}
   219  
   220  	// Delete review states
   221  	if _, err := db.GetEngine(ctx).In("pull_id", deleteCond).
   222  		Delete(&pull_model.ReviewState{}); err != nil {
   223  		return err
   224  	}
   225  
   226  	_, err := db.DeleteByBean(ctx, &PullRequest{BaseRepoID: repoID})
   227  	return err
   228  }
   229  
   230  func (pr *PullRequest) String() string {
   231  	if pr == nil {
   232  		return "<PullRequest nil>"
   233  	}
   234  
   235  	s := new(strings.Builder)
   236  	fmt.Fprintf(s, "<PullRequest [%d]", pr.ID)
   237  	if pr.BaseRepo != nil {
   238  		fmt.Fprintf(s, "%s#%d[%s...", pr.BaseRepo.FullName(), pr.Index, pr.BaseBranch)
   239  	} else {
   240  		fmt.Fprintf(s, "Repo[%d]#%d[%s...", pr.BaseRepoID, pr.Index, pr.BaseBranch)
   241  	}
   242  	if pr.HeadRepoID == pr.BaseRepoID {
   243  		fmt.Fprintf(s, "%s]", pr.HeadBranch)
   244  	} else if pr.HeadRepo != nil {
   245  		fmt.Fprintf(s, "%s:%s]", pr.HeadRepo.FullName(), pr.HeadBranch)
   246  	} else {
   247  		fmt.Fprintf(s, "Repo[%d]:%s]", pr.HeadRepoID, pr.HeadBranch)
   248  	}
   249  	s.WriteByte('>')
   250  	return s.String()
   251  }
   252  
   253  // MustHeadUserName returns the HeadRepo's username if failed return blank
   254  func (pr *PullRequest) MustHeadUserName(ctx context.Context) string {
   255  	if err := pr.LoadHeadRepo(ctx); err != nil {
   256  		if !repo_model.IsErrRepoNotExist(err) {
   257  			log.Error("LoadHeadRepo: %v", err)
   258  		} else {
   259  			log.Warn("LoadHeadRepo %d but repository does not exist: %v", pr.HeadRepoID, err)
   260  		}
   261  		return ""
   262  	}
   263  	if pr.HeadRepo == nil {
   264  		return ""
   265  	}
   266  	return pr.HeadRepo.OwnerName
   267  }
   268  
   269  // LoadAttributes loads pull request attributes from database
   270  // Note: don't try to get Issue because will end up recursive querying.
   271  func (pr *PullRequest) LoadAttributes(ctx context.Context) (err error) {
   272  	if pr.HasMerged && pr.Merger == nil {
   273  		pr.Merger, err = user_model.GetUserByID(ctx, pr.MergerID)
   274  		if user_model.IsErrUserNotExist(err) {
   275  			pr.MergerID = -1
   276  			pr.Merger = user_model.NewGhostUser()
   277  		} else if err != nil {
   278  			return fmt.Errorf("getUserByID [%d]: %w", pr.MergerID, err)
   279  		}
   280  	}
   281  
   282  	return nil
   283  }
   284  
   285  // LoadHeadRepo loads the head repository, pr.HeadRepo will remain nil if it does not exist
   286  // and thus ErrRepoNotExist will never be returned
   287  func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) {
   288  	if !pr.isHeadRepoLoaded && pr.HeadRepo == nil && pr.HeadRepoID > 0 {
   289  		if pr.HeadRepoID == pr.BaseRepoID {
   290  			if pr.BaseRepo != nil {
   291  				pr.HeadRepo = pr.BaseRepo
   292  				return nil
   293  			} else if pr.Issue != nil && pr.Issue.Repo != nil {
   294  				pr.HeadRepo = pr.Issue.Repo
   295  				return nil
   296  			}
   297  		}
   298  
   299  		pr.HeadRepo, err = repo_model.GetRepositoryByID(ctx, pr.HeadRepoID)
   300  		if err != nil && !repo_model.IsErrRepoNotExist(err) { // Head repo maybe deleted, but it should still work
   301  			return fmt.Errorf("pr[%d].LoadHeadRepo[%d]: %w", pr.ID, pr.HeadRepoID, err)
   302  		}
   303  		pr.isHeadRepoLoaded = true
   304  	}
   305  	return nil
   306  }
   307  
   308  // LoadRequestedReviewers loads the requested reviewers.
   309  func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error {
   310  	if len(pr.RequestedReviewers) > 0 {
   311  		return nil
   312  	}
   313  
   314  	reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID)
   315  	if err != nil {
   316  		return err
   317  	}
   318  
   319  	if err = reviews.LoadReviewers(ctx); err != nil {
   320  		return err
   321  	}
   322  	for _, review := range reviews {
   323  		pr.RequestedReviewers = append(pr.RequestedReviewers, review.Reviewer)
   324  	}
   325  
   326  	return nil
   327  }
   328  
   329  // LoadBaseRepo loads the target repository. ErrRepoNotExist may be returned.
   330  func (pr *PullRequest) LoadBaseRepo(ctx context.Context) (err error) {
   331  	if pr.BaseRepo != nil {
   332  		return nil
   333  	}
   334  
   335  	if pr.HeadRepoID == pr.BaseRepoID && pr.HeadRepo != nil {
   336  		pr.BaseRepo = pr.HeadRepo
   337  		return nil
   338  	}
   339  
   340  	if pr.Issue != nil && pr.Issue.Repo != nil {
   341  		pr.BaseRepo = pr.Issue.Repo
   342  		return nil
   343  	}
   344  
   345  	pr.BaseRepo, err = repo_model.GetRepositoryByID(ctx, pr.BaseRepoID)
   346  	if err != nil {
   347  		return fmt.Errorf("pr[%d].LoadBaseRepo[%d]: %w", pr.ID, pr.BaseRepoID, err)
   348  	}
   349  	return nil
   350  }
   351  
   352  // LoadIssue loads issue information from database
   353  func (pr *PullRequest) LoadIssue(ctx context.Context) (err error) {
   354  	if pr.Issue != nil {
   355  		return nil
   356  	}
   357  
   358  	pr.Issue, err = GetIssueByID(ctx, pr.IssueID)
   359  	if err == nil {
   360  		pr.Issue.PullRequest = pr
   361  	}
   362  	return err
   363  }
   364  
   365  // ReviewCount represents a count of Reviews
   366  type ReviewCount struct {
   367  	IssueID int64
   368  	Type    ReviewType
   369  	Count   int64
   370  }
   371  
   372  // GetApprovalCounts returns the approval counts by type
   373  // FIXME: Only returns official counts due to double counting of non-official counts
   374  func (pr *PullRequest) GetApprovalCounts(ctx context.Context) ([]*ReviewCount, error) {
   375  	rCounts := make([]*ReviewCount, 0, 6)
   376  	sess := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID)
   377  	return rCounts, sess.Select("issue_id, type, count(id) as `count`").Where("official = ? AND dismissed = ?", true, false).GroupBy("issue_id, type").Table("review").Find(&rCounts)
   378  }
   379  
   380  // GetApprovers returns the approvers of the pull request
   381  func (pr *PullRequest) GetApprovers() string {
   382  	stringBuilder := strings.Builder{}
   383  	if err := pr.getReviewedByLines(&stringBuilder); err != nil {
   384  		log.Error("Unable to getReviewedByLines: Error: %v", err)
   385  		return ""
   386  	}
   387  
   388  	return stringBuilder.String()
   389  }
   390  
   391  func (pr *PullRequest) getReviewedByLines(writer io.Writer) error {
   392  	maxReviewers := setting.Repository.PullRequest.DefaultMergeMessageMaxApprovers
   393  
   394  	if maxReviewers == 0 {
   395  		return nil
   396  	}
   397  
   398  	ctx, committer, err := db.TxContext(db.DefaultContext)
   399  	if err != nil {
   400  		return err
   401  	}
   402  	defer committer.Close()
   403  
   404  	// Note: This doesn't page as we only expect a very limited number of reviews
   405  	reviews, err := FindLatestReviews(ctx, FindReviewOptions{
   406  		Type:         ReviewTypeApprove,
   407  		IssueID:      pr.IssueID,
   408  		OfficialOnly: setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly,
   409  	})
   410  	if err != nil {
   411  		log.Error("Unable to FindReviews for PR ID %d: %v", pr.ID, err)
   412  		return err
   413  	}
   414  
   415  	reviewersWritten := 0
   416  
   417  	for _, review := range reviews {
   418  		if maxReviewers > 0 && reviewersWritten > maxReviewers {
   419  			break
   420  		}
   421  
   422  		if err := review.LoadReviewer(ctx); err != nil && !user_model.IsErrUserNotExist(err) {
   423  			log.Error("Unable to LoadReviewer[%d] for PR ID %d : %v", review.ReviewerID, pr.ID, err)
   424  			return err
   425  		} else if review.Reviewer == nil {
   426  			continue
   427  		}
   428  		if _, err := writer.Write([]byte("Reviewed-by: ")); err != nil {
   429  			return err
   430  		}
   431  		if _, err := writer.Write([]byte(review.Reviewer.NewGitSig().String())); err != nil {
   432  			return err
   433  		}
   434  		if _, err := writer.Write([]byte{'\n'}); err != nil {
   435  			return err
   436  		}
   437  		reviewersWritten++
   438  	}
   439  	return committer.Commit()
   440  }
   441  
   442  // GetGitRefName returns git ref for hidden pull request branch
   443  func (pr *PullRequest) GetGitRefName() string {
   444  	return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index)
   445  }
   446  
   447  func (pr *PullRequest) GetGitHeadBranchRefName() string {
   448  	return fmt.Sprintf("%s%s", git.BranchPrefix, pr.HeadBranch)
   449  }
   450  
   451  // IsChecking returns true if this pull request is still checking conflict.
   452  func (pr *PullRequest) IsChecking() bool {
   453  	return pr.Status == PullRequestStatusChecking
   454  }
   455  
   456  // CanAutoMerge returns true if this pull request can be merged automatically.
   457  func (pr *PullRequest) CanAutoMerge() bool {
   458  	return pr.Status == PullRequestStatusMergeable
   459  }
   460  
   461  // IsEmpty returns true if this pull request is empty.
   462  func (pr *PullRequest) IsEmpty() bool {
   463  	return pr.Status == PullRequestStatusEmpty
   464  }
   465  
   466  // IsAncestor returns true if the Head Commit of this PR is an ancestor of the Base Commit
   467  func (pr *PullRequest) IsAncestor() bool {
   468  	return pr.Status == PullRequestStatusAncestor
   469  }
   470  
   471  // IsFromFork return true if this PR is from a fork.
   472  func (pr *PullRequest) IsFromFork() bool {
   473  	return pr.HeadRepoID != pr.BaseRepoID
   474  }
   475  
   476  // SetMerged sets a pull request to merged and closes the corresponding issue
   477  func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
   478  	if pr.HasMerged {
   479  		return false, fmt.Errorf("PullRequest[%d] already merged", pr.Index)
   480  	}
   481  	if pr.MergedCommitID == "" || pr.MergedUnix == 0 || pr.Merger == nil {
   482  		return false, fmt.Errorf("Unable to merge PullRequest[%d], some required fields are empty", pr.Index)
   483  	}
   484  
   485  	pr.HasMerged = true
   486  	sess := db.GetEngine(ctx)
   487  
   488  	if _, err := sess.Exec("UPDATE `issue` SET `repo_id` = `repo_id` WHERE `id` = ?", pr.IssueID); err != nil {
   489  		return false, err
   490  	}
   491  
   492  	if _, err := sess.Exec("UPDATE `pull_request` SET `issue_id` = `issue_id` WHERE `id` = ?", pr.ID); err != nil {
   493  		return false, err
   494  	}
   495  
   496  	pr.Issue = nil
   497  	if err := pr.LoadIssue(ctx); err != nil {
   498  		return false, err
   499  	}
   500  
   501  	if tmpPr, err := GetPullRequestByID(ctx, pr.ID); err != nil {
   502  		return false, err
   503  	} else if tmpPr.HasMerged {
   504  		if pr.Issue.IsClosed {
   505  			return false, nil
   506  		}
   507  		return false, fmt.Errorf("PullRequest[%d] already merged but it's associated issue [%d] is not closed", pr.Index, pr.IssueID)
   508  	} else if pr.Issue.IsClosed {
   509  		return false, fmt.Errorf("PullRequest[%d] already closed", pr.Index)
   510  	}
   511  
   512  	if err := pr.Issue.LoadRepo(ctx); err != nil {
   513  		return false, err
   514  	}
   515  
   516  	if err := pr.Issue.Repo.LoadOwner(ctx); err != nil {
   517  		return false, err
   518  	}
   519  
   520  	if _, err := changeIssueStatus(ctx, pr.Issue, pr.Merger, true, true); err != nil {
   521  		return false, fmt.Errorf("Issue.changeStatus: %w", err)
   522  	}
   523  
   524  	// reset the conflicted files as there cannot be any if we're merged
   525  	pr.ConflictedFiles = []string{}
   526  
   527  	// We need to save all of the data used to compute this merge as it may have already been changed by TestPatch. FIXME: need to set some state to prevent TestPatch from running whilst we are merging.
   528  	if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix, conflicted_files").Update(pr); err != nil {
   529  		return false, fmt.Errorf("Failed to update pr[%d]: %w", pr.ID, err)
   530  	}
   531  
   532  	return true, nil
   533  }
   534  
   535  // NewPullRequest creates new pull request with labels for repository.
   536  func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
   537  	ctx, committer, err := db.TxContext(ctx)
   538  	if err != nil {
   539  		return err
   540  	}
   541  	defer committer.Close()
   542  
   543  	idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
   544  	if err != nil {
   545  		return fmt.Errorf("generate pull request index failed: %w", err)
   546  	}
   547  
   548  	issue.Index = idx
   549  
   550  	if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
   551  		Repo:        repo,
   552  		Issue:       issue,
   553  		LabelIDs:    labelIDs,
   554  		Attachments: uuids,
   555  		IsPull:      true,
   556  	}); err != nil {
   557  		if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
   558  			return err
   559  		}
   560  		return fmt.Errorf("newIssue: %w", err)
   561  	}
   562  
   563  	pr.Index = issue.Index
   564  	pr.BaseRepo = repo
   565  	pr.IssueID = issue.ID
   566  	if err = db.Insert(ctx, pr); err != nil {
   567  		return fmt.Errorf("insert pull repo: %w", err)
   568  	}
   569  
   570  	if err = committer.Commit(); err != nil {
   571  		return fmt.Errorf("Commit: %w", err)
   572  	}
   573  
   574  	return nil
   575  }
   576  
   577  // GetUnmergedPullRequest returns a pull request that is open and has not been merged
   578  // by given head/base and repo/branch.
   579  func GetUnmergedPullRequest(ctx context.Context, headRepoID, baseRepoID int64, headBranch, baseBranch string, flow PullRequestFlow) (*PullRequest, error) {
   580  	pr := new(PullRequest)
   581  	has, err := db.GetEngine(ctx).
   582  		Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND flow = ? AND issue.is_closed=?",
   583  			headRepoID, headBranch, baseRepoID, baseBranch, false, flow, false).
   584  		Join("INNER", "issue", "issue.id=pull_request.issue_id").
   585  		Get(pr)
   586  	if err != nil {
   587  		return nil, err
   588  	} else if !has {
   589  		return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
   590  	}
   591  
   592  	return pr, nil
   593  }
   594  
   595  // GetLatestPullRequestByHeadInfo returns the latest pull request (regardless of its status)
   596  // by given head information (repo and branch).
   597  func GetLatestPullRequestByHeadInfo(repoID int64, branch string) (*PullRequest, error) {
   598  	pr := new(PullRequest)
   599  	has, err := db.GetEngine(db.DefaultContext).
   600  		Where("head_repo_id = ? AND head_branch = ? AND flow = ?", repoID, branch, PullRequestFlowGithub).
   601  		OrderBy("id DESC").
   602  		Get(pr)
   603  	if !has {
   604  		return nil, err
   605  	}
   606  	return pr, err
   607  }
   608  
   609  // GetPullRequestByIndex returns a pull request by the given index
   610  func GetPullRequestByIndex(ctx context.Context, repoID, index int64) (*PullRequest, error) {
   611  	if index < 1 {
   612  		return nil, ErrPullRequestNotExist{}
   613  	}
   614  	pr := &PullRequest{
   615  		BaseRepoID: repoID,
   616  		Index:      index,
   617  	}
   618  
   619  	has, err := db.GetEngine(ctx).Get(pr)
   620  	if err != nil {
   621  		return nil, err
   622  	} else if !has {
   623  		return nil, ErrPullRequestNotExist{0, 0, 0, repoID, "", ""}
   624  	}
   625  
   626  	if err = pr.LoadAttributes(ctx); err != nil {
   627  		return nil, err
   628  	}
   629  	if err = pr.LoadIssue(ctx); err != nil {
   630  		return nil, err
   631  	}
   632  
   633  	return pr, nil
   634  }
   635  
   636  // GetPullRequestByID returns a pull request by given ID.
   637  func GetPullRequestByID(ctx context.Context, id int64) (*PullRequest, error) {
   638  	pr := new(PullRequest)
   639  	has, err := db.GetEngine(ctx).ID(id).Get(pr)
   640  	if err != nil {
   641  		return nil, err
   642  	} else if !has {
   643  		return nil, ErrPullRequestNotExist{id, 0, 0, 0, "", ""}
   644  	}
   645  	return pr, pr.LoadAttributes(ctx)
   646  }
   647  
   648  // GetPullRequestByIssueIDWithNoAttributes returns pull request with no attributes loaded by given issue ID.
   649  func GetPullRequestByIssueIDWithNoAttributes(issueID int64) (*PullRequest, error) {
   650  	var pr PullRequest
   651  	has, err := db.GetEngine(db.DefaultContext).Where("issue_id = ?", issueID).Get(&pr)
   652  	if err != nil {
   653  		return nil, err
   654  	}
   655  	if !has {
   656  		return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""}
   657  	}
   658  	return &pr, nil
   659  }
   660  
   661  // GetPullRequestByIssueID returns pull request by given issue ID.
   662  func GetPullRequestByIssueID(ctx context.Context, issueID int64) (*PullRequest, error) {
   663  	pr := &PullRequest{
   664  		IssueID: issueID,
   665  	}
   666  	has, err := db.GetByBean(ctx, pr)
   667  	if err != nil {
   668  		return nil, err
   669  	} else if !has {
   670  		return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""}
   671  	}
   672  	return pr, pr.LoadAttributes(ctx)
   673  }
   674  
   675  // GetAllUnmergedAgitPullRequestByPoster get all unmerged agit flow pull request
   676  // By poster id.
   677  func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*PullRequest, error) {
   678  	pulls := make([]*PullRequest, 0, 10)
   679  
   680  	err := db.GetEngine(ctx).
   681  		Where("has_merged=? AND flow = ? AND issue.is_closed=? AND issue.poster_id=?",
   682  			false, PullRequestFlowAGit, false, uid).
   683  		Join("INNER", "issue", "issue.id=pull_request.issue_id").
   684  		Find(&pulls)
   685  
   686  	return pulls, err
   687  }
   688  
   689  // Update updates all fields of pull request.
   690  func (pr *PullRequest) Update() error {
   691  	_, err := db.GetEngine(db.DefaultContext).ID(pr.ID).AllCols().Update(pr)
   692  	return err
   693  }
   694  
   695  // UpdateCols updates specific fields of pull request.
   696  func (pr *PullRequest) UpdateCols(cols ...string) error {
   697  	_, err := db.GetEngine(db.DefaultContext).ID(pr.ID).Cols(cols...).Update(pr)
   698  	return err
   699  }
   700  
   701  // UpdateColsIfNotMerged updates specific fields of a pull request if it has not been merged
   702  func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string) error {
   703  	_, err := db.GetEngine(ctx).Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr)
   704  	return err
   705  }
   706  
   707  // IsWorkInProgress determine if the Pull Request is a Work In Progress by its title
   708  // Issue must be set before this method can be called.
   709  func (pr *PullRequest) IsWorkInProgress() bool {
   710  	if err := pr.LoadIssue(db.DefaultContext); err != nil {
   711  		log.Error("LoadIssue: %v", err)
   712  		return false
   713  	}
   714  	return HasWorkInProgressPrefix(pr.Issue.Title)
   715  }
   716  
   717  // HasWorkInProgressPrefix determines if the given PR title has a Work In Progress prefix
   718  func HasWorkInProgressPrefix(title string) bool {
   719  	for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
   720  		if strings.HasPrefix(strings.ToUpper(title), strings.ToUpper(prefix)) {
   721  			return true
   722  		}
   723  	}
   724  	return false
   725  }
   726  
   727  // IsFilesConflicted determines if the  Pull Request has changes conflicting with the target branch.
   728  func (pr *PullRequest) IsFilesConflicted() bool {
   729  	return len(pr.ConflictedFiles) > 0
   730  }
   731  
   732  // GetWorkInProgressPrefix returns the prefix used to mark the pull request as a work in progress.
   733  // It returns an empty string when none were found
   734  func (pr *PullRequest) GetWorkInProgressPrefix(ctx context.Context) string {
   735  	if err := pr.LoadIssue(ctx); err != nil {
   736  		log.Error("LoadIssue: %v", err)
   737  		return ""
   738  	}
   739  
   740  	for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
   741  		if strings.HasPrefix(strings.ToUpper(pr.Issue.Title), strings.ToUpper(prefix)) {
   742  			return pr.Issue.Title[0:len(prefix)]
   743  		}
   744  	}
   745  	return ""
   746  }
   747  
   748  // UpdateCommitDivergence update Divergence of a pull request
   749  func (pr *PullRequest) UpdateCommitDivergence(ctx context.Context, ahead, behind int) error {
   750  	if pr.ID == 0 {
   751  		return fmt.Errorf("pull ID is 0")
   752  	}
   753  	pr.CommitsAhead = ahead
   754  	pr.CommitsBehind = behind
   755  	_, err := db.GetEngine(ctx).ID(pr.ID).Cols("commits_ahead", "commits_behind").Update(pr)
   756  	return err
   757  }
   758  
   759  // IsSameRepo returns true if base repo and head repo is the same
   760  func (pr *PullRequest) IsSameRepo() bool {
   761  	return pr.BaseRepoID == pr.HeadRepoID
   762  }
   763  
   764  // GetPullRequestsByHeadBranch returns all prs by head branch
   765  // Since there could be multiple prs with the same head branch, this function returns a slice of prs
   766  func GetPullRequestsByHeadBranch(ctx context.Context, headBranch string, headRepoID int64) ([]*PullRequest, error) {
   767  	log.Trace("GetPullRequestsByHeadBranch: headBranch: '%s', headRepoID: '%d'", headBranch, headRepoID)
   768  	prs := make([]*PullRequest, 0, 2)
   769  	if err := db.GetEngine(ctx).Where(builder.Eq{"head_branch": headBranch, "head_repo_id": headRepoID}).
   770  		Find(&prs); err != nil {
   771  		return nil, err
   772  	}
   773  	return prs, nil
   774  }
   775  
   776  // GetBaseBranchLink returns the relative URL of the base branch
   777  func (pr *PullRequest) GetBaseBranchLink() string {
   778  	if err := pr.LoadBaseRepo(db.DefaultContext); err != nil {
   779  		log.Error("LoadBaseRepo: %v", err)
   780  		return ""
   781  	}
   782  	if pr.BaseRepo == nil {
   783  		return ""
   784  	}
   785  	return pr.BaseRepo.Link() + "/src/branch/" + util.PathEscapeSegments(pr.BaseBranch)
   786  }
   787  
   788  // GetHeadBranchLink returns the relative URL of the head branch
   789  func (pr *PullRequest) GetHeadBranchLink() string {
   790  	if pr.Flow == PullRequestFlowAGit {
   791  		return ""
   792  	}
   793  
   794  	if err := pr.LoadHeadRepo(db.DefaultContext); err != nil {
   795  		log.Error("LoadHeadRepo: %v", err)
   796  		return ""
   797  	}
   798  	if pr.HeadRepo == nil {
   799  		return ""
   800  	}
   801  	return pr.HeadRepo.Link() + "/src/branch/" + util.PathEscapeSegments(pr.HeadBranch)
   802  }
   803  
   804  // UpdateAllowEdits update if PR can be edited from maintainers
   805  func UpdateAllowEdits(ctx context.Context, pr *PullRequest) error {
   806  	if _, err := db.GetEngine(ctx).ID(pr.ID).Cols("allow_maintainer_edit").Update(pr); err != nil {
   807  		return err
   808  	}
   809  	return nil
   810  }
   811  
   812  // Mergeable returns if the pullrequest is mergeable.
   813  func (pr *PullRequest) Mergeable() bool {
   814  	// If a pull request isn't mergable if it's:
   815  	// - Being conflict checked.
   816  	// - Has a conflict.
   817  	// - Received a error while being conflict checked.
   818  	// - Is a work-in-progress pull request.
   819  	return pr.Status != PullRequestStatusChecking && pr.Status != PullRequestStatusConflict &&
   820  		pr.Status != PullRequestStatusError && !pr.IsWorkInProgress()
   821  }
   822  
   823  // HasEnoughApprovals returns true if pr has enough granted approvals.
   824  func HasEnoughApprovals(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
   825  	if protectBranch.RequiredApprovals == 0 {
   826  		return true
   827  	}
   828  	return GetGrantedApprovalsCount(ctx, protectBranch, pr) >= protectBranch.RequiredApprovals
   829  }
   830  
   831  // GetGrantedApprovalsCount returns the number of granted approvals for pr. A granted approval must be authored by a user in an approval whitelist.
   832  func GetGrantedApprovalsCount(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) int64 {
   833  	sess := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
   834  		And("type = ?", ReviewTypeApprove).
   835  		And("official = ?", true).
   836  		And("dismissed = ?", false)
   837  	if protectBranch.DismissStaleApprovals {
   838  		sess = sess.And("stale = ?", false)
   839  	}
   840  	approvals, err := sess.Count(new(Review))
   841  	if err != nil {
   842  		log.Error("GetGrantedApprovalsCount: %v", err)
   843  		return 0
   844  	}
   845  
   846  	return approvals
   847  }
   848  
   849  // MergeBlockedByRejectedReview returns true if merge is blocked by rejected reviews
   850  func MergeBlockedByRejectedReview(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
   851  	if !protectBranch.BlockOnRejectedReviews {
   852  		return false
   853  	}
   854  	rejectExist, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
   855  		And("type = ?", ReviewTypeReject).
   856  		And("official = ?", true).
   857  		And("dismissed = ?", false).
   858  		Exist(new(Review))
   859  	if err != nil {
   860  		log.Error("MergeBlockedByRejectedReview: %v", err)
   861  		return true
   862  	}
   863  
   864  	return rejectExist
   865  }
   866  
   867  // MergeBlockedByOfficialReviewRequests block merge because of some review request to official reviewer
   868  // of from official review
   869  func MergeBlockedByOfficialReviewRequests(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
   870  	if !protectBranch.BlockOnOfficialReviewRequests {
   871  		return false
   872  	}
   873  	has, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
   874  		And("type = ?", ReviewTypeRequest).
   875  		And("official = ?", true).
   876  		Exist(new(Review))
   877  	if err != nil {
   878  		log.Error("MergeBlockedByOfficialReviewRequests: %v", err)
   879  		return true
   880  	}
   881  
   882  	return has
   883  }
   884  
   885  // MergeBlockedByOutdatedBranch returns true if merge is blocked by an outdated head branch
   886  func MergeBlockedByOutdatedBranch(protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
   887  	return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0
   888  }
   889  
   890  func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullRequest) error {
   891  	files := []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}
   892  
   893  	if pr.IsWorkInProgress() {
   894  		return nil
   895  	}
   896  
   897  	if err := pr.LoadBaseRepo(ctx); err != nil {
   898  		return err
   899  	}
   900  
   901  	repo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
   902  	if err != nil {
   903  		return err
   904  	}
   905  	defer repo.Close()
   906  
   907  	branch, err := repo.GetDefaultBranch()
   908  	if err != nil {
   909  		return err
   910  	}
   911  
   912  	commit, err := repo.GetBranchCommit(branch)
   913  	if err != nil {
   914  		return err
   915  	}
   916  
   917  	var data string
   918  	for _, file := range files {
   919  		if blob, err := commit.GetBlobByPath(file); err == nil {
   920  			data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
   921  			if err == nil {
   922  				break
   923  			}
   924  		}
   925  	}
   926  
   927  	rules, _ := GetCodeOwnersFromContent(ctx, data)
   928  	changedFiles, err := repo.GetFilesChangedBetween(git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
   929  	if err != nil {
   930  		return err
   931  	}
   932  
   933  	uniqUsers := make(map[int64]*user_model.User)
   934  	uniqTeams := make(map[string]*org_model.Team)
   935  	for _, rule := range rules {
   936  		for _, f := range changedFiles {
   937  			if (rule.Rule.MatchString(f) && !rule.Negative) || (!rule.Rule.MatchString(f) && rule.Negative) {
   938  				for _, u := range rule.Users {
   939  					uniqUsers[u.ID] = u
   940  				}
   941  				for _, t := range rule.Teams {
   942  					uniqTeams[fmt.Sprintf("%d/%d", t.OrgID, t.ID)] = t
   943  				}
   944  			}
   945  		}
   946  	}
   947  
   948  	for _, u := range uniqUsers {
   949  		if u.ID != pull.Poster.ID {
   950  			if _, err := AddReviewRequest(ctx, pull, u, pull.Poster); err != nil {
   951  				log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
   952  				return err
   953  			}
   954  		}
   955  	}
   956  	for _, t := range uniqTeams {
   957  		if _, err := AddTeamReviewRequest(ctx, pull, t, pull.Poster); err != nil {
   958  			log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err)
   959  			return err
   960  		}
   961  	}
   962  
   963  	return nil
   964  }
   965  
   966  // GetCodeOwnersFromContent returns the code owners configuration
   967  // Return empty slice if files missing
   968  // Return warning messages on parsing errors
   969  // We're trying to do the best we can when parsing a file.
   970  // Invalid lines are skipped. Non-existent users and teams too.
   971  func GetCodeOwnersFromContent(ctx context.Context, data string) ([]*CodeOwnerRule, []string) {
   972  	if len(data) == 0 {
   973  		return nil, nil
   974  	}
   975  
   976  	rules := make([]*CodeOwnerRule, 0)
   977  	lines := strings.Split(data, "\n")
   978  	warnings := make([]string, 0)
   979  
   980  	for i, line := range lines {
   981  		tokens := TokenizeCodeOwnersLine(line)
   982  		if len(tokens) == 0 {
   983  			continue
   984  		} else if len(tokens) < 2 {
   985  			warnings = append(warnings, fmt.Sprintf("Line: %d: incorrect format", i+1))
   986  			continue
   987  		}
   988  		rule, wr := ParseCodeOwnersLine(ctx, tokens)
   989  		for _, w := range wr {
   990  			warnings = append(warnings, fmt.Sprintf("Line: %d: %s", i+1, w))
   991  		}
   992  		if rule == nil {
   993  			continue
   994  		}
   995  
   996  		rules = append(rules, rule)
   997  	}
   998  
   999  	return rules, warnings
  1000  }
  1001  
  1002  type CodeOwnerRule struct {
  1003  	Rule     *regexp.Regexp
  1004  	Negative bool
  1005  	Users    []*user_model.User
  1006  	Teams    []*org_model.Team
  1007  }
  1008  
  1009  func ParseCodeOwnersLine(ctx context.Context, tokens []string) (*CodeOwnerRule, []string) {
  1010  	var err error
  1011  	rule := &CodeOwnerRule{
  1012  		Users:    make([]*user_model.User, 0),
  1013  		Teams:    make([]*org_model.Team, 0),
  1014  		Negative: strings.HasPrefix(tokens[0], "!"),
  1015  	}
  1016  
  1017  	warnings := make([]string, 0)
  1018  
  1019  	rule.Rule, err = regexp.Compile(fmt.Sprintf("^%s$", strings.TrimPrefix(tokens[0], "!")))
  1020  	if err != nil {
  1021  		warnings = append(warnings, fmt.Sprintf("incorrect codeowner regexp: %s", err))
  1022  		return nil, warnings
  1023  	}
  1024  
  1025  	for _, user := range tokens[1:] {
  1026  		user = strings.TrimPrefix(user, "@")
  1027  
  1028  		// Only @org/team can contain slashes
  1029  		if strings.Contains(user, "/") {
  1030  			s := strings.Split(user, "/")
  1031  			if len(s) != 2 {
  1032  				warnings = append(warnings, fmt.Sprintf("incorrect codeowner group: %s", user))
  1033  				continue
  1034  			}
  1035  			orgName := s[0]
  1036  			teamName := s[1]
  1037  
  1038  			org, err := org_model.GetOrgByName(ctx, orgName)
  1039  			if err != nil {
  1040  				warnings = append(warnings, fmt.Sprintf("incorrect codeowner organization: %s", user))
  1041  				continue
  1042  			}
  1043  			teams, err := org.LoadTeams()
  1044  			if err != nil {
  1045  				warnings = append(warnings, fmt.Sprintf("incorrect codeowner team: %s", user))
  1046  				continue
  1047  			}
  1048  
  1049  			for _, team := range teams {
  1050  				if team.Name == teamName {
  1051  					rule.Teams = append(rule.Teams, team)
  1052  				}
  1053  			}
  1054  		} else {
  1055  			u, err := user_model.GetUserByName(ctx, user)
  1056  			if err != nil {
  1057  				warnings = append(warnings, fmt.Sprintf("incorrect codeowner user: %s", user))
  1058  				continue
  1059  			}
  1060  			rule.Users = append(rule.Users, u)
  1061  		}
  1062  	}
  1063  
  1064  	if (len(rule.Users) == 0) && (len(rule.Teams) == 0) {
  1065  		warnings = append(warnings, "no users/groups matched")
  1066  		return nil, warnings
  1067  	}
  1068  
  1069  	return rule, warnings
  1070  }
  1071  
  1072  func TokenizeCodeOwnersLine(line string) []string {
  1073  	if len(line) == 0 {
  1074  		return nil
  1075  	}
  1076  
  1077  	line = strings.TrimSpace(line)
  1078  	line = strings.ReplaceAll(line, "\t", " ")
  1079  
  1080  	tokens := make([]string, 0)
  1081  
  1082  	escape := false
  1083  	token := ""
  1084  	for _, char := range line {
  1085  		if escape {
  1086  			token += string(char)
  1087  			escape = false
  1088  		} else if string(char) == "\\" {
  1089  			escape = true
  1090  		} else if string(char) == "#" {
  1091  			break
  1092  		} else if string(char) == " " {
  1093  			if len(token) > 0 {
  1094  				tokens = append(tokens, token)
  1095  				token = ""
  1096  			}
  1097  		} else {
  1098  			token += string(char)
  1099  		}
  1100  	}
  1101  
  1102  	if len(token) > 0 {
  1103  		tokens = append(tokens, token)
  1104  	}
  1105  
  1106  	return tokens
  1107  }
  1108  
  1109  // InsertPullRequests inserted pull requests
  1110  func InsertPullRequests(ctx context.Context, prs ...*PullRequest) error {
  1111  	ctx, committer, err := db.TxContext(ctx)
  1112  	if err != nil {
  1113  		return err
  1114  	}
  1115  	defer committer.Close()
  1116  	sess := db.GetEngine(ctx)
  1117  	for _, pr := range prs {
  1118  		if err := insertIssue(ctx, pr.Issue); err != nil {
  1119  			return err
  1120  		}
  1121  		pr.IssueID = pr.Issue.ID
  1122  		if _, err := sess.NoAutoTime().Insert(pr); err != nil {
  1123  			return err
  1124  		}
  1125  	}
  1126  	return committer.Commit()
  1127  }