// Find the last warning comment that the bot has posted. // It can return an empty comment if it fails to find one, even if there are no errors. func findLatestWarningComment(obj *github.MungeObject) (*githubapi.IssueComment, error) { var lastFoundComment *githubapi.IssueComment comments, err := obj.ListComments() if err != nil { return nil, err } for i := range comments { comment := comments[i] if !validComment(comment) { continue } if !mergeBotComment(comment) { continue } if !warningCommentRE.MatchString(*comment.Body) { continue } if lastFoundComment == nil || lastFoundComment.CreatedAt.Before(*comment.UpdatedAt) { if lastFoundComment != nil { obj.DeleteComment(lastFoundComment) } lastFoundComment = comment } } return lastFoundComment, nil }
// Munge is the workhorse the will actually make updates to the PR func (h AssignUnassignHandler) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } comments, err := obj.ListComments() if err != nil { glog.Errorf("unexpected error getting comments: %v", err) return } fileList, err := obj.ListFiles() if err != nil { glog.Errorf("Could not list the files for PR %v: %v", obj.Issue.Number, err) return } //get ALL (not just leaf) the people that could potentially own the file based on the blunderbuss.go implementation potentialOwners, _ := getPotentialOwners(*obj.Issue.User.Login, h.features, fileList, false) toAssign, toUnassign := h.getAssigneesAndUnassignees(obj, comments, fileList, potentialOwners) for _, username := range toAssign.List() { obj.AssignPR(username) } obj.UnassignPR(toUnassign.List()...) }
// Munge is the workhorse the will actually make updates to the PR func (CommentDeleter) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } comments, err := obj.ListComments() if err != nil { return } validComments := []githubapi.IssueComment{} for i := range comments { comment := comments[i] if !validComment(comment) { continue } validComments = append(validComments, comment) } for _, d := range deleters { stale := d.StaleComments(obj, validComments) for _, comment := range stale { obj.DeleteComment(&comment) } } }
// Munge is the workhorse the will actually make updates to the PR func (r *RebuildMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } comments, err := obj.ListComments() if err != nil { glog.Errorf("unexpected error getting comments: %v", err) } for ix := range comments { comment := comments[ix] // Skip all robot comments if r.robots.Has(*comment.User.Login) { glog.V(4).Infof("Skipping comment by robot %s: %s", *comment.User.Login, *comment.Body) continue } if isRebuildComment(comment) && rebuildCommentMissingIssueNumber(comment) { if err := obj.DeleteComment(comment); err != nil { glog.Errorf("Error deleting comment: %v", err) continue } body := fmt.Sprintf(rebuildFormat, *comment.User.Login) err := obj.WriteComment(body) if err != nil { glog.Errorf("unexpected error adding comment: %v", err) continue } } } }
func (h *ApprovalHandler) updateNotification(obj *github.MungeObject, ownersMap map[string]sets.String) error { notificationMatcher := c.MungerNotificationName(approvalNotificationName) comments, err := obj.ListComments() if err != nil { glog.Error("Could not list the comments for PR%v", obj.Issue.Number) return err } notifications := c.FilterComments(comments, notificationMatcher) latestNotification := notifications.GetLast() if latestNotification == nil { return h.createMessage(obj, ownersMap) } latestApprove := c.FilterComments(comments, c.CommandName(approveCommand)).GetLast() if latestApprove == nil || latestApprove.CreatedAt == nil { // there was already a bot notification and nothing has changed since // or we wouldn't tell when the latestApproval occurred return nil } if latestApprove.CreatedAt.After(*latestNotification.CreatedAt) { // if we can't tell when latestApprove happened, we should make a new one obj.DeleteComment(latestNotification) return h.createMessage(obj, ownersMap) } lastModified := obj.LastModifiedTime() if latestNotification.CreatedAt.Before(*lastModified) { obj.DeleteComment(latestNotification) return h.createMessage(obj, ownersMap) } return nil }
// 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 (CommentDeleter) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } comments, err := obj.ListComments() if err != nil { return } for i := range comments { comment := &comments[i] if comment.User == nil || comment.User.Login == nil { continue } if *comment.User.Login != botName { continue } if comment.Body == nil { continue } for _, f := range funcs { if f(obj, comment) { obj.DeleteComment(comment) break } } } }
// Priority implements IssueSource func (p *brokenJobSource) Priority(obj *github.MungeObject) (sync.Priority, error) { comments, err := obj.ListComments() if err != nil { return sync.PriorityP2, fmt.Errorf("Failed to list comment of issue: %v", err) } // Different IssueSource's Priority calculation may differ return autoPrioritize(comments, obj.Issue.CreatedAt), nil }
// 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) }
func getCommentsAfterLastModified(obj *github.MungeObject) ([]*githubapi.IssueComment, error) { afterLastModified := func(opt *githubapi.IssueListCommentsOptions) *githubapi.IssueListCommentsOptions { // Only comments updated at or after this time are returned. // One possible case is that reviewer might "/lgtm" first, contributor updated PR, and reviewer updated "/lgtm". // This is still valid. We don't recommend user to update it. lastModified := *obj.LastModifiedTime() opt.Since = lastModified return opt } return obj.ListComments(afterLastModified) }
// Search through the body and comments to see if the given item is already // mentioned in the given github issue. func (s *IssueSyncer) isRecorded(obj *github.MungeObject, source IssueSource) (bool, error) { id := source.ID() if obj.Issue.Body != nil && strings.Contains(*obj.Issue.Body, id) { // We already wrote this item return true, nil } comments, err := obj.ListComments() if err != nil { return false, fmt.Errorf("error getting comments for %v: %v", *obj.Issue.Number, err) } for _, c := range comments { if c.Body == nil { continue } if strings.Contains(*c.Body, id) { // We already wrote this item return true, nil } } return false, nil }
// Munge is the workhorse the will actually make updates to the PR func (RebuildMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } comments, err := obj.ListComments(*obj.Issue.Number) if err != nil { glog.Errorf("unexpected error getting comments: %v", err) } for ix := range comments { comment := &comments[ix] // Skip all robot comments for _, robot := range []string{"k8s-bot", "k8s-merge-robot", "googlebot"} { if *comment.User.Login == robot { glog.V(4).Infof("Skipping comment by robot %s: %s", robot, *comment.Body) continue } } if isRebuildComment(comment) && rebuildCommentMissingIssueNumber(comment) { if err := obj.DeleteComment(comment); err != nil { glog.Errorf("Error deleting comment: %v", err) continue } body := fmt.Sprintf(`@%s an issue is required for any manual rebuild. Expecting comment of the form 'github issue: #<number>' [Open test flakes](https://github.com/kubernetes/kubernetes/issues?q=is%3Aissue%20label%3Akind%2Fflake)`, *comment.User.Login) err := obj.WriteComment(body) if err != nil { glog.Errorf("unexpected error adding comment: %v", err) continue } if obj.HasLabel("lgtm") { if err := obj.RemoveLabel("lgtm"); err != nil { glog.Errorf("unexpected error removing lgtm label: %v", err) } } } } }
// Munge is the workhorse the will actually make updates to the PR func (r *RebuildMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } comments, err := obj.ListComments(*obj.Issue.Number) if err != nil { glog.Errorf("unexpected error getting comments: %v", err) } for ix := range comments { comment := &comments[ix] // Skip all robot comments if r.robots.Has(*comment.User.Login) { glog.V(4).Infof("Skipping comment by robot %s: %s", *comment.User.Login, *comment.Body) continue } if isRebuildComment(comment) && rebuildCommentMissingIssueNumber(comment) { if err := obj.DeleteComment(comment); err != nil { glog.Errorf("Error deleting comment: %v", err) continue } body := fmt.Sprintf(`@%s You must link to the test flake issue which caused you to request this manual re-test. Re-test requests should be in the form of: `+"`"+`k8s-bot test this issue: #<number>`+"`"+` Here is the [list of open test flakes](https://github.com/kubernetes/kubernetes/issues?q=is:issue+label:kind/flake+is:open).`, *comment.User.Login) err := obj.WriteComment(body) if err != nil { glog.Errorf("unexpected error adding comment: %v", err) continue } if obj.HasLabel("lgtm") { if err := obj.RemoveLabel("lgtm"); err != nil { glog.Errorf("unexpected error removing lgtm label: %v", err) } } } } }
func findLastHumanIssueUpdate(obj *github.MungeObject) (*time.Time, error) { lastHuman := obj.Issue.CreatedAt comments, err := obj.ListComments() if err != nil { return nil, err } for i := range comments { comment := comments[i] if !validComment(comment) { continue } if mergeBotComment(comment) || jenkinsBotComment(comment) { continue } if lastHuman.Before(*comment.UpdatedAt) { lastHuman = comment.UpdatedAt } } return lastHuman, nil }
// assigneeActionNeeded returns true if we are waiting on an action from the reviewer. func isReviewerActionNeeded(obj *github.MungeObject) (bool, error) { comments, err := obj.ListComments() if err != nil { return false, err } lastAuthorCommentTime := comment.LastComment(comments, comment.Author(*obj.Issue.User), nil) lastReviewerCommentTime := getLastReviewerComment(obj, comments) if lastReviewerCommentTime == nil { // this implies that no reviewer has commented on the PR yet. return true, nil } if obj.LastModifiedTime().After(*lastReviewerCommentTime) { return true, nil } if lastAuthorCommentTime == nil { return false, nil } return lastReviewerCommentTime.Before(*lastAuthorCommentTime), nil }