github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/prow/plugins/size/size.go (about)

     1  /*
     2  Copyright 2016 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 size contains a Prow plugin which counts the number of lines changed
    18  // in a pull request, buckets this number into a few size classes (S, L, XL, etc),
    19  // and finally labels the pull request with this size.
    20  package size
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  
    26  	"github.com/sirupsen/logrus"
    27  
    28  	"k8s.io/test-infra/prow/genfiles"
    29  	"k8s.io/test-infra/prow/github"
    30  	"k8s.io/test-infra/prow/plugins"
    31  )
    32  
    33  const pluginName = "size"
    34  
    35  func init() {
    36  	plugins.RegisterPullRequestHandler(pluginName, handlePullRequest)
    37  }
    38  
    39  func handlePullRequest(pc plugins.PluginClient, pe github.PullRequestEvent) error {
    40  	return handlePR(pc.GitHubClient, pc.Logger, pe)
    41  }
    42  
    43  // Strict subset of *github.Client methods.
    44  type githubClient interface {
    45  	AddLabel(owner, repo string, number int, label string) error
    46  	RemoveLabel(owner, repo string, number int, label string) error
    47  	GetIssueLabels(org, repo string, number int) ([]github.Label, error)
    48  	GetFile(org, repo, filepath, commit string) ([]byte, error)
    49  	GetPullRequestChanges(org, repo string, number int) ([]github.PullRequestChange, error)
    50  }
    51  
    52  func handlePR(gc githubClient, le *logrus.Entry, pe github.PullRequestEvent) error {
    53  	if !isPRChanged(pe) {
    54  		return nil
    55  	}
    56  
    57  	var (
    58  		owner = pe.PullRequest.Base.Repo.Owner.Login
    59  		repo  = pe.PullRequest.Base.Repo.Name
    60  		num   = pe.PullRequest.Number
    61  		sha   = pe.PullRequest.Base.SHA
    62  	)
    63  
    64  	g, err := genfiles.NewGroup(gc, owner, repo, sha)
    65  	if err != nil {
    66  		switch err.(type) {
    67  		case *genfiles.ParseError:
    68  			// Continue on parse errors, but warn that something is wrong.
    69  			le.Warnf("error while parsing .generated_files: %v", err)
    70  		default:
    71  			return err
    72  		}
    73  	}
    74  
    75  	changes, err := gc.GetPullRequestChanges(owner, repo, num)
    76  	if err != nil {
    77  		return fmt.Errorf("can not get PR changes for size plugin: %v", err)
    78  	}
    79  
    80  	var count int
    81  	for _, change := range changes {
    82  		if g.Match(change.Filename) {
    83  			continue
    84  		}
    85  
    86  		count += change.Additions + change.Deletions
    87  	}
    88  
    89  	labels, err := gc.GetIssueLabels(owner, repo, num)
    90  	if err != nil {
    91  		le.Warnf("while retrieving labels, error: %v", err)
    92  	}
    93  
    94  	newLabel := bucket(count).label()
    95  	var hasLabel bool
    96  
    97  	for _, label := range labels {
    98  		if label.Name == newLabel {
    99  			hasLabel = true
   100  			continue
   101  		}
   102  
   103  		if strings.HasPrefix(label.Name, labelPrefix) {
   104  			if err := gc.RemoveLabel(owner, repo, num, label.Name); err != nil {
   105  				le.Warnf("error while removing label %q: %v", label.Name, err)
   106  			}
   107  		}
   108  	}
   109  
   110  	if hasLabel {
   111  		return nil
   112  	}
   113  
   114  	if err := gc.AddLabel(owner, repo, num, newLabel); err != nil {
   115  		return fmt.Errorf("error adding label to %s/%s PR #%d: %v", owner, repo, num, err)
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // One of a set of discrete buckets.
   122  type size int
   123  
   124  const (
   125  	sizeXS size = iota
   126  	sizeS
   127  	sizeM
   128  	sizeL
   129  	sizeXL
   130  	sizeXXL
   131  )
   132  
   133  const (
   134  	labelPrefix = "size/"
   135  
   136  	labelXS     = "size/XS"
   137  	labelS      = "size/S"
   138  	labelM      = "size/M"
   139  	labelL      = "size/L"
   140  	labelXL     = "size/XL"
   141  	labelXXL    = "size/XXL"
   142  	labelUnkown = "size/?"
   143  )
   144  
   145  func (s size) label() string {
   146  	switch s {
   147  	case sizeXS:
   148  		return labelXS
   149  	case sizeS:
   150  		return labelS
   151  	case sizeM:
   152  		return labelM
   153  	case sizeL:
   154  		return labelL
   155  	case sizeXL:
   156  		return labelXL
   157  	case sizeXXL:
   158  		return labelXXL
   159  	}
   160  
   161  	return labelUnkown
   162  }
   163  
   164  func bucket(lineCount int) size {
   165  	if lineCount < 10 {
   166  		return sizeXS
   167  	} else if lineCount < 30 {
   168  		return sizeS
   169  	} else if lineCount < 100 {
   170  		return sizeM
   171  	} else if lineCount < 500 {
   172  		return sizeL
   173  	} else if lineCount < 1000 {
   174  		return sizeXL
   175  	}
   176  
   177  	return sizeXXL
   178  }
   179  
   180  // These are the only actions indicating the code diffs may have changed.
   181  func isPRChanged(pe github.PullRequestEvent) bool {
   182  	switch pe.Action {
   183  	case github.PullRequestActionOpened:
   184  		return true
   185  	case github.PullRequestActionReopened:
   186  		return true
   187  	case github.PullRequestActionSynchronize:
   188  		return true
   189  	case github.PullRequestActionEdited:
   190  		return true
   191  	default:
   192  		return false
   193  	}
   194  }