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