github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/merge-method-comment/merge-method-comment.go (about)

     1  /*
     2  Copyright 2020 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 mergemethodcomment contains a Prow plugin which comments on PRs with
    18  // 2 or more commits, informing the user:
    19  // - How to request commits to be squashed if default merge method is merge,
    20  // - How to request commits to be merged if the repo squashes commits by default,
    21  // - That the commits will be merged/squashed if it is not possible to override
    22  // the default merge method.
    23  package mergemethodcomment
    24  
    25  import (
    26  	"fmt"
    27  	"strings"
    28  
    29  	"sigs.k8s.io/prow/pkg/config"
    30  	"sigs.k8s.io/prow/pkg/git/types"
    31  	"sigs.k8s.io/prow/pkg/github"
    32  	"sigs.k8s.io/prow/pkg/pluginhelp"
    33  	"sigs.k8s.io/prow/pkg/plugins"
    34  )
    35  
    36  const pluginName = "merge-method-comment"
    37  
    38  // Strict subset of github.Client methods.
    39  type githubClient interface {
    40  	ListIssueComments(org, repo string, number int) ([]github.IssueComment, error)
    41  	CreateComment(org, repo string, number int, comment string) error
    42  	BotUserChecker() (func(candidate string) bool, error)
    43  }
    44  
    45  func init() {
    46  	plugins.RegisterPullRequestHandler(pluginName, handlePullRequest, helpProvider)
    47  }
    48  
    49  func helpProvider(config *plugins.Configuration, _ []config.OrgRepo) (*pluginhelp.PluginHelp, error) {
    50  	return &pluginhelp.PluginHelp{
    51  			Description: "The merge-method-comment plugin adds a comment on how to request a different-from-default merge method to PRs with more than 1 commit",
    52  		},
    53  		nil
    54  }
    55  
    56  func handlePullRequest(pc plugins.Agent, pe github.PullRequestEvent) error {
    57  	return handlePR(pc.GitHubClient, pc.Config.ProwConfig.Tide, pe)
    58  }
    59  
    60  func handlePR(gc githubClient, c config.Tide, pe github.PullRequestEvent) error {
    61  	if !isPRChanged(pe) {
    62  		return nil
    63  	}
    64  
    65  	commentNeeded, comment := needsComment(c, pe)
    66  	if !commentNeeded {
    67  		return nil
    68  	}
    69  
    70  	owner := pe.PullRequest.Base.Repo.Owner.Login
    71  	repo := pe.PullRequest.Base.Repo.Name
    72  	num := pe.PullRequest.Number
    73  
    74  	hasComment, err := issueHasComment(gc, owner, repo, num, comment)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	if hasComment {
    79  		return nil
    80  	}
    81  
    82  	return gc.CreateComment(owner, repo, num, plugins.FormatSimpleResponse(comment))
    83  }
    84  
    85  func needsComment(c config.Tide, pe github.PullRequestEvent) (bool, string) {
    86  	if pe.PullRequest.Commits <= 1 {
    87  		return false, ""
    88  	}
    89  
    90  	orgRepo := config.OrgRepo{
    91  		Org:  pe.PullRequest.Base.Repo.Owner.Login,
    92  		Repo: pe.PullRequest.Base.Repo.Name,
    93  	}
    94  	method := c.MergeMethod(orgRepo)
    95  	comment := fmt.Sprintf("This PR has multiple commits, and the default merge method is: %s.\n", method)
    96  
    97  	switch {
    98  	case method == types.MergeSquash && c.MergeLabel != "":
    99  		comment = fmt.Sprintf("%sYou can request commits to be merged using the label: %s", comment, c.MergeLabel)
   100  	case method == types.MergeSquash && c.MergeLabel == "":
   101  		comment = comment + "Commits will be squashed, as no merge labels are defined"
   102  	case method == types.MergeMerge && c.SquashLabel != "":
   103  		comment = fmt.Sprintf("%sYou can request commits to be squashed using the label: %s", comment, c.SquashLabel)
   104  	case method == types.MergeMerge && c.SquashLabel == "":
   105  		comment = comment + "Commits will be merged, as no squash labels are defined"
   106  	}
   107  
   108  	return true, comment
   109  }
   110  
   111  func issueHasComment(gc githubClient, org, repo string, number int, comment string) (bool, error) {
   112  	botNameChecker, err := gc.BotUserChecker()
   113  	if err != nil {
   114  		return false, err
   115  	}
   116  
   117  	comments, err := gc.ListIssueComments(org, repo, number)
   118  	if err != nil {
   119  		return false, fmt.Errorf("error listing issue comments: %w", err)
   120  	}
   121  
   122  	for _, c := range comments {
   123  		if botNameChecker(c.User.Login) && strings.Contains(c.Body, comment) {
   124  			return true, nil
   125  		}
   126  	}
   127  	return false, nil
   128  }
   129  
   130  // These are the only actions indicating the code diffs may have changed.
   131  func isPRChanged(pe github.PullRequestEvent) bool {
   132  	switch pe.Action {
   133  	case github.PullRequestActionOpened:
   134  		return true
   135  	case github.PullRequestActionReopened:
   136  		return true
   137  	case github.PullRequestActionSynchronize:
   138  		return true
   139  	case github.PullRequestActionEdited:
   140  		return true
   141  	default:
   142  		return false
   143  	}
   144  }