sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/mergecommitblocker/mergecommitblocker.go (about)

     1  /*
     2  Copyright 2019 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 mergecommitblocker
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	"github.com/sirupsen/logrus"
    24  
    25  	"sigs.k8s.io/prow/pkg/config"
    26  	"sigs.k8s.io/prow/pkg/git/v2"
    27  	"sigs.k8s.io/prow/pkg/github"
    28  	"sigs.k8s.io/prow/pkg/labels"
    29  	"sigs.k8s.io/prow/pkg/pluginhelp"
    30  	"sigs.k8s.io/prow/pkg/plugins"
    31  )
    32  
    33  const (
    34  	// PluginName defines this plugin's registered name.
    35  	PluginName = "mergecommitblocker"
    36  )
    37  
    38  var (
    39  	commentBody = fmt.Sprintf("Adding label `%s` because PR contains merge commits, which are not allowed in this repository.\nUse `git rebase` to reapply your commits on top of the target branch. Detailed instructions for doing so can be found [here](https://git.k8s.io/community/contributors/guide/github-workflow.md#4-keep-your-branch-in-sync).", labels.MergeCommits)
    40  )
    41  
    42  // init registers out plugin as a pull request handler
    43  func init() {
    44  	plugins.RegisterPullRequestHandler(PluginName, handlePullRequest, helpProvider)
    45  }
    46  
    47  // helpProvider provides information on the plugin
    48  func helpProvider(config *plugins.Configuration, _ []config.OrgRepo) (*pluginhelp.PluginHelp, error) {
    49  	// Only the Description field is specified because this plugin is not triggered with commands and is not configurable.
    50  	return &pluginhelp.PluginHelp{
    51  		Description: fmt.Sprintf("The merge commit blocker plugin adds the %s label to pull requests that contain merge commits", labels.MergeCommits),
    52  	}, nil
    53  }
    54  
    55  type githubClient interface {
    56  	AddLabel(org, repo string, number int, label string) error
    57  	RemoveLabel(owner, repo string, number int, label string) error
    58  	GetIssueLabels(org, repo string, number int) ([]github.Label, error)
    59  	CreateComment(org, repo string, number int, comment string) error
    60  }
    61  
    62  type pruneClient interface {
    63  	PruneComments(func(ic github.IssueComment) bool)
    64  }
    65  
    66  func handlePullRequest(pc plugins.Agent, pre github.PullRequestEvent) error {
    67  	if pre.Action != github.PullRequestActionOpened &&
    68  		pre.Action != github.PullRequestActionReopened &&
    69  		pre.Action != github.PullRequestActionSynchronize {
    70  		return nil
    71  	}
    72  	cp, err := pc.CommentPruner()
    73  	if err != nil {
    74  		return err
    75  	}
    76  	return handle(pc.GitHubClient, pc.GitClient, cp, pc.Logger, &pre)
    77  }
    78  
    79  func handle(ghc githubClient, gc git.ClientFactory, cp pruneClient, log *logrus.Entry, pre *github.PullRequestEvent) error {
    80  	var (
    81  		org  = pre.PullRequest.Base.Repo.Owner.Login
    82  		repo = pre.PullRequest.Base.Repo.Name
    83  		num  = pre.PullRequest.Number
    84  	)
    85  
    86  	// Clone the repo, checkout the PR.
    87  	r, err := gc.ClientFor(org, repo)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	defer func() {
    92  		if err := r.Clean(); err != nil {
    93  			log.WithError(err).Error("Error cleaning up repo.")
    94  		}
    95  	}()
    96  	if err := r.CheckoutPullRequest(num); err != nil {
    97  		return err
    98  	}
    99  	// We are guaranteed to have both Base.SHA and Head.SHA
   100  	target, head := pre.PullRequest.Base.SHA, pre.PullRequest.Head.SHA
   101  	existMergeCommits, err := r.MergeCommitsExistBetween(target, head)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	issueLabels, err := ghc.GetIssueLabels(org, repo, num)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	hasLabel := github.HasLabel(labels.MergeCommits, issueLabels)
   110  	if hasLabel && !existMergeCommits {
   111  		log.Infof("Removing %q Label for %s/%s#%d", labels.MergeCommits, org, repo, num)
   112  		if err := ghc.RemoveLabel(org, repo, num, labels.MergeCommits); err != nil {
   113  			return err
   114  		}
   115  		cp.PruneComments(func(ic github.IssueComment) bool {
   116  			return strings.Contains(ic.Body, commentBody)
   117  		})
   118  	} else if !hasLabel && existMergeCommits {
   119  		log.Infof("Adding %q Label for %s/%s#%d", labels.MergeCommits, org, repo, num)
   120  		if err := ghc.AddLabel(org, repo, num, labels.MergeCommits); err != nil {
   121  			return err
   122  		}
   123  		msg := plugins.FormatSimpleResponse(commentBody)
   124  		return ghc.CreateComment(org, repo, num, msg)
   125  	}
   126  	return nil
   127  }