// MungePullRequest is the workhorse the will actually make updates to the PR func (s *SizeMunger) MungePullRequest(config *config.MungeConfig, pr *github.PullRequest, issue *github.Issue, commits []github.RepositoryCommit, events []github.IssueEvent) { s.getGeneratedFiles(config) genFiles := *s.genFiles genPrefixes := *s.genPrefixes if pr.Additions == nil { glog.Warningf("PR %d has nil Additions", *pr.Number) return } adds := *pr.Additions if pr.Deletions == nil { glog.Warningf("PR %d has nil Deletions", *pr.Number) return } dels := *pr.Deletions for _, c := range commits { for _, f := range c.Files { for _, p := range genPrefixes { if strings.HasPrefix(*f.Filename, p) { adds = adds - *f.Additions dels = dels - *f.Deletions continue } } if genFiles.Has(*f.Filename) { adds = adds - *f.Additions dels = dels - *f.Deletions continue } } } newSize := calculateSize(adds, dels) newLabel := labelSizePrefix + newSize existing := github_util.GetLabelsWithPrefix(issue.Labels, labelSizePrefix) needsUpdate := true for _, l := range existing { if l == newLabel { needsUpdate = false continue } config.RemoveLabel(*pr.Number, l) } if needsUpdate { config.AddLabels(*pr.Number, []string{newLabel}) body := fmt.Sprintf("Labelling this PR as %s", newLabel) config.WriteComment(*pr.Number, body) } }
func (NeedsRebaseMunger) MungePullRequest(config *config.MungeConfig, pr *github.PullRequest, issue *github.Issue, commits []github.RepositoryCommit, events []github.IssueEvent) { mergeable, err := config.IsPRMergeable(pr) if err != nil { glog.V(2).Infof("Skipping %d - problem determining mergeable", *pr.Number) return } if mergeable && github_util.HasLabel(issue.Labels, needsRebase) { config.RemoveLabel(*pr.Number, needsRebase) } if !mergeable && !github_util.HasLabel(issue.Labels, needsRebase) { config.AddLabels(*pr.Number, []string{needsRebase}) } }
func doMungers(config *config.MungeConfig) error { if len(config.IssueMungers) == 0 && len(config.PRMungersList) == 0 { glog.Fatalf("must include at least one --issue-mungers or --pr-mungers") } for { nextRunStartTime := time.Now().Add(config.Period) if len(config.IssueMungers) > 0 { glog.Infof("Running issue mungers") if err := issues.MungeIssues(config); err != nil { glog.Errorf("Error munging issues: %v", err) } } if len(config.PRMungersList) > 0 { glog.Infof("Running PR mungers") if err := pulls.MungePullRequests(config); err != nil { glog.Errorf("Error munging PRs: %v", err) } } config.ResetAPICount() if config.Once { break } if nextRunStartTime.After(time.Now()) { sleepDuration := nextRunStartTime.Sub(time.Now()) glog.Infof("Sleeping for %v\n", sleepDuration) time.Sleep(sleepDuration) } else { glog.Infof("Not sleeping as we took more than %v to complete one loop\n", config.Period) } } return nil }
func (b *BlunderbussMunger) MungePullRequest(config *config.MungeConfig, pr *github.PullRequest, issue *github.Issue, commits []github.RepositoryCommit, events []github.IssueEvent) { if b.config == nil { b.loadConfig() } if !b.blunderbussReassign && issue.Assignee != nil { glog.V(6).Infof("skipping %v: reassign: %v assignee: %v", *pr.Number, b.blunderbussReassign, describeUser(issue.Assignee)) return } potentialOwners := weightMap{} weightSum := int64(0) for _, commit := range commits { if commit.Author == nil || commit.Author.Login == nil || commit.SHA == nil { glog.Warningf("Skipping invalid commit for %d: %#v", *pr.Number, commit) continue } for _, file := range commit.Files { fileWeight := int64(1) if file.Changes != nil && *file.Changes != 0 { fileWeight = int64(*file.Changes) } // Judge file size on a log scale-- effectively this // makes three buckets, we shouldn't have many 10k+ // line changes. fileWeight = int64(math.Log10(float64(fileWeight))) + 1 fileOwners := b.config.FindOwners(*file.Filename) if len(fileOwners) == 0 { glog.Warningf("Couldn't find an owner for: %s", *file.Filename) } for owner, ownerWeight := range fileOwners { if owner == *pr.User.Login { continue } potentialOwners[owner] = potentialOwners[owner] + fileWeight*ownerWeight weightSum += fileWeight * ownerWeight } } } if len(potentialOwners) == 0 { glog.Errorf("No owners found for PR %d", *pr.Number) return } glog.V(4).Infof("Weights: %#v\nSum: %v", potentialOwners, weightSum) if issue.Assignee != nil { cur := *issue.Assignee.Login glog.Infof("Current assignee %v has a %02.2f%% chance of having been chosen", cur, 100.0*float64(potentialOwners[cur])/float64(weightSum)) } selection := rand.Int63n(weightSum) owner := "" for o, w := range potentialOwners { owner = o selection -= w if selection <= 0 { break } } glog.Infof("Assigning %v to %v (previously assigned to %v)", *pr.Number, owner, describeUser(issue.Assignee)) config.AssignPR(*pr.Number, owner) }
// getGeneratedFiles returns a list of all automatically generated files in the repo. These include // docs, deep_copy, and conversions // // It would be 'better' to call this for every commit but that takes // a whole lot of time for almost always the same information, and if // our results are slightly wrong, who cares? Instead look for the // generated files once and if someone changed what files are generated // we'll size slightly wrong. No biggie. func (s *SizeMunger) getGeneratedFiles(config *config.MungeConfig) { if s.genFiles != nil { return } files := sets.NewString() prefixes := []string{} s.genFiles = &files s.genPrefixes = &prefixes file := s.generatedFilesFile if len(file) == 0 { glog.Infof("No --generated-files-config= supplied, applying no labels") return } fp, err := os.Open(file) if err != nil { glog.Errorf("Unable to open %q: %v", file, err) return } defer fp.Close() scanner := bufio.NewScanner(fp) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "#") || line == "" { continue } fields := strings.Fields(line) if len(fields) != 2 { glog.Errorf("Invalid line in generated docs config %s: %q", file, line) continue } eType := fields[0] file := fields[1] if eType == "prefix" { prefixes = append(prefixes, file) } else if eType == "path" { files.Insert(file) } else if eType == "paths-from-repo" { docs, err := config.GetFileContents(file, "") if err != nil { continue } docSlice := strings.Split(docs, "\n") files.Insert(docSlice...) } else { glog.Errorf("Invalid line in generated docs config, unknown type: %s, %q", eType, line) continue } } if scanner.Err() != nil { glog.Errorf("Error scanning %s: %v", file, err) return } s.genFiles = &files s.genPrefixes = &prefixes return }
func (OkToTestMunger) MungePullRequest(config *config.MungeConfig, pr *github.PullRequest, issue *github.Issue, commits []github.RepositoryCommit, events []github.IssueEvent) { if !github_util.HasLabel(issue.Labels, "lgtm") { return } status, err := config.GetStatus(*pr.Number, []string{"Jenkins GCE e2e"}) if err != nil { glog.Errorf("unexpected error getting status: %v", err) return } if status == "incomplete" { glog.V(2).Infof("status is incomplete, adding ok to test") msg := `@k8s-bot ok to test pr builder appears to be missing, activating due to 'lgtm' label.` config.WriteComment(*pr.Number, msg) } }
func MungeIssues(config *config.MungeConfig) error { mfunc := func(pr *github_api.PullRequest, issue *github_api.Issue) error { return mungeIssue(config, pr, issue) } if err := config.ForEachIssueDo([]string{}, mfunc); err != nil { return err } return nil }
// MungePullRequest is the workhorse the will actually make updates to the PR func (LGTMAfterCommitMunger) MungePullRequest(config *config.MungeConfig, pr *github.PullRequest, issue *github.Issue, commits []github.RepositoryCommit, events []github.IssueEvent) { lastModified := lastModifiedTime(commits) lgtmTime := lgtmTime(events) if lastModified == nil || lgtmTime == nil { return } if !github_util.HasLabel(issue.Labels, "lgtm") { return } if lastModified.After(*lgtmTime) { lgtmRemovedBody := "PR changed after LGTM, removing LGTM." if err := config.WriteComment(*pr.Number, lgtmRemovedBody); err != nil { return } config.RemoveLabel(*pr.Number, "lgtm") } }
func (PingCIMunger) MungePullRequest(config *config.MungeConfig, pr *github.PullRequest, issue *github.Issue, commits []github.RepositoryCommit, events []github.IssueEvent) { if !github_util.HasLabel(issue.Labels, "lgtm") { return } if mergeable, err := config.IsPRMergeable(pr); err != nil { glog.V(2).Infof("Skipping %d - problem determining mergeability", *pr.Number) } else if !mergeable { glog.V(2).Infof("Skipping %d - not mergeable", *pr.Number) } status, err := config.GetStatus(pr, []string{"Shippable", "continuous-integration/travis-ci/pr"}) if err != nil { glog.Errorf("unexpected error getting status: %v", err) return } if status == "incomplete" { glog.V(2).Infof("status is incomplete, closing and re-opening") msg := "Continuous integration appears to have missed, closing and re-opening to trigger it" config.WriteComment(*pr.Number, msg) config.ClosePR(pr) time.Sleep(5 * time.Second) config.OpenPR(pr, 10) } }
func mungePR(config *config.MungeConfig, pr *github_api.PullRequest, issue *github_api.Issue) error { if pr == nil { fmt.Printf("found nil pr\n") } mungers, err := getMungers(config.PRMungersList) if err != nil { return err } commits, err := config.GetFilledCommits(*pr.Number) if err != nil { return err } events, err := config.GetAllEventsForPR(*pr.Number) if err != nil { return err } for _, munger := range mungers { munger.MungePullRequest(config, pr, issue, commits, events) } return nil }
// getGeneratedFiles returns a list of all automatically generated files in the repo. These include // docs, deep_copy, and conversions func getGeneratedFiles(config *config.MungeConfig, c github.RepositoryCommit) []string { genFiles := []string{ "pkg/api/v1/deep_copy_generated.go", "pkg/api/deep_copy_generated.go", "pkg/expapi/v1/deep_copy_generated.go", "pkg/expapi/deep_copy_generated.go", "pkg/api/v1/conversion_generated.go", "pkg/expapi/v1/conversion_generated.go", "api/swagger-spec/resourceListing.json", "api/swagger-spec/version.json", "api/swagger-spec/api.json", "api/swagger-spec/v1.json", } docs, err := config.GetFileContents(".generated_docs", *c.SHA) if err != nil { docs = "" } docSlice := strings.Split(docs, "\n") genFiles = append(genFiles, docSlice...) return genFiles }
// MungePullRequest is the workhorse the will actually make updates to the PR func (p *PathLabelMunger) MungePullRequest(config *config.MungeConfig, pr *github.PullRequest, issue *github.Issue, commits []github.RepositoryCommit, events []github.IssueEvent) { if p.labelMap == nil { if err := p.loadPathMap(); err != nil { return } } labelMap := *p.labelMap needsLabels := sets.NewString() for _, c := range commits { for _, f := range c.Files { for prefix, label := range labelMap { if strings.HasPrefix(*f.Filename, prefix) && !github_util.HasLabel(issue.Labels, label) { needsLabels.Insert(label) } } } } if needsLabels.Len() != 0 { config.AddLabels(*pr.Number, needsLabels.List()) } }
func (PRSizeMunger) MungePullRequest(config *config.MungeConfig, pr *github.PullRequest, issue *github.Issue, commits []github.RepositoryCommit, events []github.IssueEvent) { if pr.Additions == nil { glog.Warningf("PR %d has nil Additions", *pr.Number) return } if pr.Deletions == nil { glog.Warningf("PR %d has nil Deletions", *pr.Number) return } adds := *pr.Additions dels := *pr.Deletions // It would be 'better' to call this for every commit but that takes // a whole lot of time for almost always the same information, and if // our results are slightly wrong, who cares? Instead look for the // generated files once per PR and if someone changed both what files // are generated and then undid that change in an intermediate commit // we might call this PR bigger than we "should." genFiles := getGeneratedFiles(config, commits[len(commits)-1]) for _, c := range commits { for _, f := range c.Files { if strings.HasPrefix(*f.Filename, "Godeps/") { adds = adds - *f.Additions dels = dels - *f.Deletions continue } found := false for _, genFile := range genFiles { if *f.Filename == genFile { adds = adds - *f.Additions dels = dels - *f.Deletions found = true break } } if found { continue } } } newSize := calculateSize(adds, dels) newLabel := labelSizePrefix + newSize existing := github_util.GetLabelsWithPrefix(issue.Labels, labelSizePrefix) needsUpdate := true for _, l := range existing { if l == newLabel { needsUpdate = false continue } config.RemoveLabel(*pr.Number, l) } if needsUpdate { config.AddLabels(*pr.Number, []string{newLabel}) body := fmt.Sprintf("Labelling this PR as %s", newLabel) config.WriteComment(*pr.Number, body) } }