code.gitea.io/gitea@v1.22.3/services/pull/commit_status.go (about)

     1  // Copyright 2019 The Gitea Authors.
     2  // All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package pull
     6  
     7  import (
     8  	"context"
     9  
    10  	"code.gitea.io/gitea/models/db"
    11  	git_model "code.gitea.io/gitea/models/git"
    12  	issues_model "code.gitea.io/gitea/models/issues"
    13  	"code.gitea.io/gitea/modules/git"
    14  	"code.gitea.io/gitea/modules/gitrepo"
    15  	"code.gitea.io/gitea/modules/log"
    16  	"code.gitea.io/gitea/modules/structs"
    17  
    18  	"github.com/gobwas/glob"
    19  	"github.com/pkg/errors"
    20  )
    21  
    22  // MergeRequiredContextsCommitStatus returns a commit status state for given required contexts
    23  func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, requiredContexts []string) structs.CommitStatusState {
    24  	// matchedCount is the number of `CommitStatus.Context` that match any context of `requiredContexts`
    25  	matchedCount := 0
    26  	returnedStatus := structs.CommitStatusSuccess
    27  
    28  	if len(requiredContexts) > 0 {
    29  		requiredContextsGlob := make(map[string]glob.Glob, len(requiredContexts))
    30  		for _, ctx := range requiredContexts {
    31  			if gp, err := glob.Compile(ctx); err != nil {
    32  				log.Error("glob.Compile %s failed. Error: %v", ctx, err)
    33  			} else {
    34  				requiredContextsGlob[ctx] = gp
    35  			}
    36  		}
    37  
    38  		for _, gp := range requiredContextsGlob {
    39  			var targetStatus structs.CommitStatusState
    40  			for _, commitStatus := range commitStatuses {
    41  				if gp.Match(commitStatus.Context) {
    42  					targetStatus = commitStatus.State
    43  					matchedCount++
    44  					break
    45  				}
    46  			}
    47  
    48  			// If required rule not match any action, then it is pending
    49  			if targetStatus == "" {
    50  				if structs.CommitStatusPending.NoBetterThan(returnedStatus) {
    51  					returnedStatus = structs.CommitStatusPending
    52  				}
    53  				break
    54  			}
    55  
    56  			if targetStatus.NoBetterThan(returnedStatus) {
    57  				returnedStatus = targetStatus
    58  			}
    59  		}
    60  	}
    61  
    62  	if matchedCount == 0 && returnedStatus == structs.CommitStatusSuccess {
    63  		status := git_model.CalcCommitStatus(commitStatuses)
    64  		if status != nil {
    65  			return status.State
    66  		}
    67  		return structs.CommitStatusSuccess
    68  	}
    69  
    70  	return returnedStatus
    71  }
    72  
    73  // IsCommitStatusContextSuccess returns true if all required status check contexts succeed.
    74  func IsCommitStatusContextSuccess(commitStatuses []*git_model.CommitStatus, requiredContexts []string) bool {
    75  	// If no specific context is required, require that last commit status is a success
    76  	if len(requiredContexts) == 0 {
    77  		status := git_model.CalcCommitStatus(commitStatuses)
    78  		if status == nil || status.State != structs.CommitStatusSuccess {
    79  			return false
    80  		}
    81  		return true
    82  	}
    83  
    84  	for _, ctx := range requiredContexts {
    85  		var found bool
    86  		for _, commitStatus := range commitStatuses {
    87  			if commitStatus.Context == ctx {
    88  				if commitStatus.State != structs.CommitStatusSuccess {
    89  					return false
    90  				}
    91  
    92  				found = true
    93  				break
    94  			}
    95  		}
    96  		if !found {
    97  			return false
    98  		}
    99  	}
   100  	return true
   101  }
   102  
   103  // IsPullCommitStatusPass returns if all required status checks PASS
   104  func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) {
   105  	pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
   106  	if err != nil {
   107  		return false, errors.Wrap(err, "GetLatestCommitStatus")
   108  	}
   109  	if pb == nil || !pb.EnableStatusCheck {
   110  		return true, nil
   111  	}
   112  
   113  	state, err := GetPullRequestCommitStatusState(ctx, pr)
   114  	if err != nil {
   115  		return false, err
   116  	}
   117  	return state.IsSuccess(), nil
   118  }
   119  
   120  // GetPullRequestCommitStatusState returns pull request merged commit status state
   121  func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullRequest) (structs.CommitStatusState, error) {
   122  	// Ensure HeadRepo is loaded
   123  	if err := pr.LoadHeadRepo(ctx); err != nil {
   124  		return "", errors.Wrap(err, "LoadHeadRepo")
   125  	}
   126  
   127  	// check if all required status checks are successful
   128  	headGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.HeadRepo)
   129  	if err != nil {
   130  		return "", errors.Wrap(err, "OpenRepository")
   131  	}
   132  	defer closer.Close()
   133  
   134  	if pr.Flow == issues_model.PullRequestFlowGithub && !headGitRepo.IsBranchExist(pr.HeadBranch) {
   135  		return "", errors.New("Head branch does not exist, can not merge")
   136  	}
   137  	if pr.Flow == issues_model.PullRequestFlowAGit && !git.IsReferenceExist(ctx, headGitRepo.Path, pr.GetGitRefName()) {
   138  		return "", errors.New("Head branch does not exist, can not merge")
   139  	}
   140  
   141  	var sha string
   142  	if pr.Flow == issues_model.PullRequestFlowGithub {
   143  		sha, err = headGitRepo.GetBranchCommitID(pr.HeadBranch)
   144  	} else {
   145  		sha, err = headGitRepo.GetRefCommitID(pr.GetGitRefName())
   146  	}
   147  	if err != nil {
   148  		return "", err
   149  	}
   150  
   151  	if err := pr.LoadBaseRepo(ctx); err != nil {
   152  		return "", errors.Wrap(err, "LoadBaseRepo")
   153  	}
   154  
   155  	commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
   156  	if err != nil {
   157  		return "", errors.Wrap(err, "GetLatestCommitStatus")
   158  	}
   159  
   160  	pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
   161  	if err != nil {
   162  		return "", errors.Wrap(err, "LoadProtectedBranch")
   163  	}
   164  	var requiredContexts []string
   165  	if pb != nil {
   166  		requiredContexts = pb.StatusCheckContexts
   167  	}
   168  
   169  	return MergeRequiredContextsCommitStatus(commitStatuses, requiredContexts), nil
   170  }