Exemple #1
0
// 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
		}
	}
}
Exemple #2
0
func mergeTime(obj *github.MungeObject) time.Time {
	t := obj.MergedAt()
	if t == nil {
		t = &maxTime
	}
	return *t
}
// 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()...)
}
Exemple #4
0
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
	}
}
Exemple #5
0
// 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
}
Exemple #6
0
// 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
			}
		}
	}
}
Exemple #7
0
func (h *LGTMHandler) removeLGTMIfCancelled(obj *github.MungeObject, comments []*githubapi.IssueComment, reviewers mungerutil.UserSet) {
	for i := len(comments) - 1; i >= 0; i-- {
		comment := comments[i]
		if !mungerutil.IsValidUser(comment.User) {
			continue
		}

		if !mungerutil.IsMungeBot(comment.User) && !isReviewer(comment.User, reviewers) {
			continue
		}

		fields := getFields(*comment.Body)
		if isLGTMComment(fields) {
			// "/lgtm" if commented more recently than "/lgtm cancel"
			return
		}

		if !isCancelComment(fields) {
			continue
		}

		glog.Infof("Removing lgtm label. Reviewer (%s) cancelled", *comment.User.Login)
		obj.RemoveLabel(lgtmLabel)
		return
	}
}
Exemple #8
0
// 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
}
Exemple #9
0
func (h *LGTMHandler) removeLGTMIfCancelled(obj *github.MungeObject, comments []*githubapi.IssueComment, events []*githubapi.IssueEvent, reviewers mungerutil.UserSet) {
	// Get time when the last (unlabeled, lgtm) event occurred.
	addLGTMTime := e.LastEvent(events, e.And{e.AddLabel{}, e.LabelName(lgtmLabel), e.HumanActor()}, nil)
	for i := len(comments) - 1; i >= 0; i-- {
		comment := comments[i]
		if !mungerutil.IsValidUser(comment.User) {
			continue
		}

		if !mungerutil.IsMungeBot(comment.User) && !isReviewer(comment.User, reviewers) {
			continue
		}

		fields := getFields(*comment.Body)
		if isLGTMComment(fields) {
			// "/lgtm" if commented more recently than "/lgtm cancel"
			return
		}

		if !isCancelComment(fields) {
			continue
		}

		// check if someone manually added the lgtm label after the `/lgtm cancel` comment
		// and honor it.
		if addLGTMTime != nil && addLGTMTime.After(*comment.CreatedAt) {
			return
		}

		glog.Infof("Removing lgtm label. Reviewer (%s) cancelled", *comment.User.Login)
		obj.RemoveLabel(lgtmLabel)
		return
	}
}
Exemple #10
0
// 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
	}
}
Exemple #11
0
// 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
}
Exemple #12
0
func findLastHumanPullRequestUpdate(obj *github.MungeObject) (*time.Time, error) {
	pr, err := obj.GetPR()
	if err != nil {
		return nil, err
	}

	comments, err := obj.ListReviewComments()
	if err != nil {
		return nil, err
	}

	lastHuman := pr.CreatedAt
	for i := range comments {
		comment := comments[i]
		if comment.User == nil || comment.User.Login == nil || comment.CreatedAt == nil || comment.Body == nil {
			continue
		}
		if *comment.User.Login == botName || *comment.User.Login == jenkinsBotName {
			continue
		}
		if lastHuman.Before(*comment.UpdatedAt) {
			lastHuman = comment.UpdatedAt
		}
	}

	return lastHuman, nil
}
Exemple #13
0
// MungeIssue is the real worker. It is called for every open github Issue
// But note that all PRs are Issues. (Not all Issues are PRs.) This particular
// function ignores all issues that are not PRs and prints the number for the
// PRs.
func MungeIssue(obj *github.MungeObject) error {
	if !obj.IsPR() {
		return nil
	}
	glog.Infof("PR: %d", *obj.Issue.Number)
	return nil
}
Exemple #14
0
// SetMergeStatus will set the status given a particular PR. This function should
// be used instead of manipulating the prStatus directly as sq.Lock() must be
// called when manipulating that structure
// `obj` is the active github object
// `reason` is the new 'status' for this object
func (sq *SubmitQueue) SetMergeStatus(obj *github.MungeObject, reason string) {
	glog.V(4).Infof("SubmitQueue not merging %d because %q", *obj.Issue.Number, reason)
	submitStatus := submitStatus{
		Time:              sq.clock.Now(),
		statusPullRequest: *objToStatusPullRequest(obj),
		Reason:            reason,
	}

	status := obj.GetStatus(sqContext)
	if status == nil || *status.Description != reason {
		state := reasonToState(reason)
		url := fmt.Sprintf("http://submit-queue.k8s.io/#/prs/?prDisplay=%d&historyDisplay=%d", *obj.Issue.Number, *obj.Issue.Number)
		_ = obj.SetStatus(state, url, reason, sqContext)
	}

	sq.Lock()
	defer sq.Unlock()

	// If we are currently retesting E2E the normal munge loop might find
	// that the ci tests are not green. That's normal and expected and we
	// should just ignore that status update entirely.
	if sq.githubE2ERunning != nil && *sq.githubE2ERunning.Issue.Number == *obj.Issue.Number && reason == ciFailure {
		return
	}

	if sq.onQueue(obj) {
		sq.statusHistory = append(sq.statusHistory, submitStatus)
		if len(sq.statusHistory) > 128 {
			sq.statusHistory = sq.statusHistory[1:]
		}
	}
	sq.prStatus[strconv.Itoa(*obj.Issue.Number)] = submitStatus
	sq.cleanupOldE2E(obj, reason)
}
Exemple #15
0
// 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(obj *github.MungeObject) {
	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 := obj.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
}
Exemple #16
0
// Munge is the workhorse the will actually make updates to the PR
func (s *SizeMunger) Munge(obj *github.MungeObject) {
	if !obj.IsPR() {
		return
	}

	issue := obj.Issue

	s.getGeneratedFiles(obj)
	genFiles := *s.genFiles
	genPrefixes := *s.genPrefixes

	files, err := obj.ListFiles()
	if err != nil {
		return
	}

	adds := 0
	dels := 0
	for _, f := range files {
		skip := false
		for _, p := range genPrefixes {
			if strings.HasPrefix(*f.Filename, p) {
				skip = true
				break
			}
		}
		if skip {
			continue
		}
		if genFiles.Has(*f.Filename) {
			continue
		}
		if f.Additions != nil {
			adds += *f.Additions
		}
		if f.Deletions != nil {
			dels += *f.Deletions
		}
	}

	newSize := calculateSize(adds, dels)
	newLabel := labelSizePrefix + newSize

	existing := github.GetLabelsWithPrefix(issue.Labels, labelSizePrefix)
	needsUpdate := true
	for _, l := range existing {
		if l == newLabel {
			needsUpdate = false
			continue
		}
		obj.RemoveLabel(l)
	}
	if needsUpdate {
		obj.AddLabels([]string{newLabel})

		body := fmt.Sprintf("Labelling this PR as %s", newLabel)
		obj.WriteComment(body)
	}
}
Exemple #17
0
// 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
}
func handleFound(obj *github.MungeObject, gitMsg []byte, branch string) error {
	msg := string(gitMsg)
	o := strings.SplitN(msg, "\n", 2)
	sha := o[0]
	msg = fmt.Sprintf("Commit %s found in the %q branch appears to be this PR. Removing the %q label. If this s an error find help to get your PR picked.", sha, branch, cpCandidateLabel)
	obj.WriteComment(msg)
	obj.RemoveLabel(cpCandidateLabel)
	return nil
}
Exemple #19
0
func newInterruptedObject(obj *github.MungeObject) *submitQueueInterruptedObject {
	if headSHA, baseRef, gotHeadSHA := obj.GetHeadAndBase(); !gotHeadSHA {
		return nil
	} else if baseSHA, gotBaseSHA := obj.GetSHAFromRef(baseRef); !gotBaseSHA {
		return nil
	} else {
		return &submitQueueInterruptedObject{obj, headSHA, baseSHA}
	}
}
// 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
}
Exemple #21
0
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
}
Exemple #22
0
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
}
Exemple #24
0
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 (PickMustHaveMilestone) isStaleComment(obj *github.MungeObject, comment *githubapi.IssueComment) bool {
	if *comment.Body != pickMustHaveMilestoneBody {
		return false
	}
	stale := obj.ReleaseMilestone() != ""
	if stale {
		glog.V(6).Infof("Found stale PickMustHaveMilestone comment")
	}
	return stale
}
Exemple #26
0
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 (sq *SubmitQueue) requiredStatusContexts(obj *github.MungeObject) []string {
	contexts := sq.RequiredStatusContexts

	// If the pr has a jenkins ci status, require it, otherwise require shippable
	if status, err := obj.GetStatus([]string{jenkinsCIContext}); err == nil && status != "incomplete" {
		contexts = append(contexts, jenkinsCIContext)
	} else {
		contexts = append(contexts, shippableContext)
	}
	return contexts
}
Exemple #28
0
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)
}
Exemple #29
0
func getEarliestApprovedTime(obj *github.MungeObject) *time.Time {
	lgtmTime := obj.LabelTime(lgtmLabel)
	approvedTime := obj.LabelTime(approvedLabel)
	// if both lgtmTime and approvedTime are nil, this func will return nil pointer
	if lgtmTime == nil {
		return approvedTime
	} else if approvedTime == nil {
		return lgtmTime
	} else if lgtmTime.Before(*approvedTime) {
		return lgtmTime
	}
	return approvedTime
}
Exemple #30
0
func priority(obj *github.MungeObject) int {
	// jump to the front of the queue if you don't need retested
	if obj.HasLabel(retestNotRequiredLabel) {
		return retestNotRequiredMergePriority
	}

	prio := obj.Priority()
	// eparis randomly decided that unlabel issues count at p3
	if prio == math.MaxInt32 {
		return defaultMergePriority
	}
	return prio
}