// 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) } }
// 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") } }
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 }
func (LGTMAfterCommitMunger) isStaleComment(obj *github.MungeObject, comment *githubapi.IssueComment) bool { if !mergeBotComment(comment) { return false } if !lgtmRemovedRegex.MatchString(*comment.Body) { return false } if !obj.HasLabel(lgtmLabel) { return false } lgtmTime := obj.LabelTime(lgtmLabel) if lgtmTime == nil { return false } stale := lgtmTime.After(*comment.CreatedAt) if stale { glog.V(6).Infof("Found stale LGTMAfterCommitMunger comment") } return stale }
func (LGTMAfterCommitMunger) isStaleComment(obj *github.MungeObject, comment *githubapi.IssueComment) bool { if *comment.Body != lgtmRemovedBody { return false } if comment.CreatedAt == nil { return false } if !obj.HasLabel("lgtm") { return false } lgtmTime := obj.LabelTime("lgtm") if lgtmTime == nil { return false } stale := lgtmTime.After(*comment.CreatedAt) if stale { glog.V(6).Infof("Found stale LGTMAfterCommitMunger comment") } return stale }
// Munge is the workhorse the will actually make updates to the PR func (sq *SubmitQueue) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } e2e := sq.e2e userSet := sq.userWhitelist if !obj.HasLabels([]string{claYes}) && !obj.HasLabels([]string{claHuman}) { sq.SetMergeStatus(obj, noCLA, false) return } if mergeable, err := obj.IsMergeable(); err != nil { sq.SetMergeStatus(obj, undeterminedMergability, false) return } else if !mergeable { sq.SetMergeStatus(obj, unmergeable, false) return } // Validate the status information for this PR contexts := sq.requiredStatusContexts(obj) if ok := obj.IsStatusSuccess(contexts); !ok { sq.SetMergeStatus(obj, ciFailure, false) return } if !obj.HasLabel(sq.WhitelistOverride) && !userSet.Has(*obj.Issue.User.Login) { if !obj.HasLabel(needsOKToMergeLabel) { obj.AddLabels([]string{needsOKToMergeLabel}) body := "The author of this PR is not in the whitelist for merge, can one of the admins add the 'ok-to-merge' label?" obj.WriteComment(body) } sq.SetMergeStatus(obj, needsok, false) return } // Tidy up the issue list. if obj.HasLabel(needsOKToMergeLabel) { obj.RemoveLabel(needsOKToMergeLabel) } if !obj.HasLabels([]string{"lgtm"}) { sq.SetMergeStatus(obj, noLGTM, false) return } lastModifiedTime := obj.LastModifiedTime() lgtmTime := obj.LabelTime("lgtm") if lastModifiedTime == nil || lgtmTime == nil { glog.Errorf("PR %d was unable to determine when LGTM was added or when last modified", *obj.Issue.Number) sq.SetMergeStatus(obj, unknown, false) return } if lastModifiedTime.After(*lgtmTime) { sq.SetMergeStatus(obj, lgtmEarly, false) return } if !e2e.Stable() { sq.flushGithubE2EQueue(e2eFailure) sq.SetMergeStatus(obj, e2eFailure, false) return } // if there is a 'e2e-not-required' label, just merge it. if obj.HasLabel(e2eNotRequiredLabel) { obj.MergePR("submit-queue") sq.SetMergeStatus(obj, merged, true) return } added := false sq.Lock() if _, ok := sq.githubE2EQueue[*obj.Issue.Number]; !ok { sq.githubE2EQueue[*obj.Issue.Number] = obj sq.githubE2EWakeup <- true added = true } sq.Unlock() if added { sq.SetMergeStatus(obj, ghE2EQueued, true) } return }
// validForMerge is the base logic about what PR can be automatically merged. // PRs must pass this logic to be placed on the queue and they must pass this // logic a second time to be retested/merged after they get to the top of // the queue. // // If you update the logic PLEASE PLEASE PLEASE update serveMergeInfo() as well. func (sq *SubmitQueue) validForMerge(obj *github.MungeObject) bool { // Can't merge an issue! if !obj.IsPR() { return false } // Can't merge something already merged. if m, err := obj.IsMerged(); err != nil { glog.Errorf("%d: unknown err: %v", *obj.Issue.Number, err) sq.SetMergeStatus(obj, unknown) return false } else if m { sq.SetMergeStatus(obj, mergedByHand) return false } if milestone := obj.Issue.Milestone; true { title := "" // Net set means the empty milestone, "" if milestone != nil && milestone.Title != nil { title = *milestone.Title } for _, blocked := range sq.doNotMergeMilestones { if title == blocked { sq.SetMergeStatus(obj, unmergeableMilestone) return false } } } // Must pass CLA checks if !obj.HasLabel(claYesLabel) && !obj.HasLabel(claHumanLabel) { sq.SetMergeStatus(obj, noCLA) return false } // Obviously must be mergeable if mergeable, err := obj.IsMergeable(); err != nil { sq.SetMergeStatus(obj, undeterminedMergability) return false } else if !mergeable { sq.SetMergeStatus(obj, unmergeable) return false } // Validate the status information for this PR if len(sq.RequiredStatusContexts) > 0 { if ok := obj.IsStatusSuccess(sq.RequiredStatusContexts); !ok { sq.SetMergeStatus(obj, ciFailure) return false } } if len(sq.RequiredRetestContexts) > 0 { if ok := obj.IsStatusSuccess(sq.RequiredRetestContexts); !ok { sq.SetMergeStatus(obj, ciFailure) return false } } // Clearly if !obj.HasLabel(lgtmLabel) { sq.SetMergeStatus(obj, noLGTM) return false } // PR cannot change since LGTM was added lastModifiedTime := obj.LastModifiedTime() lgtmTime := obj.LabelTime(lgtmLabel) if lastModifiedTime == nil || lgtmTime == nil { glog.Errorf("PR %d was unable to determine when LGTM was added or when last modified", *obj.Issue.Number) sq.SetMergeStatus(obj, unknown) return false } if lastModifiedTime.After(*lgtmTime) { sq.SetMergeStatus(obj, lgtmEarly) return false } // PR cannot have the label which prevents merging. if obj.HasLabel(doNotMergeLabel) { sq.SetMergeStatus(obj, noMerge) return false } return true }
// Munge is the workhorse the will actually make updates to the PR func (sq *SubmitQueue) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } userSet := sq.userWhitelist if !obj.HasLabels([]string{claYes}) && !obj.HasLabels([]string{claHuman}) { sq.SetMergeStatus(obj, noCLA, false) return } if mergeable, err := obj.IsMergeable(); err != nil { sq.SetMergeStatus(obj, undeterminedMergability, false) return } else if !mergeable { sq.SetMergeStatus(obj, unmergeable, false) return } // Validate the status information for this PR contexts := sq.requiredStatusContexts(obj) if ok := obj.IsStatusSuccess(contexts); !ok { sq.SetMergeStatus(obj, ciFailure, false) return } if !obj.HasLabel(sq.WhitelistOverride) && !userSet.Has(*obj.Issue.User.Login) { if !obj.HasLabel(needsOKToMergeLabel) { obj.AddLabels([]string{needsOKToMergeLabel}) body := "The author of this PR is not in the whitelist for merge, can one of the admins add the 'ok-to-merge' label?" obj.WriteComment(body) } sq.SetMergeStatus(obj, needsok, false) return } // Tidy up the issue list. if obj.HasLabel(needsOKToMergeLabel) { obj.RemoveLabel(needsOKToMergeLabel) } if !obj.HasLabels([]string{"lgtm"}) { sq.SetMergeStatus(obj, noLGTM, false) return } lastModifiedTime := obj.LastModifiedTime() lgtmTime := obj.LabelTime("lgtm") if lastModifiedTime == nil || lgtmTime == nil { glog.Errorf("PR %d was unable to determine when LGTM was added or when last modified", *obj.Issue.Number) sq.SetMergeStatus(obj, unknown, false) return } if lastModifiedTime.After(*lgtmTime) { sq.SetMergeStatus(obj, lgtmEarly, false) return } added := false sq.Lock() if _, ok := sq.githubE2EQueue[*obj.Issue.Number]; !ok { added = true } // Add this most-recent object in place of the existing object. It will // have more up2date information. Even though we explicitly refresh the // PR information before do anything with it, this allow things like the // queue order to change dynamically as labels are added/removed. sq.githubE2EQueue[*obj.Issue.Number] = obj sq.Unlock() if added { sq.SetMergeStatus(obj, ghE2EQueued, true) } return }
// validForMerge is the base logic about what PR can be automatically merged. // PRs must pass this logic to be placed on the queue and they must pass this // logic a second time to be retested/merged after they get to the top of // the queue. // // If you update the logic PLEASE PLEASE PLEASE update serveMergeInfo() as well. func (sq *SubmitQueue) validForMerge(obj *github.MungeObject) bool { // Can't merge an issue! if !obj.IsPR() { return false } // Can't merge something already merged. if m, err := obj.IsMerged(); err != nil { glog.Errorf("%d: unknown err: %v", *obj.Issue.Number, err) sq.SetMergeStatus(obj, unknown) return false } else if m { sq.SetMergeStatus(obj, merged) return false } userSet := sq.userWhitelist // Must pass CLA checks if !obj.HasLabel(claYesLabel) && !obj.HasLabel(claHumanLabel) { sq.SetMergeStatus(obj, noCLA) return false } // Obviously must be mergeable if mergeable, err := obj.IsMergeable(); err != nil { sq.SetMergeStatus(obj, undeterminedMergability) return false } else if !mergeable { sq.SetMergeStatus(obj, unmergeable) return false } // Validate the status information for this PR contexts := sq.requiredStatusContexts(obj) if ok := obj.IsStatusSuccess(contexts); !ok { sq.SetMergeStatus(obj, ciFailure) return false } // The user either must be on the whitelist or have ok-to-merge if !obj.HasLabel(okToMergeLabel) && !userSet.Has(*obj.Issue.User.Login) { if !obj.HasLabel(needsOKToMergeLabel) { obj.AddLabels([]string{needsOKToMergeLabel}) obj.WriteComment(notInWhitelistBody) } sq.SetMergeStatus(obj, needsok) return false } // Tidy up the issue list. if obj.HasLabel(needsOKToMergeLabel) { obj.RemoveLabel(needsOKToMergeLabel) } // Clearly if !obj.HasLabel(lgtmLabel) { sq.SetMergeStatus(obj, noLGTM) return false } // PR cannot change since LGTM was added lastModifiedTime := obj.LastModifiedTime() lgtmTime := obj.LabelTime(lgtmLabel) if lastModifiedTime == nil || lgtmTime == nil { glog.Errorf("PR %d was unable to determine when LGTM was added or when last modified", *obj.Issue.Number) sq.SetMergeStatus(obj, unknown) return false } if lastModifiedTime.After(*lgtmTime) { sq.SetMergeStatus(obj, lgtmEarly) return false } // PR cannot have the label which prevents merging. if obj.HasLabel(doNotMergeLabel) { sq.SetMergeStatus(obj, noMerge) return false } return true }