sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/tide/codereview.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tide 18 19 import ( 20 "encoding/json" 21 "errors" 22 "time" 23 24 "github.com/andygrunwald/go-gerrit" 25 "github.com/sirupsen/logrus" 26 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 27 "sigs.k8s.io/prow/pkg/config" 28 "sigs.k8s.io/prow/pkg/git/types" 29 "sigs.k8s.io/prow/pkg/tide/blockers" 30 31 githubql "github.com/shurcooL/githubv4" 32 ) 33 34 // CodeReviewForDeck contains superset of data from CodeReviewCommon, it's meant 35 // to be consumed by deck only. 36 // 37 // Tide serves Pool data to deck via http request inside cluster, which could 38 // contain many PullRequests, sending over full PullRequest struct could be very 39 // expensive in some cases. 40 type CodeReviewForDeck struct { 41 Title string 42 Number int 43 HeadRefOID string 44 Mergeable string 45 } 46 47 func FromCodeReviewCommonToCodeReviewForDeck(crc *CodeReviewCommon) *CodeReviewForDeck { 48 if crc == nil { 49 return nil 50 } 51 return &CodeReviewForDeck{ 52 Title: crc.Title, 53 Number: crc.Number, 54 HeadRefOID: crc.HeadRefOID, 55 Mergeable: crc.Mergeable, 56 } 57 } 58 59 // MinCodeReviewCommon can be casted into full CodeReviewCommon, which will 60 // result in json marshal/unmarshal overrides. 61 // 62 // This should be used only right before serialization, and for now it's 63 // consumed only by Deck. 64 type MinCodeReviewCommon CodeReviewCommon 65 66 // MarshalJSON marshals MinCodeReviewCommon into CodeReviewForDeck 67 func (m *MinCodeReviewCommon) MarshalJSON() ([]byte, error) { 68 min := &CodeReviewForDeck{ 69 Title: m.Title, 70 Number: m.Number, 71 HeadRefOID: m.HeadRefOID, 72 Mergeable: m.Mergeable, 73 } 74 return json.Marshal(min) 75 } 76 77 // UnmarshalJSON overrides unmarshal function, the marshalled bytes should only 78 // be used by Typescript for now 79 func (m *MinCodeReviewCommon) UnmarshalJSON(b []byte) error { 80 return errors.New("this is not implemented") 81 } 82 83 type CodeReviewCommon struct { 84 // NameWithOwner is from graphql.NameWithOwner, <org>/<repo> 85 NameWithOwner string 86 // The number of PR 87 Number int 88 Org string 89 Repo string 90 // BaseRefPrefix gets prefix of ref, such as /refs/head, /refs/tags 91 BaseRefPrefix string 92 BaseRefName string 93 HeadRefName string 94 HeadRefOID string 95 96 Title string 97 Body string 98 // AuthorLogin is the author login from the fork on GitHub, this will be the 99 // author login from Gerrit. 100 AuthorLogin string 101 UpdatedAtTime time.Time 102 103 Mergeable string 104 105 GitHub *PullRequest 106 Gerrit *gerrit.ChangeInfo 107 } 108 109 func (crc *CodeReviewCommon) logFields() logrus.Fields { 110 return logrus.Fields{ 111 "org": crc.Org, 112 "repo": crc.Repo, 113 "pr": crc.Number, 114 "branch": crc.BaseRefName, 115 "sha": crc.HeadRefOID, 116 } 117 } 118 119 // GitHubLabels returns labels struct for GitHub, using this function is almost 120 // equivalent to `if isGitHub() {// then do that}`. 121 // 122 // This is useful for determining the merging strategy. 123 func (crc *CodeReviewCommon) GitHubLabels() *Labels { 124 if crc.GitHub == nil { 125 return nil 126 } 127 return &crc.GitHub.Labels 128 } 129 130 // GitHubCommits returns Commits struct from GitHub. 131 // 132 // This is used by checking status context to determine whether the PR is ready 133 // for merge or not. 134 func (crc *CodeReviewCommon) GitHubCommits() *Commits { 135 if crc.GitHub == nil { 136 return nil 137 } 138 return &crc.GitHub.Commits 139 } 140 141 // CodeReviewCommonFromPullRequest derives CodeReviewCommon struct from GitHub 142 // PullRequest struct, by extracting shared fields among different code review 143 // providers. 144 func CodeReviewCommonFromPullRequest(pr *PullRequest) *CodeReviewCommon { 145 if pr == nil { 146 return nil 147 } 148 // Make a copy 149 prCopy := *pr 150 crc := &CodeReviewCommon{ 151 NameWithOwner: string(pr.Repository.NameWithOwner), 152 Number: int(pr.Number), 153 Org: string(pr.Repository.Owner.Login), 154 Repo: string(pr.Repository.Name), 155 BaseRefPrefix: string(pr.BaseRef.Prefix), 156 BaseRefName: string(pr.BaseRef.Name), 157 HeadRefName: string(pr.HeadRefName), 158 HeadRefOID: string(pr.HeadRefOID), 159 Title: string(pr.Title), 160 Body: string(pr.Body), 161 AuthorLogin: string(pr.Author.Login), 162 Mergeable: string(pr.Mergeable), 163 UpdatedAtTime: pr.UpdatedAt.Time, 164 165 GitHub: &prCopy, 166 } 167 168 return crc 169 } 170 171 // CodeReviewCommonFromGerrit derives CodeReviewCommon struct from Gerrit 172 // ChangeInfo struct, by extracting shared fields among different code review 173 // providers. 174 // 175 // Gerrit ChangeInfo doesn't know which host it's from, which makes sense, as 176 // host for Gerrit is like `github.com` for GitHub, so it's required to be 177 // passed in by caller. 178 func CodeReviewCommonFromGerrit(gci *gerrit.ChangeInfo, instance string) *CodeReviewCommon { 179 if gci == nil { 180 return nil 181 } 182 // Make a copy 183 gciCopy := *gci 184 185 // MergeableState is an enum with three different values: 186 // MergeableStateUnknown, MergeableStateMergeable, and 187 // MergeableStateConflicting. 188 // Ref: https://pkg.go.dev/github.com/shurcooL/githubv4#MergeableState 189 mergeable := string(githubql.MergeableStateUnknown) 190 // gci.Mergeable is only set if this feature is enabled on the Gerrit Host. 191 // https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info 192 // Mergeability can still be queried with GetMergeableInfo. 193 if gci.Mergeable { 194 mergeable = string(githubql.MergeableStateMergeable) 195 } else if gci.ContainsGitConflicts { 196 mergeable = string(githubql.MergeableStateConflicting) 197 } 198 crc := &CodeReviewCommon{ 199 NameWithOwner: instance + "/" + gci.Project, // org + "/" + repo 200 Number: gci.Number, 201 Org: instance, 202 Repo: gci.Project, 203 BaseRefPrefix: "refs/", // This will be stripped 204 BaseRefName: gci.Branch, // Target branch without `/refs/for` prefix. 205 HeadRefName: "not_applicable", // Used by GitHub status controller, not useful for Gerrit at all. 206 HeadRefOID: gci.CurrentRevision, 207 Title: gci.Subject, 208 Body: "", 209 AuthorLogin: gci.Owner.Username, 210 Mergeable: mergeable, 211 UpdatedAtTime: gci.Updated.Time, 212 213 Gerrit: &gciCopy, 214 } 215 216 return crc 217 } 218 219 // provider is the interface implemented by each source code 220 // providers, such as GitHub and Gerrit. 221 type provider interface { 222 Query() (map[string]CodeReviewCommon, error) 223 blockers() (blockers.Blockers, error) 224 isAllowedToMerge(crc *CodeReviewCommon) (string, error) 225 // GetRef returns the SHA of the given ref, such as the latest SHA for 226 // "heads/master", which tide will use for making decision on whether a 227 // prowjob was tested against latest HEAD, it has to be from remote server. 228 GetRef(org, repo, ref string) (string, error) 229 // headContexts returns Contexts from all presubmit requirements. 230 // Tide needs to know whether a PR passed all tests or not, this includes 231 // prow jobs, but also any external tests that are required by GitHub branch 232 // protection, for example GH actions. For GitHub these are all reflected on 233 // status contexts, and more importantly each prowjob is a context. For 234 // Gerrit we can transform every prow jobs into a context, and mark it 235 // optional if the prowjob doesn't vote on label that's required for 236 // merging. And also transform any other label that is not voted by prow 237 // into a context. 238 headContexts(pr *CodeReviewCommon) ([]Context, error) 239 // mergePRs attempts to merge the specified PRs and returns the prs that were successfully merged. 240 mergePRs(sp subpool, prs []CodeReviewCommon, dontUpdateStatus *threadSafePRSet) ([]CodeReviewCommon, error) 241 GetTideContextPolicy(org, repo, branch string, baseSHAGetter config.RefGetter, pr *CodeReviewCommon) (contextChecker, error) 242 prMergeMethod(crc *CodeReviewCommon) *types.PullRequestMergeType 243 244 // GetPresubmits will return all presubmits for the given identifier. This includes 245 // Presubmits that are versioned inside the tested repo, if the inrepoconfig feature 246 // is enabled. 247 // Consumers that pass in a RefGetter implementation that does a call to GitHub and who 248 // also need the result of that GitHub call just keep a pointer to its result, but must 249 // nilcheck that pointer before accessing it. 250 GetPresubmits(identifier, baseBranch string, baseSHAGetter config.RefGetter, headSHAGetters ...config.RefGetter) ([]config.Presubmit, error) 251 GetChangedFiles(org, repo string, number int) ([]string, error) 252 253 refsForJob(sp subpool, prs []CodeReviewCommon) (prowapi.Refs, error) 254 labelsAndAnnotations(instance string, jobLabels, jobAnnotations map[string]string, changes ...CodeReviewCommon) (labels, annotations map[string]string) 255 256 // jobIsRequiredByTide is defined by each provider for figuring out whether 257 // a job is required by Tide. 258 jobIsRequiredByTide(ps *config.Presubmit, pr *CodeReviewCommon) bool 259 }