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 }