// 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) } } }
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 }
// 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 (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 } } } }
// 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 } } } }
func closePullRequest(obj *github.MungeObject, inactiveFor time.Duration) { comment := findLatestWarningComment(obj) if comment != nil { obj.DeleteComment(comment) } obj.WriteComment(fmt.Sprintf(closingComment, durationToDays(inactiveFor))) obj.ClosePR() }
func closePullRequest(obj *github.MungeObject, inactiveFor time.Duration) { mention := mungerutil.GetIssueUsers(obj.Issue).AllUsers().Mention().Join() if mention != "" { mention = "cc " + mention + "\n" } comment := findLatestWarningComment(obj) if comment != nil { obj.DeleteComment(comment) } obj.WriteComment(fmt.Sprintf(closingComment, durationToDays(inactiveFor), mention)) obj.ClosePR() }
func checkAndWarn(obj *github.MungeObject, inactiveFor time.Duration, closeIn time.Duration) { if closeIn < day { // We are going to close the PR in less than a day. Too late to warn return } comment := findLatestWarningComment(obj) if comment == nil { // We don't already have the comment. Post it postWarningComment(obj, inactiveFor, closeIn) } else if time.Since(*comment.UpdatedAt) > remindWarning { // It's time to warn again obj.DeleteComment(comment) postWarningComment(obj, inactiveFor, closeIn) } else { // We already have a warning, and it's not expired. Do nothing } }
// 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) } } } } }
// getAssigneesAndUnassignees checks to see when someone comments "/assign" or "/unassign" // returns two sets.String // 1. github handles to be assigned // 2. github handles to be unassigned // Note* Could possibly assign directly in the function call, but easier to test if function returns toAssign, toUnassign func (h *AssignUnassignHandler) getAssigneesAndUnassignees(obj *github.MungeObject, comments []*githubapi.IssueComment, fileList []*githubapi.CommitFile, potentialOwners weightMap) (toAssign, toUnassign sets.String) { toAssign = sets.String{} toUnassign = sets.String{} assignComments := c.FilterComments(comments, c.CommandName(assignCommand)) unassignComments := c.FilterComments(comments, c.CommandName(unassignCommand)) invalidUsers := sets.String{} //collect all the people that should be assigned for _, cmt := range assignComments { if isValidReviewer(potentialOwners, cmt.User) { obj.DeleteComment(cmt) toAssign.Insert(*cmt.User.Login) } else { // build the set of people who asked to be assigned but aren't in reviewers // use the @ as a prefix so github notifies invalid users invalidUsers.Insert("@" + *cmt.User.Login) } } // collect all the people that should be unassigned for _, cmt := range unassignComments { if isAssignee(obj.Issue.Assignees, cmt.User) { obj.DeleteComment(cmt) toUnassign.Insert(*cmt.User.Login) } } // Create a notification if someone tried to self assign, but could not because they weren't in the owners files if invalidUsers.Len() != 0 { previousNotifications := c.FilterComments(comments, c.MungerNotificationName(invalidReviewer)) if assignComments.Empty() || (!previousNotifications.Empty() && previousNotifications.GetLast().CreatedAt.After(*assignComments.GetLast().CreatedAt)) { // if there were no assign comments, no need to notify // if the last notification happened after the last assign comment, no need to notify again return toAssign, toUnassign } if !previousNotifications.Empty() { for _, c := range previousNotifications { obj.DeleteComment(c) } } context := bytes.NewBufferString("The following people cannot be assigned because they are not in the OWNERS files\n") for user := range invalidUsers { context.WriteString(fmt.Sprintf("- %s\n", user)) } context.WriteString("\n") c.Notification{Name: invalidReviewer, Arguments: "", Context: context.String()}.Post(obj) } return toAssign, toUnassign }