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  }