def get_needs_revision_facts(triager, issuewrapper, meta, shippable=None): # Thanks @adityacs for this PR. This PR requires revisions, either # because it fails to build or by reviewer request. Please make the # suggested revisions. When you are done, please comment with text # 'ready_for_review' and we will put this PR back into review. # a "dirty" mergeable_state can exist with "successfull" ci_state. # short alias iw = issuewrapper committer_count = None needs_revision = False needs_revision_msgs = [] merge_commits = [] has_merge_commit_notification = False needs_rebase = False needs_rebase_msgs = [] has_shippable = False has_landscape = False has_travis = False has_travis_notification = False ci_state = None ci_stale = None mstate = None change_requested = None ready_for_review = None has_commit_mention = False has_commit_mention_notification = False has_shippable_yaml = None has_shippable_yaml_notification = None has_remote_repo = None user_reviews = None stale_reviews = {} # https://github.com/ansible/ansibullbot/issues/302 has_multiple_modules = False needs_multiple_new_modules_notification = False rmeta = { 'committer_count': committer_count, 'is_needs_revision': needs_revision, 'is_needs_revision_msgs': needs_revision_msgs, 'is_needs_rebase': needs_rebase, 'is_needs_rebase_msgs': needs_rebase_msgs, 'has_commit_mention': has_commit_mention, 'has_commit_mention_notification': has_commit_mention_notification, 'has_shippable': has_shippable, 'has_landscape': has_landscape, 'has_travis': has_travis, 'has_travis_notification': has_travis_notification, 'merge_commits': merge_commits, 'has_merge_commit_notification': has_merge_commit_notification, 'mergeable': None, 'mergeable_state': mstate, 'change_requested': change_requested, 'ci_state': ci_state, 'ci_stale': ci_stale, 'reviews': None, #'www_reviews': None, #'www_summary': None, 'ready_for_review': ready_for_review, 'has_shippable_yaml': has_shippable_yaml, 'has_shippable_yaml_notification': has_shippable_yaml_notification, 'has_remote_repo': has_remote_repo, 'stale_reviews': stale_reviews, 'has_multiple_modules': has_multiple_modules, 'needs_multiple_new_modules_notification': needs_multiple_new_modules_notification } if not iw.is_pullrequest(): return rmeta bpcs = iw.history.get_boilerplate_comments() bpcs = [x[0] for x in bpcs] # Scrape web data for debug purposes #rfn = iw.repo_full_name #www_summary = triager.gws.get_single_issue_summary(rfn, iw.number) #www_reviews = triager.gws.scrape_pullrequest_review(rfn, iw.number) maintainers = [ x for x in triager.ansible_core_team if x not in triager.BOTNAMES ] #if meta.get('module_match'): # maintainers += meta['module_match'].get('maintainers', []) maintainers += meta.get('component_maintainers', []) # get the exact state from shippable ... # success/pending/failure/... ? ci_status = iw.pullrequest_status # code quality hooks if [ x for x in ci_status if isinstance(x, dict) and 'landscape.io' in x['target_url'] ]: has_landscape = True ci_states = [ x['state'] for x in ci_status if isinstance(x, dict) and 'shippable.com' in x['target_url'] ] if not ci_states: ci_state = None else: ci_state = ci_states[0] logging.info('ci_state == %s' % ci_state) # https://github.com/ansible/ansibullbot/issues/458 ci_dates = [x['created_at'] for x in ci_status] ci_dates = sorted(set(ci_dates)) if ci_dates: last_ci_date = ci_dates[-1] last_ci_date = datetime.datetime.strptime(last_ci_date, '%Y-%m-%dT%H:%M:%SZ') ci_delta = (datetime.datetime.now() - last_ci_date).days if ci_delta > 7: ci_stale = True else: ci_stale = False else: ci_stale = False # clean/unstable/dirty/unknown mstate = iw.mergeable_state if not mstate: mstate = 'unknown' logging.info('mergeable_state == %s' % mstate) # clean/unstable/dirty/unknown if mstate != 'clean': if ci_state == 'failure': needs_revision = True needs_revision_msgs.append('ci failure') if mstate == 'dirty': needs_revision = True needs_rebase = True needs_revision_msgs.append('mergeable state is dirty') needs_rebase_msgs.append('mergeable state is dirty') elif mstate == 'unknown': # if tests are still running, this needs to be ignored. if ci_state not in ['pending']: needs_revision = True needs_revision_msgs.append('mergeable state is unknown') needs_rebase = True needs_rebase_msgs.append('mergeable state is unknown') elif mstate == 'unstable': # reduce the label churn if ci_state == 'pending' and 'needs_revision' in iw.labels: needs_revision = True needs_rebase_msgs.append('keep label till test finished') else: #current_hash = None #pending_reviews = [] #hash_reviews = {} user_reviews = {} shipits = {} # key: actor, value: created_at for event in iw.history.history: if event['actor'] in triager.BOTNAMES: continue if event['actor'] in maintainers and \ event['actor'] != iw.submitter: if event['event'] == 'labeled': if event['label'] == 'needs_revision': needs_revision = True needs_revision_msgs.append('[%s] labeled' % event['actor']) continue if event['event'] == 'unlabeled': if event['label'] == 'needs_revision': needs_revision = False needs_revision_msgs.append('[%s] unlabeled' % event['actor']) continue if event['event'] == 'commented': if is_approval(event['body']): shipits[event['actor']] = event['created_at'] if '!needs_revision' in event['body']: needs_revision = False needs_revision_msgs.append('[%s] !needs_revision' % event['actor']) continue if 'needs_revision' in event['body'] and \ '!needs_revision' not in event['body']: needs_revision = True needs_revision_msgs.append('[%s] needs_revision' % event['actor']) continue if event['actor'] == iw.submitter: if event['event'] == 'commented': if 'ready_for_review' in event['body']: if ready_for_review is None or event[ 'created_at'] > ready_for_review: ready_for_review = event['created_at'] needs_revision = False needs_revision_msgs.append('[%s] ready_for_review' % event['actor']) continue if 'shipit' in event['body'].lower(): #ready_for_review = True if ready_for_review is None or event[ 'created_at'] > ready_for_review: ready_for_review = event['created_at'] needs_revision = False needs_revision_msgs.append('[%s] shipit' % event['actor']) continue # This is a complicated algo ... sigh user_reviews = get_review_state(iw.reviews, iw.submitter, number=iw.number, store=True) if user_reviews: last_commit = iw.commits[-1].sha change_requested = changes_requested_by(user_reviews, shipits, last_commit, ready_for_review) if change_requested: needs_revision = True needs_revision_msgs.append('outstanding reviews: %s' % ','.join(change_requested)) #import pprint; pprint.pprint(www_reviews) #import pprint; pprint.pprint(change_requested) #import epdb; epdb.st() # Merge commits are bad, force a rebase if iw.merge_commits: needs_rebase = True for mc in iw.merge_commits: merge_commits.append(mc.html_url) needs_rebase_msgs.append('merge commit %s' % mc.commit.sha) if 'merge_commit_notify' not in bpcs: has_merge_commit_notification = False else: mc_comments = iw.history.search_user_comments( triager.BOTNAMES, 'boilerplate: merge_commit_notify') last_mc_comment = mc_comments[-1] mc_missing = [] for mc in iw.merge_commits: if mc.html_url not in last_mc_comment: mc_missing.append(mc) if mc_missing: has_merge_commit_notification = False else: has_merge_commit_notification = True # Count committers committer_count = len(sorted(set(iw.committer_emails))) if ci_status: for x in ci_status: if 'travis-ci.org' in x['target_url']: has_travis = True continue if 'shippable.com' in x['target_url']: has_shippable = True continue # we don't like @you in the commit messages # https://github.com/ansible/ansibullbot/issues/375 for x in iw.commits: words = x.commit.message.split() if not words: continue if [x for x in words if x.startswith('@') and not x.endswith('@')]: has_commit_mention = True needs_revision = True needs_revision_msgs.append('@ in commit message') break # make sure they're notified about the problem if has_commit_mention: if 'commit_msg_mentions' in bpcs: has_commit_mention_notification = True if has_travis: needs_rebase = True needs_rebase_msgs.append('travis-ci found in status') # 'has_travis_notification': has_travis_notification, if 'travis_notify' in bpcs: has_travis_notification = True else: has_travis_notification = False # keep track of who deleted their repo/branch if iw.pullrequest.head.repo: has_remote_repo = True else: has_remote_repo = False # https://github.com/ansible/ansibullbot/issues/406 has_shippable_yaml = iw.pullrequest_filepath_exists('shippable.yml') if not has_shippable_yaml: needs_rebase = True needs_rebase_msgs.append('missing shippable.yml') if 'no_shippable_yaml' in bpcs: has_shippable_yaml_notification = True else: has_shippable_yaml_notification = False # stale reviews if user_reviews: now = pytz.utc.localize(datetime.datetime.now()) commits = [x for x in iw.history.history if x['event'] == 'committed'] lc_date = commits[-1]['created_at'] stale_reviews = {} for actor, review in user_reviews.items(): if review['state'] != 'CHANGES_REQUESTED': continue lrd = None for x in iw.history.history: if x['actor'] != actor: continue if x['event'] == 'review_changes_requested': if not lrd or lrd < x['created_at']: lrd = x['created_at'] elif x['event'] == 'commented' and is_approval(x['body']): if lrd and lrd < x['created_at']: lrd = None if lrd: age = (now - lc_date).days delta = (lc_date - lrd).days if (lc_date > lrd) and (age > 7): stale_reviews[actor] = { 'age': age, 'delta': delta, 'review_date': lrd.isoformat(), 'commit_date': lc_date.isoformat() } # https://github.com/ansible/ansibullbot/issues/302 if len(iw.new_modules) > 1: has_multiple_modules = True if 'multiple_module_notify' not in bpcs: needs_multiple_new_modules_notification = True needs_revision = True needs_revision_msgs.append('multiple new modules') logging.info('mergeable_state is %s' % mstate) logging.info('needs_rebase is %s' % needs_rebase) logging.info('needs_revision is %s' % needs_revision) logging.info('ready_for_review is %s' % ready_for_review) rmeta = { 'committer_count': committer_count, 'is_needs_revision': needs_revision, 'is_needs_revision_msgs': needs_revision_msgs, 'is_needs_rebase': needs_rebase, 'is_needs_rebase_msgs': needs_rebase_msgs, 'has_shippable': has_shippable, 'has_landscape': has_landscape, 'has_travis': has_travis, 'has_travis_notification': has_travis_notification, 'has_commit_mention': has_commit_mention, 'has_commit_mention_notification': has_commit_mention_notification, 'merge_commits': merge_commits, 'has_merge_commit_notification': has_merge_commit_notification, 'mergeable': iw.pullrequest.mergeable, 'mergeable_state': mstate, 'change_requested': change_requested, 'ci_state': ci_state, 'ci_stale': ci_stale, 'reviews': iw.reviews, #'www_summary': www_summary, #'www_reviews': www_reviews, 'ready_for_review_date': ready_for_review, 'ready_for_review': bool(ready_for_review), 'has_shippable_yaml': has_shippable_yaml, 'has_shippable_yaml_notification': has_shippable_yaml_notification, 'has_remote_repo': has_remote_repo, 'stale_reviews': stale_reviews, 'has_multiple_modules': has_multiple_modules, 'needs_multiple_new_modules_notification': needs_multiple_new_modules_notification } if rmeta['ready_for_review_date']: rmeta['ready_for_review_date'] = rmeta[ 'ready_for_review_date'].isoformat() return rmeta
def get_needs_revision_facts(triager, issuewrapper, meta, shippable): # Thanks @adityacs for this PR. This PR requires revisions, either # because it fails to build or by reviewer request. Please make the # suggested revisions. When you are done, please comment with text # 'ready_for_review' and we will put this PR back into review. # a "dirty" mergeable_state can exist with "successfull" ci_state. # short alias iw = issuewrapper committer_count = None needs_revision = False needs_revision_msgs = [] merge_commits = [] has_merge_commit_notification = False needs_rebase = False needs_rebase_msgs = [] ci_state = None ci_stale = True mstate = None change_requested = None ready_for_review = None has_commit_mention = False has_commit_mention_notification = False has_shippable = False has_shippable_yaml = None has_shippable_yaml_notification = None has_remote_repo = None user_reviews = None stale_reviews = {} # https://github.com/ansible/ansibullbot/issues/302 has_multiple_modules = False needs_multiple_new_modules_notification = False rmeta = { u'committer_count': committer_count, u'is_needs_revision': needs_revision, u'is_needs_revision_msgs': needs_revision_msgs, u'is_needs_rebase': needs_rebase, u'is_needs_rebase_msgs': needs_rebase_msgs, u'has_commit_mention': has_commit_mention, u'has_commit_mention_notification': has_commit_mention_notification, u'has_shippable': has_shippable, u'merge_commits': merge_commits, u'has_merge_commit_notification': has_merge_commit_notification, u'mergeable': None, u'mergeable_state': mstate, u'change_requested': change_requested, u'ci_state': ci_state, u'ci_stale': ci_stale, u'reviews': None, u'ready_for_review': ready_for_review, u'has_shippable_yaml': has_shippable_yaml, u'has_shippable_yaml_notification': has_shippable_yaml_notification, u'has_remote_repo': has_remote_repo, u'stale_reviews': stale_reviews, u'has_multiple_modules': has_multiple_modules, u'needs_multiple_new_modules_notification': needs_multiple_new_modules_notification } if not iw.is_pullrequest(): return rmeta bpcs = iw.history.get_boilerplate_comments() bpcs = [x[0] for x in bpcs] maintainers = [ x for x in triager.ansible_core_team if x not in triager.BOTNAMES ] maintainers += meta.get(u'component_maintainers', []) ci_states = shippable.get_states(iw.pullrequest_status) if ci_states: has_shippable = True ci_stale = shippable.is_stale(ci_states) ci_state = ci_states[0].get('state') logging.info(u'ci_state == %s' % ci_state) # clean/unstable/dirty/unknown mstate = iw.mergeable_state if not mstate: mstate = u'unknown' logging.info(u'mergeable_state == %s' % mstate) # clean/unstable/dirty/unknown # FIXME ci_state related to shippable? make it general? if mstate != u'clean': if ci_state == u'failure': needs_revision = True needs_revision_msgs.append(u'ci failure') if mstate == u'dirty': needs_revision = True needs_rebase = True needs_revision_msgs.append(u'mergeable state is dirty') needs_rebase_msgs.append(u'mergeable state is dirty') elif mstate == u'unknown': # if tests are still running, this needs to be ignored. if ci_state not in [u'pending']: needs_revision = True needs_revision_msgs.append(u'mergeable state is unknown') needs_rebase = True needs_rebase_msgs.append(u'mergeable state is unknown') elif mstate == u'unstable': # reduce the label churn if ci_state == u'pending' and u'needs_revision' in iw.labels: needs_revision = True needs_rebase_msgs.append(u'keep label till test finished') else: user_reviews = {} shipits = {} # key: actor, value: created_at has_set_needs_revision = set() for event in iw.history.history: if event[u'actor'] in triager.BOTNAMES: continue if event[u'actor'] in maintainers and \ event[u'actor'] != iw.submitter: if event[u'event'] == u'labeled': if event[u'label'] == u'needs_revision': needs_revision = True needs_revision_msgs.append(u'[%s] labeled' % event[u'actor']) has_set_needs_revision.add(event[u'actor']) continue if event[u'event'] == u'unlabeled': if event[u'label'] == u'needs_revision': needs_revision = False needs_revision_msgs.append(u'[%s] unlabeled' % event[u'actor']) continue if event[u'event'] == u'commented': if is_approval(event[u'body']): shipits[event[u'actor']] = event[u'created_at'] if u'!needs_revision' in event[u'body']: needs_revision = False needs_revision_msgs.append(u'[%s] !needs_revision' % event[u'actor']) continue if u'needs_revision' in event[u'body'] and \ u'!needs_revision' not in event[u'body']: needs_revision = True needs_revision_msgs.append(u'[%s] needs_revision' % event[u'actor']) has_set_needs_revision.add(event[u'actor']) continue if u'shipit' in event[u'body'].lower(): if event[u'actor'] in has_set_needs_revision: has_set_needs_revision.remove(event[u'actor']) if not has_set_needs_revision: needs_revision = False continue if event[u'actor'] == iw.submitter: if event[u'event'] == u'commented': if u'ready_for_review' in event[u'body']: if ready_for_review is None or event[ u'created_at'] > ready_for_review: ready_for_review = event[u'created_at'] needs_revision = False needs_revision_msgs.append(u'[%s] ready_for_review' % event[u'actor']) continue if u'shipit' in event[u'body'].lower(): #ready_for_review = True if ready_for_review is None or event[ u'created_at'] > ready_for_review: ready_for_review = event[u'created_at'] needs_revision = False needs_revision_msgs.append(u'[%s] shipit' % event[u'actor']) continue # This is a complicated algo ... sigh user_reviews = _get_review_state( iw.reviews, iw.submitter, number=iw.number, ) if user_reviews: last_commit = iw.commits[-1].sha change_requested = _changes_requested_by(user_reviews, shipits, last_commit, ready_for_review) if change_requested: needs_revision = True needs_revision_msgs.append(u'outstanding reviews: %s' % u','.join(change_requested)) # Merge commits are bad, force a rebase if iw.merge_commits: needs_rebase = True for mc in iw.merge_commits: merge_commits.append(mc.html_url) needs_rebase_msgs.append(u'merge commit %s' % mc.commit.sha) if u'merge_commit_notify' not in bpcs: has_merge_commit_notification = False else: mc_comments = iw.history.search_user_comments( triager.BOTNAMES, u'boilerplate: merge_commit_notify') last_mc_comment = mc_comments[-1] mc_missing = [] for mc in iw.merge_commits: if mc.html_url not in last_mc_comment: mc_missing.append(mc) if mc_missing: has_merge_commit_notification = False else: has_merge_commit_notification = True # Count committers committer_count = len(sorted(set(iw.committer_emails))) # we don't like @you in the commit messages # https://github.com/ansible/ansibullbot/issues/375 for x in iw.commits: words = x.commit.message.split() if not words: continue if [x for x in words if x.startswith(u'@') and not x.endswith(u'@')]: has_commit_mention = True needs_revision = True needs_revision_msgs.append(u'@ in commit message') break # make sure they're notified about the problem if has_commit_mention: if u'commit_msg_mentions' in bpcs: has_commit_mention_notification = True # keep track of who deleted their repo/branch if iw.pullrequest.head.repo: has_remote_repo = True else: has_remote_repo = False # https://github.com/ansible/ansibullbot/issues/406 has_shippable_yaml = iw.pullrequest_filepath_exists( shippable.required_file) if not has_shippable_yaml: needs_rebase = True needs_rebase_msgs.append(u'missing shippable.yml') if u'no_shippable_yaml' in bpcs: has_shippable_yaml_notification = True else: has_shippable_yaml_notification = False # stale reviews if user_reviews: now = pytz.utc.localize(datetime.datetime.now()) commits = [ x for x in iw.history.history if x[u'event'] == u'committed' ] lc_date = commits[-1][u'created_at'] stale_reviews = {} for actor, review in user_reviews.items(): if review[u'state'] != u'CHANGES_REQUESTED': continue lrd = None for x in iw.history.history: if x[u'actor'] != actor: continue if x[u'event'] == u'review_changes_requested': if not lrd or lrd < x[u'created_at']: lrd = x[u'created_at'] elif x[u'event'] == u'commented' and is_approval(x[u'body']): if lrd and lrd < x[u'created_at']: lrd = None if lrd: age = (now - lc_date).days delta = (lc_date - lrd).days if (lc_date > lrd) and (age > 7): stale_reviews[actor] = { u'age': age, u'delta': delta, u'review_date': lrd.isoformat(), u'commit_date': lc_date.isoformat() } # https://github.com/ansible/ansibullbot/issues/302 if len(iw.new_modules) > 1: has_multiple_modules = True if u'multiple_module_notify' not in bpcs: needs_multiple_new_modules_notification = True needs_revision = True needs_revision_msgs.append(u'multiple new modules') logging.info(u'mergeable_state is %s' % mstate) logging.info(u'needs_rebase is %s' % needs_rebase) logging.info(u'needs_revision is %s' % needs_revision) logging.info(u'ready_for_review is %s' % ready_for_review) rmeta = { u'committer_count': committer_count, u'is_needs_revision': needs_revision, u'is_needs_revision_msgs': needs_revision_msgs, u'is_needs_rebase': needs_rebase, u'is_needs_rebase_msgs': needs_rebase_msgs, u'has_shippable': has_shippable, u'has_commit_mention': has_commit_mention, u'has_commit_mention_notification': has_commit_mention_notification, u'merge_commits': merge_commits, u'has_merge_commit_notification': has_merge_commit_notification, u'mergeable': iw.mergeable, u'mergeable_state': mstate, u'change_requested': change_requested, u'ci_state': ci_state, u'ci_stale': ci_stale, u'reviews': iw.reviews, u'ready_for_review_date': ready_for_review, u'ready_for_review': bool(ready_for_review), u'has_shippable_yaml': has_shippable_yaml, u'has_shippable_yaml_notification': has_shippable_yaml_notification, u'has_remote_repo': has_remote_repo, u'stale_reviews': stale_reviews, u'has_multiple_modules': has_multiple_modules, u'needs_multiple_new_modules_notification': needs_multiple_new_modules_notification } if rmeta[u'ready_for_review_date']: rmeta[u'ready_for_review_date'] = rmeta[ u'ready_for_review_date'].isoformat() return rmeta
def test_is_approval(self): self.assertTrue(is_approval('shipit')) self.assertTrue(is_approval('+1')) self.assertTrue(is_approval('LGTM')) self.assertTrue(is_approval(' shipit ')) self.assertTrue(is_approval("\tshipit\t")) self.assertTrue(is_approval("\tshipit\n")) self.assertTrue(is_approval('Hey, LGTM !')) self.assertFalse(is_approval(':+1:')) self.assertFalse(is_approval('lgtm')) self.assertFalse(is_approval('Shipit')) self.assertFalse(is_approval('shipit!')) self.assertFalse(is_approval('shipits')) self.assertFalse(is_approval('LGTM.')) self.assertFalse(is_approval('Looks good to me'))
def test_is_approval(self): self.assertTrue(is_approval(u'shipit')) self.assertTrue(is_approval(u'+1')) self.assertTrue(is_approval(u'LGTM')) self.assertTrue(is_approval(u' shipit ')) self.assertTrue(is_approval(u"\tshipit\t")) self.assertTrue(is_approval(u"\tshipit\n")) self.assertTrue(is_approval(u'Hey, LGTM !')) self.assertFalse(is_approval(u':+1:')) self.assertFalse(is_approval(u'lgtm')) self.assertFalse(is_approval(u'Shipit')) self.assertFalse(is_approval(u'shipit!')) self.assertFalse(is_approval(u'shipits')) self.assertFalse(is_approval(u'LGTM.')) self.assertFalse(is_approval(u'Looks good to me'))
def get_needs_revision_facts(iw, meta, ci, maintainer_team=None, botnames=None): # Thanks @adityacs for this PR. This PR requires revisions, either # because it fails to build or by reviewer request. Please make the # suggested revisions. When you are done, please comment with text # 'ready_for_review' and we will put this PR back into review. # a "dirty" mergeable_state can exist with "successfull" ci_state. if maintainer_team is None: maintainer_team = [] if botnames is None: botnames = [] committer_count = None needs_revision = False needs_revision_msgs = [] merge_commits = [] has_merge_commit_notification = False needs_rebase = False needs_rebase_msgs = [] ci_state = None ci_stale = False mstate = None change_requested = None ready_for_review = None has_commit_mention = False has_commit_mention_notification = False has_ci = False has_remote_repo = None user_reviews = None stale_reviews = {} # https://github.com/ansible/ansibullbot/issues/302 has_multiple_modules = False needs_multiple_new_modules_notification = False rmeta = { 'committer_count': committer_count, 'is_needs_revision': needs_revision, 'is_needs_revision_msgs': needs_revision_msgs, 'is_needs_rebase': needs_rebase, 'is_needs_rebase_msgs': needs_rebase_msgs, 'has_commit_mention': has_commit_mention, 'has_commit_mention_notification': has_commit_mention_notification, 'has_ci': has_ci, 'merge_commits': merge_commits, 'has_merge_commit_notification': has_merge_commit_notification, 'mergeable': None, 'mergeable_state': mstate, 'change_requested': change_requested, 'ci_state': ci_state, 'ci_stale': ci_stale, 'reviews': None, 'ready_for_review': ready_for_review, 'has_remote_repo': has_remote_repo, 'stale_reviews': stale_reviews, 'has_multiple_modules': has_multiple_modules, 'needs_multiple_new_modules_notification': needs_multiple_new_modules_notification } if not iw.is_pullrequest(): return rmeta bpcs = iw.history.get_boilerplate_comments() bpcs = [x[0] for x in bpcs] maintainers = [x for x in maintainer_team if x not in botnames] maintainers += meta.get('component_maintainers', []) try: ci_date = ci.get_last_full_run_date() except NoCIError: pass else: has_ci = True if ci_date: ci_stale = (datetime.datetime.now() - ci_date).days > CI_STALE_DAYS ci_state = ci.state logging.info('ci_state == %s' % ci_state) # clean/unstable/dirty/unknown mstate = iw.mergeable_state if not mstate: mstate = 'unknown' logging.info('mergeable_state == %s' % mstate) # clean/unstable/dirty/unknown if mstate != 'clean': if ci_state == 'failure': needs_revision = True needs_revision_msgs.append('ci failure') if mstate == 'dirty': needs_revision = True needs_rebase = True needs_revision_msgs.append('mergeable state is dirty') needs_rebase_msgs.append('mergeable state is dirty') elif mstate == 'unknown': # if tests are still running, this needs to be ignored. if ci_state not in ['pending']: needs_revision = True needs_revision_msgs.append('mergeable state is unknown') needs_rebase = True needs_rebase_msgs.append('mergeable state is unknown') elif mstate == 'unstable': # reduce the label churn if ci_state == 'pending' and 'needs_revision' in iw.labels: needs_revision = True needs_rebase_msgs.append('keep label till test finished') if ci_state is None: needs_revision = True # FIXME mstate == 'draft' else: shipits = {} # key: actor, value: created_at has_set_needs_revision = set() for event in iw.history.history: if event['actor'] in botnames: continue if event['actor'] in maintainers and \ event['actor'] != iw.submitter: if event['event'] == 'labeled': if event['label'] == 'needs_revision': needs_revision = True needs_revision_msgs.append('[%s] labeled' % event['actor']) has_set_needs_revision.add(event['actor']) continue if event['event'] == 'unlabeled': if event['label'] == 'needs_revision': needs_revision = False needs_revision_msgs.append('[%s] unlabeled' % event['actor']) continue if event['event'] == 'commented': if is_approval(event['body']): shipits[event['actor']] = event['created_at'] if '!needs_revision' in event['body']: needs_revision = False needs_revision_msgs.append('[%s] !needs_revision' % event['actor']) continue if any(line.startswith('needs_revision') for line in event['body'].splitlines()) and \ '!needs_revision' not in event['body']: needs_revision = True needs_revision_msgs.append('[%s] needs_revision' % event['actor']) has_set_needs_revision.add(event['actor']) continue if 'shipit' in event['body'].lower(): if event['actor'] in has_set_needs_revision: has_set_needs_revision.remove(event['actor']) if not has_set_needs_revision: needs_revision = False continue if event['actor'] == iw.submitter: if event['event'] == 'commented': if 'ready_for_review' in event['body']: if ready_for_review is None or event[ 'created_at'] > ready_for_review: ready_for_review = event['created_at'] needs_revision = False needs_revision_msgs.append('[%s] ready_for_review' % event['actor']) continue if 'shipit' in event['body'].lower(): if ready_for_review is None or event[ 'created_at'] > ready_for_review: ready_for_review = event['created_at'] needs_revision = False needs_revision_msgs.append('[%s] shipit' % event['actor']) continue # This is a complicated algo ... sigh user_reviews = _get_review_state( iw.reviews, iw.submitter, ) if user_reviews: last_commit = iw.commits[-1].sha change_requested = _changes_requested_by(user_reviews, shipits, last_commit, ready_for_review) if change_requested: needs_revision = True needs_revision_msgs.append('outstanding reviews: %s' % ','.join(change_requested)) # Merge commits are bad, force a rebase if iw.merge_commits: needs_rebase = True for mc in iw.merge_commits: merge_commits.append(mc.html_url) needs_rebase_msgs.append('merge commit %s' % mc.commit.sha) if 'merge_commit_notify' not in bpcs: has_merge_commit_notification = False else: mc_comments = iw.history.search_user_comments( botnames, 'boilerplate: merge_commit_notify') last_mc_comment = mc_comments[-1] mc_missing = [] for mc in iw.merge_commits: if mc.html_url not in last_mc_comment: mc_missing.append(mc) if mc_missing: has_merge_commit_notification = False else: has_merge_commit_notification = True # Count committers committer_count = len(sorted(set(iw.committer_emails))) # we don't like @you in the commit messages # https://github.com/ansible/ansibullbot/issues/375 for x in iw.commits: words = x.commit.message.split() if not words: continue if [x for x in words if x.startswith('@') and not x.endswith('@')]: has_commit_mention = True needs_revision = True needs_revision_msgs.append('@ in commit message') break # make sure they're notified about the problem if has_commit_mention: if 'commit_msg_mentions' in bpcs: has_commit_mention_notification = True # keep track of who deleted their repo/branch has_remote_repo = bool(iw.pullrequest.head.repo) # stale reviews if user_reviews: now = pytz.utc.localize(datetime.datetime.now()) commits = [x for x in iw.history.history if x['event'] == 'committed'] lc_date = commits[-1]['created_at'] stale_reviews = {} for actor, review in user_reviews.items(): if review['state'] != 'CHANGES_REQUESTED': continue lrd = None for x in iw.history.history: if x['actor'] != actor: continue if x['event'] == 'review_changes_requested': if not lrd or lrd < x['created_at']: lrd = x['created_at'] elif x['event'] == 'commented' and is_approval(x['body']): if lrd and lrd < x['created_at']: lrd = None if lrd: age = (now - lc_date).days delta = (lc_date - lrd).days if (lc_date > lrd) and (age > 7): stale_reviews[actor] = { 'age': age, 'delta': delta, 'review_date': lrd.isoformat(), 'commit_date': lc_date.isoformat() } # https://github.com/ansible/ansibullbot/issues/302 if len(iw.new_modules) > 1: has_multiple_modules = True if 'multiple_module_notify' not in bpcs: needs_multiple_new_modules_notification = True needs_revision = True needs_revision_msgs.append('multiple new modules') logging.info('mergeable_state is %s' % mstate) logging.info('needs_rebase is %s' % needs_rebase) logging.info('needs_revision is %s' % needs_revision) logging.info('ready_for_review is %s' % ready_for_review) rmeta = { 'committer_count': committer_count, 'is_needs_revision': needs_revision, 'is_needs_revision_msgs': needs_revision_msgs, 'is_needs_rebase': needs_rebase, 'is_needs_rebase_msgs': needs_rebase_msgs, 'has_ci': has_ci, 'has_commit_mention': has_commit_mention, 'has_commit_mention_notification': has_commit_mention_notification, 'merge_commits': merge_commits, 'has_merge_commit_notification': has_merge_commit_notification, 'mergeable': iw.mergeable, 'mergeable_state': mstate, 'change_requested': change_requested, 'ci_state': ci_state, 'ci_stale': ci_stale, 'reviews': iw.reviews, 'ready_for_review_date': ready_for_review, 'ready_for_review': bool(ready_for_review), 'has_remote_repo': has_remote_repo, 'stale_reviews': stale_reviews, 'has_multiple_modules': has_multiple_modules, 'needs_multiple_new_modules_notification': needs_multiple_new_modules_notification } if rmeta['ready_for_review_date']: rmeta['ready_for_review_date'] = rmeta[ 'ready_for_review_date'].isoformat() return rmeta
def get_needs_revision_facts(triager, issuewrapper, meta, shippable=None): # Thanks @adityacs for this PR. This PR requires revisions, either # because it fails to build or by reviewer request. Please make the # suggested revisions. When you are done, please comment with text # 'ready_for_review' and we will put this PR back into review. # a "dirty" mergeable_state can exist with "successfull" ci_state. # short alias iw = issuewrapper committer_count = None needs_revision = False needs_revision_msgs = [] merge_commits = [] has_merge_commit_notification = False needs_rebase = False needs_rebase_msgs = [] has_shippable = False has_landscape = False has_travis = False has_travis_notification = False has_zuul = False ci_state = None ci_stale = None mstate = None change_requested = None ready_for_review = None has_commit_mention = False has_commit_mention_notification = False has_shippable_yaml = None has_shippable_yaml_notification = None has_remote_repo = None user_reviews = None stale_reviews = {} # https://github.com/ansible/ansibullbot/issues/302 has_multiple_modules = False needs_multiple_new_modules_notification = False rmeta = { u'committer_count': committer_count, u'is_needs_revision': needs_revision, u'is_needs_revision_msgs': needs_revision_msgs, u'is_needs_rebase': needs_rebase, u'is_needs_rebase_msgs': needs_rebase_msgs, u'has_commit_mention': has_commit_mention, u'has_commit_mention_notification': has_commit_mention_notification, u'has_shippable': has_shippable, u'has_landscape': has_landscape, u'has_travis': has_travis, u'has_travis_notification': has_travis_notification, u'has_zuul': has_zuul, u'merge_commits': merge_commits, u'has_merge_commit_notification': has_merge_commit_notification, u'mergeable': None, u'mergeable_state': mstate, u'change_requested': change_requested, u'ci_state': ci_state, u'ci_stale': ci_stale, u'reviews': None, #'www_reviews': None, #'www_summary': None, u'ready_for_review': ready_for_review, u'has_shippable_yaml': has_shippable_yaml, u'has_shippable_yaml_notification': has_shippable_yaml_notification, u'has_remote_repo': has_remote_repo, u'stale_reviews': stale_reviews, u'has_multiple_modules': has_multiple_modules, u'needs_multiple_new_modules_notification': needs_multiple_new_modules_notification } if not iw.is_pullrequest(): return rmeta bpcs = iw.history.get_boilerplate_comments() bpcs = [x[0] for x in bpcs] # Scrape web data for debug purposes #rfn = iw.repo_full_name #www_summary = triager.gws.get_single_issue_summary(rfn, iw.number) #www_reviews = triager.gws.scrape_pullrequest_review(rfn, iw.number) maintainers = [x for x in triager.ansible_core_team if x not in triager.BOTNAMES] #if meta.get('module_match'): # maintainers += meta['module_match'].get('maintainers', []) maintainers += meta.get(u'component_maintainers', []) # get the exact state from shippable ... # success/pending/failure/... ? ci_status = iw.pullrequest_status # check if this has shippable and or travis if ci_status: for x in ci_status: if u'travis-ci.org' in x[u'target_url']: has_travis = True continue if x.get('context') == 'Shippable': has_shippable = True continue # code quality hooks if [x for x in ci_status if isinstance(x, dict) and u'landscape.io' in x[u'target_url']]: has_landscape = True if [x for x in ci_status if isinstance(x, dict) and u'zuul' in x[u'target_url']]: has_zuul = True ci_states = [x[u'state'] for x in ci_status if isinstance(x, dict) and x.get('context') == 'Shippable'] if not ci_states: ci_state = None else: ci_state = ci_states[0] logging.info(u'ci_state == %s' % ci_state) # decide if the CI run is "stale" if not has_shippable: ci_stale = True else: # https://github.com/ansible/ansibullbot/issues/935 ci_date = get_last_shippable_full_run_date(ci_status, shippable) # https://github.com/ansible/ansibullbot/issues/458 if ci_date: ci_date = datetime.datetime.strptime(ci_date, u'%Y-%m-%dT%H:%M:%S.%fZ') ci_delta = (datetime.datetime.now() - ci_date).days if ci_delta > 7: ci_stale = True else: ci_stale = False else: ci_stale = False # clean/unstable/dirty/unknown mstate = iw.mergeable_state if not mstate: mstate = u'unknown' logging.info(u'mergeable_state == %s' % mstate) # clean/unstable/dirty/unknown if mstate != u'clean': if ci_state == u'failure': needs_revision = True needs_revision_msgs.append(u'ci failure') if mstate == u'dirty': needs_revision = True needs_rebase = True needs_revision_msgs.append(u'mergeable state is dirty') needs_rebase_msgs.append(u'mergeable state is dirty') elif mstate == u'unknown': # if tests are still running, this needs to be ignored. if ci_state not in [u'pending']: needs_revision = True needs_revision_msgs.append(u'mergeable state is unknown') needs_rebase = True needs_rebase_msgs.append(u'mergeable state is unknown') elif mstate == u'unstable': # reduce the label churn if ci_state == u'pending' and u'needs_revision' in iw.labels: needs_revision = True needs_rebase_msgs.append(u'keep label till test finished') else: #current_hash = None #pending_reviews = [] #hash_reviews = {} user_reviews = {} shipits = {} # key: actor, value: created_at has_set_needs_revision = set() for event in iw.history.history: if event[u'actor'] in triager.BOTNAMES: continue if event[u'actor'] in maintainers and \ event[u'actor'] != iw.submitter: if event[u'event'] == u'labeled': if event[u'label'] == u'needs_revision': needs_revision = True needs_revision_msgs.append( u'[%s] labeled' % event[u'actor'] ) has_set_needs_revision.add(event[u'actor']) continue if event[u'event'] == u'unlabeled': if event[u'label'] == u'needs_revision': needs_revision = False needs_revision_msgs.append( u'[%s] unlabeled' % event[u'actor'] ) continue if event[u'event'] == u'commented': if is_approval(event[u'body']): shipits[event[u'actor']] = event[u'created_at'] if u'!needs_revision' in event[u'body']: needs_revision = False needs_revision_msgs.append( u'[%s] !needs_revision' % event[u'actor'] ) continue if u'needs_revision' in event[u'body'] and \ u'!needs_revision' not in event[u'body']: needs_revision = True needs_revision_msgs.append( u'[%s] needs_revision' % event[u'actor'] ) has_set_needs_revision.add(event[u'actor']) continue if u'shipit' in event[u'body'].lower(): if event[u'actor'] in has_set_needs_revision: has_set_needs_revision.remove(event[u'actor']) if not has_set_needs_revision: needs_revision = False continue if event[u'actor'] == iw.submitter: if event[u'event'] == u'commented': if u'ready_for_review' in event[u'body']: if ready_for_review is None or event[u'created_at'] > ready_for_review: ready_for_review = event[u'created_at'] needs_revision = False needs_revision_msgs.append( u'[%s] ready_for_review' % event[u'actor'] ) continue if u'shipit' in event[u'body'].lower(): #ready_for_review = True if ready_for_review is None or event[u'created_at'] > ready_for_review: ready_for_review = event[u'created_at'] needs_revision = False needs_revision_msgs.append( u'[%s] shipit' % event[u'actor'] ) continue # This is a complicated algo ... sigh user_reviews = get_review_state( iw.reviews, iw.submitter, number=iw.number, store=True ) if user_reviews: last_commit = iw.commits[-1].sha change_requested = changes_requested_by(user_reviews, shipits, last_commit, ready_for_review) if change_requested: needs_revision = True needs_revision_msgs.append( u'outstanding reviews: %s' % u','.join(change_requested) ) # Merge commits are bad, force a rebase if iw.merge_commits: needs_rebase = True for mc in iw.merge_commits: merge_commits.append(mc.html_url) needs_rebase_msgs.append(u'merge commit %s' % mc.commit.sha) if u'merge_commit_notify' not in bpcs: has_merge_commit_notification = False else: mc_comments = iw.history.search_user_comments( triager.BOTNAMES, u'boilerplate: merge_commit_notify' ) last_mc_comment = mc_comments[-1] mc_missing = [] for mc in iw.merge_commits: if mc.html_url not in last_mc_comment: mc_missing.append(mc) if mc_missing: has_merge_commit_notification = False else: has_merge_commit_notification = True # Count committers committer_count = len(sorted(set(iw.committer_emails))) # we don't like @you in the commit messages # https://github.com/ansible/ansibullbot/issues/375 for x in iw.commits: words = x.commit.message.split() if not words: continue if [x for x in words if x.startswith(u'@') and not x.endswith(u'@')]: has_commit_mention = True needs_revision = True needs_revision_msgs.append(u'@ in commit message') break # make sure they're notified about the problem if has_commit_mention: if u'commit_msg_mentions' in bpcs: has_commit_mention_notification = True if has_travis: needs_rebase = True needs_rebase_msgs.append(u'travis-ci found in status') # 'has_travis_notification': has_travis_notification, if u'travis_notify' in bpcs: has_travis_notification = True else: has_travis_notification = False # keep track of who deleted their repo/branch if iw.pullrequest.head.repo: has_remote_repo = True else: has_remote_repo = False # https://github.com/ansible/ansibullbot/issues/406 has_shippable_yaml = iw.pullrequest_filepath_exists(u'shippable.yml') if not has_shippable_yaml: needs_rebase = True needs_rebase_msgs.append(u'missing shippable.yml') if u'no_shippable_yaml' in bpcs: has_shippable_yaml_notification = True else: has_shippable_yaml_notification = False # stale reviews if user_reviews: now = pytz.utc.localize(datetime.datetime.now()) commits = [x for x in iw.history.history if x[u'event'] == u'committed'] lc_date = commits[-1][u'created_at'] stale_reviews = {} for actor, review in user_reviews.items(): if review[u'state'] != u'CHANGES_REQUESTED': continue lrd = None for x in iw.history.history: if x[u'actor'] != actor: continue if x[u'event'] == u'review_changes_requested': if not lrd or lrd < x[u'created_at']: lrd = x[u'created_at'] elif x[u'event'] == u'commented' and is_approval(x[u'body']): if lrd and lrd < x[u'created_at']: lrd = None if lrd: age = (now - lc_date).days delta = (lc_date - lrd).days if (lc_date > lrd) and (age > 7): stale_reviews[actor] = { u'age': age, u'delta': delta, u'review_date': lrd.isoformat(), u'commit_date': lc_date.isoformat() } # https://github.com/ansible/ansibullbot/issues/302 if len(iw.new_modules) > 1: has_multiple_modules = True if u'multiple_module_notify' not in bpcs: needs_multiple_new_modules_notification = True needs_revision = True needs_revision_msgs.append(u'multiple new modules') logging.info(u'mergeable_state is %s' % mstate) logging.info(u'needs_rebase is %s' % needs_rebase) logging.info(u'needs_revision is %s' % needs_revision) logging.info(u'ready_for_review is %s' % ready_for_review) rmeta = { u'committer_count': committer_count, u'is_needs_revision': needs_revision, u'is_needs_revision_msgs': needs_revision_msgs, u'is_needs_rebase': needs_rebase, u'is_needs_rebase_msgs': needs_rebase_msgs, u'has_shippable': has_shippable, u'has_landscape': has_landscape, u'has_travis': has_travis, u'has_travis_notification': has_travis_notification, u'has_commit_mention': has_commit_mention, u'has_commit_mention_notification': has_commit_mention_notification, u'merge_commits': merge_commits, u'has_merge_commit_notification': has_merge_commit_notification, u'mergeable': iw.pullrequest.mergeable, u'mergeable_state': mstate, u'change_requested': change_requested, u'ci_state': ci_state, u'ci_stale': ci_stale, u'reviews': iw.reviews, #'www_summary': www_summary, #'www_reviews': www_reviews, u'ready_for_review_date': ready_for_review, u'ready_for_review': bool(ready_for_review), u'has_shippable_yaml': has_shippable_yaml, u'has_shippable_yaml_notification': has_shippable_yaml_notification, u'has_remote_repo': has_remote_repo, u'stale_reviews': stale_reviews, u'has_multiple_modules': has_multiple_modules, u'needs_multiple_new_modules_notification': needs_multiple_new_modules_notification } if rmeta[u'ready_for_review_date']: rmeta[u'ready_for_review_date'] = rmeta[u'ready_for_review_date'].isoformat() return rmeta