// Process does the necessary processing to compute whether to stay in // this state, or proceed to the next. func (nr *NeedsReview) Process(obj *github.MungeObject) (State, error) { if nr.checkLGTM(obj) { if obj.HasLabel(labelNeedsReview) { obj.RemoveLabel(labelNeedsReview) } if obj.HasLabel(labelChangesNeeded) { obj.RemoveLabel(labelChangesNeeded) } return &End{}, nil } reviewerActionNeeded, err := isReviewerActionNeeded(obj) if err != nil { return &End{}, err } if !reviewerActionNeeded { if obj.HasLabel(labelNeedsReview) { obj.RemoveLabel(labelNeedsReview) } return &ChangesNeeded{}, nil } if !obj.HasLabel(labelNeedsReview) { glog.Infof("PR #%v needs reviewer action", *obj.Issue.Number) obj.AddLabel(labelNeedsReview) } return &End{}, nil }
// 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 }
// 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 }
func getHumanCorrectedLabel(obj *github.MungeObject, s string) *string { myEvents, err := obj.GetEvents() if err != nil { glog.Errorf("Could not get the events associated with Issue %d", obj.Issue.Number) return nil } botEvents := event.FilterEvents(myEvents, event.And([]event.Matcher{event.BotActor(), event.AddLabel{}, event.LabelPrefix(s)})) if botEvents.Empty() { return nil } humanEventsAfter := event.FilterEvents( myEvents, event.And([]event.Matcher{ event.HumanActor(), event.AddLabel{}, event.LabelPrefix(s), event.CreatedAfter(*botEvents.GetLast().CreatedAt), }), ) if humanEventsAfter.Empty() { return nil } lastHumanLabel := humanEventsAfter.GetLast() glog.Infof("Recopying human-added label: %s for PR %d", *lastHumanLabel.Label.Name, *obj.Issue.Number) obj.RemoveLabel(*lastHumanLabel.Label.Name) obj.AddLabel(*lastHumanLabel.Label.Name) return lastHumanLabel.Label.Name }
// 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) }
func (h *LGTMHandler) addLGTMIfCommented(obj *github.MungeObject, comments []*githubapi.IssueComment, reviewers mungerutil.UserSet) { // Assumption: The comments should be sorted (by default from github api) from oldest to latest for i := len(comments) - 1; i >= 0; i-- { comment := comments[i] if !mungerutil.IsValidUser(comment.User) { continue } // TODO: An approver should be acceptable. // See https://github.com/kubernetes/contrib/pull/1428#discussion_r72563935 if !mungerutil.IsMungeBot(comment.User) && !isReviewer(comment.User, reviewers) { continue } fields := getFields(*comment.Body) if isCancelComment(fields) { // "/lgtm cancel" if commented more recently than "/lgtm" return } if !isLGTMComment(fields) { continue } // TODO: support more complex policies for multiple reviewers. // See https://github.com/kubernetes/contrib/issues/1389#issuecomment-235161164 glog.Infof("Adding lgtm label. Reviewer (%s) LGTM", *comment.User.Login) obj.AddLabel(lgtmLabel) 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 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 *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) } }
// syncPriority will sync the input priority to the issue if the input priority is higher than the existing ones func (s *IssueSyncer) syncPriority(obj *github.MungeObject, priority Priority) error { if obj.Priority() <= priority.Priority() { return nil } plabels := github.GetLabelsWithPrefix(obj.Issue.Labels, priorityPrefix) err := obj.AddLabel(priority.String()) if err != nil { return nil } for _, l := range plabels { err = obj.RemoveLabel(l) if err != nil { return err } } return nil }
// Munge is the workhorse the will actually make updates to the PR func (DocsNeedNoRetest) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } files, err := obj.ListFiles() if err != nil { glog.Errorf("Failed to list files for PR %d: %s", obj.Issue.Number, err) } docsOnly := areFilesDocOnly(files) if docsOnly && !obj.HasLabel(labelSkipRetest) { obj.AddLabel(labelSkipRetest) } else if !docsOnly && obj.HasLabel(labelSkipRetest) { obj.RemoveLabel(labelSkipRetest) } }
// Munge is unused by this munger. func (c *ClaMunger) Munge(obj *githubhelper.MungeObject) { if !obj.IsPR() { return } if obj.HasLabel(claHumanLabel) { return } status := obj.GetStatusState([]string{c.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 obj.HasLabel(cncfClaNoLabel) { // status reported error/failure and we've already applied 'cncf-cla: no' label. return } // Write comment and then modify the labels. err := obj.WriteComment(fmt.Sprint(cncfclaNotFoundMessage, mungerutil.GetIssueUsers(obj.Issue).Author.Mention().Join())) if err != nil { return } if obj.HasLabel(cncfClaYesLabel) { obj.RemoveLabel(cncfClaYesLabel) } obj.AddLabel(cncfClaNoLabel) }
// Munge is the workhorse the will actually make updates to the PR // The algorithm goes as: // - Initially, we set up approverSet // - Go through all comments after latest commit. If any approver said "/approve", add him to approverSet. // - For each file, we see if any approver of this file is in approverSet. // - An approver of a file is defined as: // - It's known that each dir has a list of approvers. (This might not hold true. For usability, current situation is enough.) // - Approver of a dir is also the approver of child dirs. // - We look at top N (default 3) level dir approvers. For example, for file "/a/b/c/d/e", we might search for approver from // "/", "/a/", "/a/b/" // - Iff all files has been approved, the bot will add "approved" label. 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 } approverSet := sets.String{} // from oldest to latest for i := len(comments) - 1; i >= 0; i-- { c := comments[i] if !mungerutil.IsValidUser(c.User) { continue } fields := strings.Fields(strings.TrimSpace(*c.Body)) if len(fields) == 1 && strings.ToLower(fields[0]) == "/approve" { approverSet.Insert(*c.User.Login) continue } if len(fields) == 2 && strings.ToLower(fields[0]) == "/approve" && strings.ToLower(fields[1]) == "cancel" { approverSet.Delete(*c.User.Login) } } for _, file := range files { if !h.hasApproval(*file.Filename, approverSet, maxDepth) { return } } obj.AddLabel(approvedLabel) }
// Munge is the workhorse the will actually make updates to the PR func (LabelUnapprovedPicks) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if obj.IsForBranch("master") { return } if obj.HasLabel(cpApprovedLabel) { return } obj.AddLabel(doNotMergeLabel) msg := fmt.Sprintf("This PR is not for the master branch but does not have the `%s` label. Adding the `%s` label.", cpApprovedLabel, doNotMergeLabel) obj.WriteComment(msg) }
// Munge is the workhorse the will actually make updates to the PR func (LabelUnapprovedPicks) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if obj.IsForBranch("master") { return } if obj.HasLabel(cpApprovedLabel) { return } if obj.HasLabel(doNotMergeLabel) { return } obj.AddLabel(doNotMergeLabel) obj.WriteComment(labelUnapprovedBody) }
func (h *LGTMHandler) addLGTMIfCommented(obj *github.MungeObject, comments []*githubapi.IssueComment, events []*githubapi.IssueEvent, reviewers mungerutil.UserSet) { // Get the last time when the someone applied lgtm manually. removeLGTMTime := e.LastEvent(events, e.And{e.RemoveLabel{}, e.LabelName(lgtmLabel), e.HumanActor()}, nil) // Assumption: The comments should be sorted (by default from github api) from oldest to latest for i := len(comments) - 1; i >= 0; i-- { comment := comments[i] if !mungerutil.IsValidUser(comment.User) { continue } // TODO: An approver should be acceptable. // See https://github.com/kubernetes/contrib/pull/1428#discussion_r72563935 if !mungerutil.IsMungeBot(comment.User) && !isReviewer(comment.User, reviewers) { continue } fields := getFields(*comment.Body) if isCancelComment(fields) { // "/lgtm cancel" if commented more recently than "/lgtm" return } if !isLGTMComment(fields) { continue } // check if someone manually removed the lgtm label after the `/lgtm` comment // and honor it. if removeLGTMTime != nil && removeLGTMTime.After(*comment.CreatedAt) { return } // TODO: support more complex policies for multiple reviewers. // See https://github.com/kubernetes/contrib/issues/1389#issuecomment-235161164 glog.Infof("Adding lgtm label. Reviewer (%s) LGTM", *comment.User.Login) obj.AddLabel(lgtmLabel) return } }
// Munge is the workhorse the will actually make updates to the PR func (r *ReleaseNoteLabel) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if releaseNoteAlreadyAdded(obj) { r.ensureNoRelNoteNeededLabel(obj) return } if !r.prMustFollowRelNoteProcess(obj) { r.ensureNoRelNoteNeededLabel(obj) return } labelToAdd := determineReleaseNoteLabel(obj) if labelToAdd != releaseNoteLabelNeeded { //going to apply some other release-note-label if obj.HasLabel(releaseNoteLabelNeeded) { obj.RemoveLabel(releaseNoteLabelNeeded) } obj.AddLabel(labelToAdd) return } if !obj.HasLabel(releaseNoteLabelNeeded) { obj.AddLabel(releaseNoteLabelNeeded) } if !obj.HasLabel(lgtmLabel) { return } if obj.HasLabel(doNotMergeLabel) { return } obj.WriteComment(releaseNoteBody) obj.AddLabel(doNotMergeLabel) }