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 }