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 }