// Munge is the workhorse the will actually make updates to the PR func (NagFlakeIssues) Munge(obj *mgh.MungeObject) { if obj.IsPR() || !obj.HasLabel("kind/flake") { return } comments, err := obj.ListComments() if err != nil { glog.Error(err) return } // Use the pinger to notify assignees: // - Set time period based on configuration (at the top of this file) // - Mention list of assignees as an argument // - Start the ping timer after the last HumanActor comment // How often should we ping period := findTimePeriod(obj.Issue.Labels) // Who are we pinging who := mungerutil.GetIssueUsers(obj.Issue).Assignees.Mention().Join() // When does the pinger start startDate := c.LastComment(comments, c.HumanActor(), obj.Issue.CreatedAt) // Get a notification if it's time to ping. notif := pinger.SetTimePeriod(period).PingNotification( comments, who, startDate, ) if notif != nil { obj.WriteComment(notif.String()) } }
// Munge is the workhorse the will actually make updates to the PR func (b *BlockPath) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if obj.HasLabel(doNotMergeLabel) { return } files, err := obj.ListFiles() if err != nil { return } for _, f := range files { if matchesAny(*f.Filename, b.blockRegexp) { if matchesAny(*f.Filename, b.doNotBlockRegexp) { continue } obj.WriteComment(blockPathBody) obj.AddLabels([]string{doNotMergeLabel}) return } } }
// Munge is the workhorse the will actually make updates to the PR func (LGTMAfterCommitMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel("lgtm") { return } lastModified := obj.LastModifiedTime() lgtmTime := obj.LabelTime("lgtm") if lastModified == nil || lgtmTime == nil { glog.Errorf("PR %d unable to determine lastModified or lgtmTime", *obj.Issue.Number) return } if lastModified.After(*lgtmTime) { glog.Infof("PR: %d lgtm:%s lastModified:%s", *obj.Issue.Number, lgtmTime.String(), lastModified.String()) lgtmRemovedBody := "PR changed after LGTM, removing LGTM." if err := obj.WriteComment(lgtmRemovedBody); err != nil { return } obj.RemoveLabel("lgtm") } }
// Process does the necessary processing to compute whether to stay in // this state, or proceed to the next. func (c *ChangesNeeded) Process(obj *github.MungeObject) (State, error) { if !obj.HasLabel(labelChangesNeeded) { obj.AddLabel(labelChangesNeeded) glog.Infof("PR #%v needs changes from author", *obj.Issue.Number) } return &End{}, nil }
// Munge is the workhorse that will actually close the PRs func (CloseStalePR) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if obj.HasLabel(keepOpenLabel) { return } lastModif, err := findLastModificationTime(obj) if err != nil { glog.Errorf("Failed to find last modification: %v", err) return } closeIn := -time.Since(lastModif.Add(stalePullRequest)) inactiveFor := time.Since(*lastModif) if closeIn <= 0 { closePullRequest(obj, inactiveFor) } else if closeIn <= startWarning { checkAndWarn(obj, inactiveFor, closeIn) } else { // Pull-request is active. Do nothing } }
// Munge is the workhorse the will actually make updates to the PR func (StaleGreenCI) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel(lgtmLabel) { return } if mergeable, err := obj.IsMergeable(); !mergeable || err != nil { return } if !obj.IsStatusSuccess(requiredContexts) { return } for _, context := range requiredContexts { statusTime := obj.GetStatusTime(context) if statusTime == nil { glog.Errorf("%d: unable to determine time %q context was set", *obj.Issue.Number, context) return } if time.Since(*statusTime) > staleGreenCIHours*time.Hour { obj.WriteComment(greenMsgBody) err := obj.WaitForPending(requiredContexts) if err != nil { glog.Errorf("Failed waiting for PR to start testing: %v", err) } return } } }
// Munge is the workhorse that will actually close the PRs func (CloseStalePR) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if obj.HasLabel(keepOpenLabel) { return } lastModif, err := findLastModificationTime(obj) if err != nil { glog.Errorf("Failed to find last modification: %v", err) return } closeIn := -time.Since(lastModif.Add(stalePullRequest)) inactiveFor := time.Since(*lastModif) if closeIn <= 0 { closePullRequest(obj, inactiveFor) } else if closeIn <= startWarning { checkAndWarn(obj, inactiveFor, closeIn) } else { // Pull-request is active. Remove previous potential warning // Ignore potential errors, we just want to remove old comments ... comment, _ := findLatestWarningComment(obj) if comment != nil { obj.DeleteComment(comment) } } }
// Munge is the workhorse the will actually make updates to the PR func (p *PathLabelMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } commits, err := obj.GetCommits() if 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) && !obj.HasLabel(label) { needsLabels.Insert(label) } } } } if needsLabels.Len() != 0 { obj.AddLabels(needsLabels.List()) } }
// Munge is the workhorse the will actually make updates to the PR func (r *ReleaseNoteLabel) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if completedReleaseNoteProcess(obj) { r.ensureNoRelNoteNeededLabel(obj) return } if !r.prMustFollowRelNoteProcess(obj) { r.ensureNoRelNoteNeededLabel(obj) return } if !obj.HasLabel(releaseNoteLabelNeeded) { obj.AddLabel(releaseNoteLabelNeeded) } if !obj.HasLabel(lgtmLabel) { return } obj.WriteComment(releaseNoteBody) obj.RemoveLabel(lgtmLabel) }
// Munge is the workhorse the will actually make updates to the PR func (h LGTMHandler) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } reviewers := getReviewers(obj) if len(reviewers) == 0 { return } comments, err := getCommentsAfterLastModified(obj) if err != nil { glog.Errorf("unexpected error getting comments: %v", err) return } events, err := obj.GetEvents() if err != nil { glog.Errorf("unexpected error getting events: %v", err) return } if !obj.HasLabel(lgtmLabel) { h.addLGTMIfCommented(obj, comments, events, reviewers) return } h.removeLGTMIfCancelled(obj, comments, events, reviewers) }
// Munge is the workhorse the will actually make updates to the PR func (StalePendingCI) Munge(obj *github.MungeObject) { requiredContexts := []string{jenkinsUnitContext, jenkinsE2EContext} if !obj.IsPR() { return } if !obj.HasLabel(lgtmLabel) { return } if mergeable, err := obj.IsMergeable(); !mergeable || err != nil { return } status := obj.GetStatusState(requiredContexts) if status != "pending" { return } for _, context := range requiredContexts { statusTime := obj.GetStatusTime(context) if statusTime == nil { glog.Errorf("%d: unable to determine time %q context was set", *obj.Issue.Number, context) return } if time.Since(*statusTime) > stalePendingCIHours*time.Hour { obj.WriteComment(pendingMsgBody) return } } }
// Munge is the workhorse the will actually make updates to the PR func (PingCIMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel("lgtm") { return } mergeable, err := obj.IsMergeable() if err != nil { glog.V(2).Infof("Skipping %d - problem determining mergeability", *obj.Issue.Number) return } if !mergeable { glog.V(2).Infof("Skipping %d - not mergeable", *obj.Issue.Number) return } if state := obj.GetStatusState([]string{jenkinsCIContext, travisContext}); state != "incomplete" { glog.V(2).Info("Have %s status - skipping ping CI", jenkinsCIContext) return } state := obj.GetStatusState([]string{shippableContext, travisContext}) if state == "incomplete" { msg := "Continuous integration appears to have missed, closing and re-opening to trigger it" obj.WriteComment(msg) obj.ClosePR() time.Sleep(5 * time.Second) obj.OpenPR(10) } }
// Munge is the workhorse the will actually make updates to the PR func (p *PathLabelMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } commits, err := obj.GetCommits() if err != nil { return } needsLabels := sets.NewString() for _, c := range commits { for _, f := range c.Files { for _, lm := range p.labelMap { if lm.regexp.MatchString(*f.Filename) { if !obj.HasLabel(lm.label) { needsLabels.Insert(lm.label) } } } } } if needsLabels.Len() != 0 { obj.AddLabels(needsLabels.List()) } }
func (r *ReleaseNoteLabel) prMustFollowRelNoteProcess(obj *github.MungeObject) bool { if obj.IsForBranch("master") { return true } parents := getCherrypickParentPRs(obj, r.config) // if it has no parents it needs to follow the release note process if len(parents) == 0 { return true } for _, parent := range parents { // If the parent didn't set a release note, the CP must if !parent.HasLabel(releaseNote) && !parent.HasLabel(releaseNoteActionRequired) { if !obj.HasLabel(releaseNoteLabelNeeded) { obj.WriteComment(parentReleaseNoteBody) } return true } } // All of the parents set the releaseNote or releaseNoteActionRequired label, // so this cherrypick PR needs to do nothing. return false }
// Munge is the workhorse the will actually make updates to the PR func (LGTMAfterCommitMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel(lgtmLabel) { return } lastModified := obj.LastModifiedTime() lgtmTime := obj.LabelTime(lgtmLabel) if lastModified == nil || lgtmTime == nil { glog.Errorf("PR %d unable to determine lastModified or lgtmTime", *obj.Issue.Number) return } if lastModified.After(*lgtmTime) { glog.Infof("PR: %d lgtm:%s lastModified:%s", *obj.Issue.Number, lgtmTime.String(), lastModified.String()) body := fmt.Sprintf(lgtmRemovedBody, mungerutil.GetIssueUsers(obj.Issue).AllUsers().Mention().Join()) if err := obj.WriteComment(body); err != nil { return } obj.RemoveLabel(lgtmLabel) } }
// Process does the necessary processing to compute whether to stay in // this state, or proceed to the next. func (p *PreReview) Process(obj *githubhelper.MungeObject) (State, error) { success := true if !p.checkCLA(obj) { success = false } if !p.checkReleaseNotes(obj) { success = false } if !p.checkAssignees(obj) { success = false } if success { if obj.HasLabel(labelPreReview) { obj.RemoveLabel(labelPreReview) } return &NeedsReview{}, nil } if !obj.HasLabel(labelPreReview) { obj.AddLabel(labelPreReview) } return &End{}, nil }
// Munge is the workhorse the will actually make updates to the PR func (c *CherrypickQueue) Munge(obj *github.MungeObject) { if !obj.HasLabel(cpCandidateLabel) { return } if !obj.IsPR() { return } // This will cache the PR and events so when we try to view the queue we don't // hit github while trying to load the page obj.GetPR() num := *obj.Issue.Number c.Lock() merged, _ := obj.IsMerged() if merged { if obj.HasLabel(cpApprovedLabel) { c.mergedAndApproved[num] = obj } else { c.merged[num] = obj } } else { c.unmerged[num] = obj } c.Unlock() return }
// Munge is the workhorse the will actually make updates to the PR // The algorithm goes as: // - Initially, we build an approverSet // - Go through all comments after latest commit. // - If anyone said "/approve", add them to approverSet. // - Then, for each file, we see if any approver of this file is in approverSet and keep track of files without approval // - An approver of a file is defined as: // - Someone listed as an "approver" in an OWNERS file in the files directory OR // - in one of the file's parent directorie // - Iff all files have been approved, the bot will add the "approved" label. // - Iff a cancel command is found, that reviewer will be removed from the approverSet // and the munger will remove the approved label if it has been applied func (h *ApprovalHandler) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } files, err := obj.ListFiles() if err != nil { glog.Errorf("failed to list files in this PR: %v", err) return } comments, err := getCommentsAfterLastModified(obj) if err != nil { glog.Errorf("failed to get comments in this PR: %v", err) return } ownersMap := h.getApprovedOwners(files, createApproverSet(comments)) if err := h.updateNotification(obj, ownersMap); err != nil { return } for _, approverSet := range ownersMap { if approverSet.Len() == 0 { if obj.HasLabel(approvedLabel) { obj.RemoveLabel(approvedLabel) } return } } if !obj.HasLabel(approvedLabel) { obj.AddLabel(approvedLabel) } }
// Munge is the workhorse the will actually make updates to the PR func (b *BlockPath) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if obj.HasLabel(doNotMergeLabel) { return } commits, err := obj.GetCommits() if err != nil { return } for _, c := range commits { for _, f := range c.Files { if matchesAny(*f.Filename, b.blockRegexp) { if matchesAny(*f.Filename, b.doNotBlockRegexp) { continue } body := fmt.Sprintf(`Adding label:%s because PR changes docs prohibited to auto merge See http://kubernetes.io/editdocs/ for information about editing docs`, doNotMergeLabel) obj.WriteComment(body) obj.AddLabels([]string{doNotMergeLabel}) return } } } }
func (sq *SubmitQueue) doGithubE2EAndMerge(obj *github.MungeObject) { err := obj.Refresh() if err != nil { glog.Errorf("%d: unknown err: %v", *obj.Issue.Number, err) sq.SetMergeStatus(obj, unknown) return } if !sq.validForMerge(obj) { return } if obj.HasLabel(e2eNotRequiredLabel) { obj.MergePR("submit-queue") sq.SetMergeStatus(obj, merged) return } if err := obj.WriteComment(verifySafeToMergeBody); err != nil { glog.Errorf("%d: unknown err: %v", *obj.Issue.Number, err) sq.SetMergeStatus(obj, unknown) return } // Wait for the build to start sq.SetMergeStatus(obj, ghE2EWaitingStart) err = obj.WaitForPending([]string{sq.E2EStatusContext, sq.UnitStatusContext}) if err != nil { s := fmt.Sprintf("Failed waiting for PR to start testing: %v", err) sq.SetMergeStatus(obj, s) return } // Wait for the status to go back to something other than pending sq.SetMergeStatus(obj, ghE2ERunning) err = obj.WaitForNotPending([]string{sq.E2EStatusContext, sq.UnitStatusContext}) if err != nil { s := fmt.Sprintf("Failed waiting for PR to finish testing: %v", err) sq.SetMergeStatus(obj, s) return } // Check if the thing we care about is success if ok := obj.IsStatusSuccess([]string{sq.E2EStatusContext, sq.UnitStatusContext}); !ok { sq.SetMergeStatus(obj, ghE2EFailed) return } if !sq.e2eStable() { sq.SetMergeStatus(obj, e2eFailure) return } obj.MergePR("submit-queue") sq.updateMergeRate() sq.SetMergeStatus(obj, merged) return }
// Munge is unused by this munger. func (cla *ClaMunger) Munge(obj *githubhelper.MungeObject) { if !obj.IsPR() { return } if obj.HasLabel(claHumanLabel) { return } status := obj.GetStatusState([]string{cla.CLAStatusContext}) // Check for pending status and exit. if status == contextPending { // do nothing and wait for state to be updated. return } if status == contextSuccess { if obj.HasLabel(cncfClaYesLabel) { // status is success and we've already applied 'cncf-cla: yes'. return } if obj.HasLabel(cncfClaNoLabel) { obj.RemoveLabel(cncfClaNoLabel) } obj.AddLabel(cncfClaYesLabel) return } // If we are here, that means that the context is failure/error. comments, err := obj.ListComments() if err != nil { glog.Error(err) return } who := mungerutil.GetIssueUsers(obj.Issue).Author.Mention().Join() // Get a notification if it's time to ping. notif := cla.pinger.PingNotification( comments, who, nil, ) if notif != nil { obj.WriteComment(notif.String()) } if obj.HasLabel(cncfClaNoLabel) { // status reported error/failure and we've already applied 'cncf-cla: no' label. return } if obj.HasLabel(cncfClaYesLabel) { obj.RemoveLabel(cncfClaYesLabel) } obj.AddLabel(cncfClaNoLabel) }
// Munge is the workhorse the will actually make updates to the PR func (c *ClearPickAfterMerge) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel(cpCandidateLabel) { return } if merged, err := obj.IsMerged(); !merged || err != nil { return } releaseMilestone := obj.ReleaseMilestone() if releaseMilestone == "" || len(releaseMilestone) != 4 { glog.Errorf("Found invalid milestone: %q", releaseMilestone) return } rel := releaseMilestone[1:] branch := "release-" + rel sha := obj.MergeCommit() if sha == nil { glog.Errorf("Unable to get SHA of merged %d", sha) return } logMsg := fmt.Sprintf("Merge pull request #%d from ", *obj.Issue.Number) bLogMsg := []byte(logMsg) cherrypickMsg := fmt.Sprintf("(cherry picked from commit %s)", *sha) args := []string{"log", "--pretty=tformat:%H%n%s%n%b", "--grep", cherrypickMsg, "origin/" + branch} out, err := c.features.Repos.GitCommand(args) if err != nil { glog.Errorf("Error grepping for cherrypick -x message out=%q: %v", string(out), err) return } if bytes.Contains(out, bLogMsg) { glog.Infof("Found cherry-pick using -x information") handleFound(obj, out, branch) return } args = []string{"log", "--pretty=tformat:%H%n%s%n%b", "--grep", logMsg, "origin/" + branch} out, err = c.features.Repos.GitCommand(args) if err != nil { glog.Errorf("Error grepping for log message out=%q: %v", string(out), err) return } if bytes.Contains(out, bLogMsg) { glog.Infof("Found cherry-pick using log matching") handleFound(obj, out, branch) return } return }
func (sq *SubmitQueue) isStaleWhitelistComment(obj *github.MungeObject, comment *githubapi.IssueComment) bool { if *comment.Body != notInWhitelistBody { return false } stale := obj.HasLabel(okToMergeLabel) if stale { glog.V(6).Infof("Found stale SubmitQueue Whitelist comment") } return stale }
func (sq *SubmitQueue) requiredStatusContexts(obj *github.MungeObject) []string { contexts := sq.RequiredStatusContexts if len(sq.E2EStatusContext) > 0 && !obj.HasLabel(e2eNotRequiredLabel) { contexts = append(contexts, sq.E2EStatusContext) } if len(sq.UnitStatusContext) > 0 { contexts = append(contexts, sq.UnitStatusContext) } return contexts }
func (LabelUnapprovedPicks) isStaleComment(obj *github.MungeObject, comment *githubapi.IssueComment) bool { if *comment.Body != labelUnapprovedBody { return false } stale := obj.HasLabel(cpApprovedLabel) if stale { glog.V(6).Infof("Found stale LabelUnapprovedPicks comment") } return stale }
func (b *BlockPath) isStaleComment(obj *github.MungeObject, comment *githubapi.IssueComment) bool { if *comment.Body != blockPathBody { return false } stale := !obj.HasLabel(doNotMergeLabel) if stale { glog.V(6).Infof("Found stale BlockPath comment") } return stale }
func (NeedsRebaseMunger) isStaleComment(obj *github.MungeObject, comment *githubapi.IssueComment) bool { if !rebaseRE.MatchString(*comment.Body) { return false } stale := !obj.HasLabel(needsRebaseLabel) if stale { glog.V(6).Infof("Found stale NeedsRebaseMunger comment") } return stale }
func priority(obj *github.MungeObject) int { // jump to the front of the queue if you don't need retested if obj.HasLabel(retestNotRequiredLabel) || obj.HasLabel(retestNotRequiredDocsOnlyLabel) { return retestNotRequiredMergePriority } prio := obj.Priority() // eparis randomly decided that unlabel issues count at p3 if prio == math.MaxInt32 { return defaultMergePriority } return prio }
// Munge is the workhorse the will actually make updates to the PR func (c *CherrypickAutoApprove) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if obj.IsForBranch("master") { return } if obj.HasLabel(cpApprovedLabel) && obj.ReleaseMilestone() != "" { return } parents := getCherrypickParentPRs(obj, c.config) if len(parents) == 0 { return } major := 0 minor := 0 branch := obj.Branch() if l, err := fmt.Sscanf(branch, "release-%d.%d", &major, &minor); err != nil || l != 2 { return } branchImpliedMilestone := fmt.Sprintf("v%d.%d", major, minor) milestone := obj.ReleaseMilestone() if milestone != "" && milestone != branchImpliedMilestone { glog.Errorf("Found PR %d on branch %q but have milestone %q", *obj.Issue.Number, branch, milestone) return } for _, parent := range parents { if !parent.HasLabel(cpApprovedLabel) { return } // If the parent was for milestone v1.2 but this PR has // comments saying it was 'on branch release-1.1' we should // not auto approve parentMilestone := parent.ReleaseMilestone() if parentMilestone != branchImpliedMilestone { glog.Errorf("%d: parentReleaseMilestone=%q but branch is %q", *obj.Issue.Number, parentMilestone, obj.Branch()) return } } if milestone == "" { obj.SetMilestone(branchImpliedMilestone) } if !obj.HasLabel(cpApprovedLabel) { obj.AddLabel(cpApprovedLabel) } }
// Munge is the workhorse the will actually make updates to the PR func (OkToTestMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel(lgtmLabel) { return } state := obj.GetStatusState(requiredContexts) if state == "incomplete" { glog.V(2).Infof("status is incomplete, adding ok to test") obj.WriteComment(okToTestBody) } }