def commit_message_filter(msg): # type: (bytes) -> Tuple[bytes, Dict[Text, Text]] metadata = {} m = commitparser.BUG_RE.match(msg) if m: bug_bytes, bug_number = m.groups()[:2] if msg.startswith(bug_bytes): prefix = re.compile(br"^%s[^\w\d\[\(]*" % bug_bytes) msg = prefix.sub(b"", msg) metadata[u"bugzilla-url"] = env.bz.bugzilla_url(int(bug_number)) reviewers = u", ".join( item.decode("utf8", "replace") for item in commitparser.parse_reviewers(msg)) if reviewers: metadata["gecko-reviewers"] = reviewers msg = commitparser.replace_reviewers(msg, "") msg = commitparser.strip_commit_metadata(msg) description = msg.splitlines() if description: summary = description.pop(0) summary = summary.rstrip(b"!#$%&(*+,-/:;<=>@[\\^_`{|~").rstrip() msg = summary + (b"\n" + b"\n".join(description) if description else b"") return msg, metadata
def get(self, request, *args, **kwargs): try: parent_request = get_parent_rr(ReviewRequest.objects.get( id=kwargs[self.uri_object_key])) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if parent_request is None: return DOES_NOT_EXIST if not is_parent(parent_request): return NOT_PARENT if not parent_request.is_accessible_by(request.user): return PERMISSION_DENIED if COMMITS_KEY not in parent_request.extra_data: logging.error('Parent review request %s missing COMMIT_KEY' % parent_request.id) return NOT_PARENT result = [] children = json.loads(parent_request.extra_data[COMMITS_KEY]) for child in children: try: child_request = ReviewRequest.objects.get(id=child[1]) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if not child_request.approved: return AUTOLAND_REVIEW_NOT_APPROVED reviewers = [ r.user.username for r in gen_latest_reviews(child_request) if r.ship_it and r.user != child_request.submitter ] if not reviewers and child_request.approved: # This review request is approved (the repeated check is # to ensure this is guaranteed if other parts of the code # change) but we have an empty list of reviewers. We'll # assume the author has just approved this themself and # set r=me reviewers.append('me') result.append({ 'commit': child[0], 'id': child[1], 'reviewers': reviewers, 'summary': replace_reviewers(child_request.description, reviewers) }) return 200, { 'commits': result, 'total_results': len(result), 'links': self.get_links(request=request), }
def get(self, request, *args, **kwargs): try: parent_request = get_parent_rr(ReviewRequest.objects.get( id=kwargs[self.uri_object_key])) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if parent_request is None: return DOES_NOT_EXIST if not is_parent(parent_request): return NOT_PARENT if not parent_request.is_accessible_by(request.user): return PERMISSION_DENIED if COMMITS_KEY not in parent_request.extra_data: logging.error('Parent review request %s missing COMMIT_KEY' % parent_request.id) return NOT_PARENT result = [] children = json.loads(parent_request.extra_data[COMMITS_KEY]) for child in children: try: child_request = ReviewRequest.objects.get(id=child[1]) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if not child_request.approved: return AUTOLAND_REVIEW_NOT_APPROVED reviewers = map(lambda review: review.user.username, gen_latest_reviews(child_request)) result.append({ 'commit': child[0], 'id': child[1], 'reviewers': reviewers, 'summary': replace_reviewers(child_request.description, reviewers) }) return 200, { 'commits': result, 'total_results': len(result), 'links': self.get_links(request=request), }
def commit_message_filter(msg): metadata = {} m = commitparser.BUG_RE.match(msg) if m: bug_str, bug_number = m.groups()[:2] if msg.startswith(bug_str): prefix = re.compile(r"^%s[^\w\d\[\(]*" % bug_str) msg = prefix.sub("", msg) metadata["bugzilla-url"] = env.bz.bugzilla_url(bug_number) reviewers = ", ".join(commitparser.parse_reviewers(msg)) if reviewers: metadata["gecko-reviewers"] = reviewers msg = commitparser.replace_reviewers(msg, "") msg = commitparser.strip_commit_metadata(msg) description = msg.splitlines() if description: summary = description.pop(0) summary = summary.rstrip("!#$%&(*+,-/:;<=>@[\\^_`{|~").rstrip() description = "\n".join(description) msg = summary + ("\n" + description if description else "") return msg, metadata
def test_replace_reviewers(self): # first with r? reviewer request syntax self.assertEqual(replace_reviewers('Bug 1 - some stuff; r?romulus', ['remus']), 'Bug 1 - some stuff; r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff; r?romulus, r?remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff; r?romulus,r?remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff; r?romulus, remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff; r?romulus,remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff; (r?romulus)', ['remus']), 'Bug 1 - More stuff; (r=remus)') self.assertEqual(replace_reviewers('Bug 1 - More stuff; (r?romulus,remus)', ['remus']), 'Bug 1 - More stuff; (r=remus)') self.assertEqual(replace_reviewers('Bug 1 - More stuff; [r?romulus]', ['remus']), 'Bug 1 - More stuff; [r=remus]') self.assertEqual(replace_reviewers('Bug 1 - More stuff; [r?remus, r?romulus]', ['remus']), 'Bug 1 - More stuff; [r=remus]') self.assertEqual(replace_reviewers('Bug 1 - More stuff; r?romulus, a=test-only', ['remus']), 'Bug 1 - More stuff; r=remus, a=test-only') self.assertEqual(replace_reviewers('Bug 1 - More stuff; r?romulus, ux-r=test-only', ['remus', 'romulus']), 'Bug 1 - More stuff; r=remus,romulus, ux-r=test-only') # now with r= review granted syntax self.assertEqual(replace_reviewers('Bug 1 - some stuff; r=romulus', ['remus']), 'Bug 1 - some stuff; r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff; r=romulus, r=remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff; r=romulus,r=remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff; r=romulus, remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff; r=romulus,remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff; (r=romulus)',['remus']), 'Bug 1 - More stuff; (r=remus)') self.assertEqual(replace_reviewers('Bug 1 - More stuff; (r=romulus,remus)', ['remus']), 'Bug 1 - More stuff; (r=remus)') self.assertEqual(replace_reviewers('Bug 1 - More stuff; [r=romulus]', ['remus']), 'Bug 1 - More stuff; [r=remus]') self.assertEqual(replace_reviewers('Bug 1 - More stuff; [r=remus, r=romulus]', ['remus']), 'Bug 1 - More stuff; [r=remus]') self.assertEqual(replace_reviewers('Bug 1 - More stuff; r=romulus, a=test-only', ['remus']), 'Bug 1 - More stuff; r=remus, a=test-only') # try some other separators than ; self.assertEqual(replace_reviewers('Bug 1 - some stuff r=romulus', ['remus']), 'Bug 1 - some stuff r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff. r=romulus, r=remus', ['remus']), 'Bug 1 - More stuff. r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff - r=romulus,r=remus', ['remus']), 'Bug 1 - More stuff - r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff, r=romulus, remus', ['remus']), 'Bug 1 - More stuff, r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff.. r=romulus,remus', ['remus']), 'Bug 1 - More stuff.. r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff | (r=romulus)',['remus']), 'Bug 1 - More stuff | (r=remus)') # make sure things work with different spacing self.assertEqual(replace_reviewers('Bug 1 - some stuff;r=romulus,r=remus', ['remus']), 'Bug 1 - some stuff;r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff.r=romulus, r=remus', ['remus']), 'Bug 1 - More stuff.r=remus') self.assertEqual(replace_reviewers('Bug 1 - More stuff,r=romulus, remus', ['remus']), 'Bug 1 - More stuff,r=remus') self.assertEqual(replace_reviewers( 'Bug 1094764 - Implement AudioContext.suspend and friends. r=roc,ehsan\n' '- Relevant spec text:\n' '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise\n' '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise\n', ['remus']), 'Bug 1094764 - Implement AudioContext.suspend and friends. r=remus\n' '- Relevant spec text:\n' '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise\n' '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise') self.assertEqual(replace_reviewers( 'Bug 380783 - nsStringAPI.h: no equivalent of IsVoid (tell if ' 'string is null), patch by Mook <*****@*****.**>, ' 'r=bsmedberg/dbaron, sr=dbaron, a1.9=bz', ['remus']), 'Bug 380783 - nsStringAPI.h: no equivalent of IsVoid (tell if ' 'string is null), patch by Mook <*****@*****.**>, ' 'r=remus, sr=dbaron, a1.9=bz') self.assertEqual(replace_reviewers( 'Bug 1 - blah r?dminor, r?gps, r?abc, sr=abc', ['dminor', 'glob', 'gps', 'abc']), 'Bug 1 - blah r=dminor,glob,gps,abc, sr=abc') self.assertEqual(replace_reviewers( 'Bug 1 - blah r?dminor r?gps r?abc sr=abc', ['dminor', 'glob', 'gps', 'abc']), 'Bug 1 - blah r=dminor,glob,gps,abc sr=abc') self.assertEqual(replace_reviewers( 'Bug 1 - blah r?dminor,r?gps,r?abc,sr=abc', ['dminor', 'glob', 'gps', 'abc']), 'Bug 1 - blah r=dminor,glob,gps,abc,sr=abc') self.assertEqual(replace_reviewers('Bug 123 - Blah blah; r=gps DONTBUILD (NPOTB)', ['remus']), 'Bug 123 - Blah blah; r=remus DONTBUILD (NPOTB)') self.assertEqual(replace_reviewers('Bug 123 - Blah blah; r=gps DONTBUILD', ['remus']), 'Bug 123 - Blah blah; r=remus DONTBUILD') self.assertEqual(replace_reviewers('Bug 123 - Blah blah; r=gps (DONTBUILD)', ['remus']), 'Bug 123 - Blah blah; r=remus (DONTBUILD)')
def test_replace_reviewers(self): # empty self.assertEqual(replace_reviewers('', ['remus']), 'r=remus') # first with r? reviewer request syntax self.assertEqual( replace_reviewers('Bug 1 - some stuff; r?romulus', ['remus']), 'Bug 1 - some stuff; r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff; r?romulus, r?remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff; r?romulus,r?remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff; r?romulus, remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff; r?romulus,remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff; (r?romulus)', ['remus']), 'Bug 1 - More stuff; (r=remus)') self.assertEqual( replace_reviewers('Bug 1 - More stuff; (r?romulus,remus)', ['remus']), 'Bug 1 - More stuff; (r=remus)') self.assertEqual( replace_reviewers('Bug 1 - More stuff; [r?romulus]', ['remus']), 'Bug 1 - More stuff; [r=remus]') self.assertEqual( replace_reviewers('Bug 1 - More stuff; [r?remus, r?romulus]', ['remus']), 'Bug 1 - More stuff; [r=remus]') self.assertEqual( replace_reviewers('Bug 1 - More stuff; r?romulus, a=test-only', ['remus']), 'Bug 1 - More stuff; r=remus, a=test-only') self.assertEqual( replace_reviewers('Bug 1 - More stuff; r?romulus, ux-r=test-only', ['remus', 'romulus']), 'Bug 1 - More stuff; r=remus,romulus, ux-r=test-only') # now with r= review granted syntax self.assertEqual( replace_reviewers('Bug 1 - some stuff; r=romulus', ['remus']), 'Bug 1 - some stuff; r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff; r=romulus, r=remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff; r=romulus,r=remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff; r=romulus, remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff; r=romulus,remus', ['remus']), 'Bug 1 - More stuff; r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff; (r=romulus)', ['remus']), 'Bug 1 - More stuff; (r=remus)') self.assertEqual( replace_reviewers('Bug 1 - More stuff; (r=romulus,remus)', ['remus']), 'Bug 1 - More stuff; (r=remus)') self.assertEqual( replace_reviewers('Bug 1 - More stuff; [r=romulus]', ['remus']), 'Bug 1 - More stuff; [r=remus]') self.assertEqual( replace_reviewers('Bug 1 - More stuff; [r=remus, r=romulus]', ['remus']), 'Bug 1 - More stuff; [r=remus]') self.assertEqual( replace_reviewers('Bug 1 - More stuff; r=romulus, a=test-only', ['remus']), 'Bug 1 - More stuff; r=remus, a=test-only') # try some other separators than ; self.assertEqual( replace_reviewers('Bug 1 - some stuff r=romulus', ['remus']), 'Bug 1 - some stuff r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff. r=romulus, r=remus', ['remus']), 'Bug 1 - More stuff. r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff - r=romulus,r=remus', ['remus']), 'Bug 1 - More stuff - r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff, r=romulus, remus', ['remus']), 'Bug 1 - More stuff, r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff.. r=romulus,remus', ['remus']), 'Bug 1 - More stuff.. r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff | (r=romulus)', ['remus']), 'Bug 1 - More stuff | (r=remus)') # make sure things work with different spacing self.assertEqual( replace_reviewers('Bug 1 - some stuff;r=romulus,r=remus', ['remus']), 'Bug 1 - some stuff;r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff.r=romulus, r=remus', ['remus']), 'Bug 1 - More stuff.r=remus') self.assertEqual( replace_reviewers('Bug 1 - More stuff,r=romulus, remus', ['remus']), 'Bug 1 - More stuff,r=remus') self.assertEqual( replace_reviewers( 'Bug 1094764 - Implement AudioContext.suspend and friends. r=roc,ehsan\n' '- Relevant spec text:\n' '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise\n' '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise\n', ['remus']), 'Bug 1094764 - Implement AudioContext.suspend and friends. r=remus\n' '- Relevant spec text:\n' '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise\n' '- http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise' ) self.assertEqual( replace_reviewers( 'Bug 380783 - nsStringAPI.h: no equivalent of IsVoid (tell if ' 'string is null), patch by Mook <*****@*****.**>, ' 'r=bsmedberg/dbaron, sr=dbaron, a1.9=bz', ['remus']), 'Bug 380783 - nsStringAPI.h: no equivalent of IsVoid (tell if ' 'string is null), patch by Mook <*****@*****.**>, ' 'r=remus, sr=dbaron, a1.9=bz') self.assertEqual( replace_reviewers('Bug 1 - blah r?dminor, r?gps, r?abc, sr=abc', ['dminor', 'glob', 'gps', 'abc']), 'Bug 1 - blah r=dminor,glob,gps,abc, sr=abc') self.assertEqual( replace_reviewers('Bug 1 - blah r?dminor r?gps r?abc sr=abc', ['dminor', 'glob', 'gps', 'abc']), 'Bug 1 - blah r=dminor,glob,gps,abc sr=abc') self.assertEqual( replace_reviewers('Bug 1 - blah r?dminor,r?gps,r?abc,sr=abc', ['dminor', 'glob', 'gps', 'abc']), 'Bug 1 - blah r=dminor,glob,gps,abc,sr=abc') self.assertEqual( replace_reviewers('Bug 123 - Blah blah; r=gps DONTBUILD (NPOTB)', ['remus']), 'Bug 123 - Blah blah; r=remus DONTBUILD (NPOTB)') self.assertEqual( replace_reviewers('Bug 123 - Blah blah; r=gps DONTBUILD', ['remus']), 'Bug 123 - Blah blah; r=remus DONTBUILD') self.assertEqual( replace_reviewers('Bug 123 - Blah blah; r=gps (DONTBUILD)', ['remus']), 'Bug 123 - Blah blah; r=remus (DONTBUILD)') self.assertEqual( replace_reviewers('Bug 123 - Blah blah; r?', ['remus']), 'Bug 123 - Blah blah; r=remus') self.assertEqual( replace_reviewers('Bug 123 - Blah blah; r? DONTBUILD', ['remus']), 'Bug 123 - Blah blah; r=remus DONTBUILD')
def main(node): # hg rev = http_get(f'https://hg.mozilla.org/mozilla-central/json-rev/{node}', f'{node}-hg') rev['summary'] = rev['desc'].splitlines()[0] rev['user'] = parseaddr(rev['user'])[1] if rev['backedoutby']: backedout_by = rev['backedoutby'][:12] backout = http_get( f'https://hg.mozilla.org/mozilla-central/json-rev/{backedout_by}', f'{backedout_by}-hg') backout['summary'] = backout['desc'].splitlines()[0] backout['user'] = parseaddr(backout['user'])[1] else: backout = None # patch patch = http_get(f'https://hg.mozilla.org/mozilla-central/raw-rev/{node}', f'{node}-patch', is_json=False) # bug-id bug_ids = parse_bug_ids(rev['summary']) if len(bug_ids) == 0: raise CommitException(f'failed to find bug-id in: {rev["summary"]}') if len(bug_ids) > 1: raise CommitException(f'found multiple bug-ids in: {rev["summary"]}') bug_id = bug_ids[0] # bug - meta bmo = 'https://bugzilla.mozilla.org/rest' bug = http_get(f'{bmo}/bug/{bug_id}', f'{node}-bug')['bugs'][0] # bug - history bug_history = http_get(f'{bmo}/bug/{bug_id}/history', f'{node}-bug_history')['bugs'][0]['history'] # bug - attachments bug_attachments = list( http_get(f'{bmo}/bug/{bug_id}/attachment?exclude_fields=data', f'{node}-bug-attachments')['bugs'][str(bug_id)]) # calc stats stats = dict( bug_url=f'https://bugzilla.mozilla.org/{bug_id}', bug_comment_count=bug['comment_count'], bug_product=bug['product'], bug_component=bug['component'], hg_url=f'https://hg.mozilla.org/mozilla-central/rev/{rev["node"]}', summary=rev['summary'], node=rev['node'], author=rev['user'], pusher=rev['pushuser'], people=[], patch_size=len(patch), patch_lines_of_code=len(patch.splitlines()), patches=[], push_timestamp=datetime.datetime.fromtimestamp( rev['pushdate'][0]).strftime('%Y-%m-%dT%H:%M:%SZ'), bug_id=bug_id, bug_created_timestamp=bug['creation_time'], assigned_to=[], status=[], flags=[], triaged=[], ) stats['people'].append(dict(user=bug['creator'], rel='reporter')) stats['people'].append(dict(user=rev['user'], rel='push author')) stats['people'].append(dict(user=rev['pushuser'], rel='push user')) if backout: stats['backout'] = dict( summary=backout['summary'], user=backout['user'], timestamp=datetime.datetime.fromtimestamp( backout['pushdate'][0]).strftime('%Y-%m-%dT%H:%M:%SZ'), ) stats['people'].append(dict(user=backout['user'], rel='backout author')) for attachment in bug_attachments: if not is_patch(attachment): continue patch_data = dict( content_type=attachment['content_type'], id=attachment['id'], timestamp=attachment['creation_time'], user=attachment['creator'], summary=attachment['summary'], status=[], ) if patch_data['content_type'] == ATTACHMENT_TYPE_PHABRICATOR: rev_url = base64.b64decode( http_get( f'{bmo}/bug/attachment/{patch_data["id"]}' '?include_fields=data', f'{patch_data["id"]}-attachment-data', )['attachments'][str(patch_data['id'])]['data']).decode() revision = Revision(rev_url) patch_data['revision'] = dict( url=rev_url, phid=revision.phid, diffs=revision.diffs(), ) stats['patches'].append(patch_data) stats['people'].append( dict(user=attachment['creator'], rel='patch author')) for change_group in bug_history: for change in change_group['changes']: # assigned_to if change['field_name'] == 'assigned_to': stats['assigned_to'].append( dict(user=change['added'], when=change_group['when'])) stats['people'].append( dict(user=change['added'], rel='assigned bug')) # triage (look for status-flags changed, or a component change) if (change['field_name'].startswith('cf_status_firefox') and change['added'] != '---'): stats['triaged'].append( dict( user=change_group['who'], action=f'{change["field_name"]}: {change["added"]}', timestamp=change_group['when'], )) stats['people'].append( dict(user=change_group['who'], rel='triaged')) elif (change['field_name'] == 'component' and change['removed'] == 'Untriaged'): stats['triaged'].append( dict( user=change_group['who'], action=f'{change["field_name"]} -> {change["added"]}', timestamp=change_group['when'], )) stats['people'].append( dict(user=change_group['who'], rel='triaged')) # reviews if change['field_name'] == 'flagtypes.name': add_attachment_flag(stats, change_group, change, 'review') add_attachment_flag(stats, change_group, change, 'feedback') add_bug_flag(stats, change_group, change, 'needinfo') # attachment obsoletion elif change['field_name'] == 'attachments.isobsolete': if change['added'] == '1': status = 'obsoleted' else: status = 'unobsoleted' attachment = find_attachment(stats, change['attachment_id']) if not attachment: raise CommitException(f'attach {change["attachment_id"]}') attachment['status'].append( dict( status=status, timestamp=change_group['when'], )) stats['people'].append( dict(user=change_group['who'], rel='obsoleted attachment')) # status elif change['field_name'] == 'status': stats['status'].append( dict( status=change['added'], user=change_group['who'], timestamp=change_group['when'], )) stats['people'].append( dict(user=change_group['who'], rel='bug status')) # see if we can identify the attachment that landed # this is fairly naive, comparing patch summaries to the commit summary. # this could be improved in a few ways, such as by looking at push # comments and presuming that the order of attachments is the same. # however, it is nearly impossible to be sure, so we'll just do a best # attempt here, generally avoiding false positives. landed_patch = None active_patches = [] # go backwards through attachment statuses. if we most recently # unobsoleted it, or we never obsoleted nor unobsoleted it, then it is # considered active. for patch in stats['patches']: for patch_status in reversed(patch['status']): if patch_status['status'] == 'unobsoleted': active_patches.append(patch) break if patch_status['status'] == 'obsoleted': break else: active_patches.append(patch) if len(active_patches) == 1: # *presume* this is the landed attachment landed_patch = active_patches[0] else: summary_base = commitparser.replace_reviewers(stats['summary'], None) for patch in active_patches: if (commitparser.replace_reviewers(patch['summary'], None) == summary_base): landed_patch = patch break if landed_patch: stats['landed_attachment_id'] = landed_patch['id'] else: print(f'could not determine landed patch', file=sys.stderr) # tidy up stats['people'] = normalize_people(stats['people']) # display print(json.dumps(stats, indent=2, sort_keys=True))
def create_or_update_attachment(self, review_request, review_request_draft, flags): """Create or update the MozReview attachment using the provided flags. The `flags` parameter is an array of flags to set/update/clear. This array matches the Bugzilla flag API: Setting: { 'id': flag.id 'name': 'review', 'status': '?', 'requestee': reviewer.email } Clearing: { 'id': flag.id, 'status': 'X' } """ logger.info('Posting review request %s to bug %d.' % (review_request.id, self.bug_id)) rr_url = get_obj_url(review_request) diff_url = get_diff_url(review_request) # Build the comment. Only post a comment if the diffset has # actually changed. comment = '' if review_request_draft.get_latest_diffset(): diffset_count = review_request.diffset_history.diffsets.count() if diffset_count < 1: # We don't need the first line, since it is also the attachment # summary, which is displayed in the comment. full_commit_msg = review_request_draft.description.partition( '\n')[2].strip() full_commit_msg = strip_commit_metadata(full_commit_msg) if full_commit_msg: full_commit_msg += '\n\n' comment = '%sReview commit: %s\nSee other reviews: %s' % ( full_commit_msg, diff_url, rr_url) else: comment = ('Review request updated; see interdiff: ' '%sdiff/%d-%d/\n' % (rr_url, diffset_count, diffset_count + 1)) # Set up attachment metadata. attachment = self.get_attachment(review_request) params = {} if attachment: params['attachment_id'] = attachment['id'] if attachment['is_obsolete']: params['is_obsolete'] = False else: params['data'] = diff_url params['content_type'] = 'text/x-review-board-request' params['file_name'] = 'reviewboard-%d-url.txt' % review_request.id params['summary'] = replace_reviewers(review_request_draft.summary, None) params['comment'] = comment if flags: params['flags'] = flags if attachment: self.updates.append(params) else: self.creates.append(params)
def get(self, request, *args, **kwargs): try: parent_request = get_parent_rr( ReviewRequest.objects.get(id=kwargs[self.uri_object_key])) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if parent_request is None: return DOES_NOT_EXIST commit_data = fetch_commit_data(parent_request) if not is_parent(parent_request, commit_data): return NOT_PARENT if not parent_request.is_accessible_by(request.user): return PERMISSION_DENIED if COMMITS_KEY not in commit_data.extra_data: logging.error('Parent review request %s missing COMMIT_KEY' % parent_request.id) return NOT_PARENT result = [] children = json.loads(commit_data.extra_data[COMMITS_KEY]) for child in children: try: child_request = ReviewRequest.objects.get(id=child[1]) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if not child_request.approved: return AUTOLAND_REVIEW_NOT_APPROVED reviewers = [ r.user.username for r in gen_latest_reviews(child_request) if r.ship_it and r.user != child_request.submitter ] if not reviewers and child_request.approved: # This review request is approved (the repeated check is # to ensure this is guaranteed if other parts of the code # change) but we have an empty list of reviewers. We'll # assume the author has just approved this themself. reviewers.append(child_request.submitter.username) # Detect if the commit has been changed since the last review. shipit_carryforward = has_shipit_carryforward(child_request) result.append({ 'commit': child[0], 'id': child[1], 'reviewers': reviewers, 'shipit_carryforward': shipit_carryforward, 'summary': replace_reviewers(child_request.description, reviewers) }) return 200, { 'commits': result, 'total_results': len(result), 'links': self.get_links(request=request), }
def create_or_update_attachment(self, review_request_id, summary, comment, url, carry_forward): """Creates or updates an attachment containing a review-request URL. The carry_forward argument should be a dictionary mapping reviewer email to a boolean indicating if that reviewer's flag should be left untouched. """ logger.info('Posting review request %s to bug %d.' % (review_request_id, self.bug_id)) # Copy because we modify it. carry_forward = carry_forward.copy() params = {} flags = [] rb_attachment = None self._update_attachments() # Find the associated attachment, then go through the review flags. for a in self.attachments: # Make sure we check for old-style URLs as well. if not self.bugzilla._rb_attach_url_matches(a['data'], url): continue rb_attachment = a # FIXME: There's some redundancy between here and # attachments.update_bugzilla_attachment(). The latter decides # which flags to carry forward based on their type and value, but # we also check flag types and variables here to decide, in turn, # if we should even check the carry_forward dict. This should be # unified and clarified somehow. for f in a.get('flags', []): if f['name'] not in ['review', 'feedback']: # We only care about review and feedback flags. continue elif f['name'] == 'feedback': # We always clear feedback flags. flags.append({'id': f['id'], 'status': 'X'}) elif f['status'] == '+' or f['status'] == '-': # A reviewer has left a review, either in Review Board or # in Bugzilla. if f['setter'] not in carry_forward: # This flag was set manually in Bugzilla rather # then through a review on Review Board. Always # clear these flags. flags.append({'id': f['id'], 'status': 'X'}) else: # This flag was set through Review Board; see if # we should carry it forward. if not carry_forward[f['setter']]: # We should not carry this r+/r- forward so # re-request review. flags.append({ 'id': f['id'], 'name': 'review', 'status': '?', 'requestee': f['setter'] }) # else we leave the flag alone, carrying it forward. # In either case, we've dealt with this reviewer, so # remove it from the carry_forward dict. carry_forward.pop(f['setter']) elif ('requestee' not in f or f['requestee'] not in carry_forward): # We clear review flags where the requestee is not # a reviewer, or if there is some (possibly future) flag # other than + or - that does not have a 'requestee' field. flags.append({'id': f['id'], 'status': 'X'}) elif f['requestee'] in carry_forward: # We're already waiting for a review from this user # so don't touch the flag. carry_forward.pop(f['requestee']) break # Add flags for new reviewers. # We can't set a missing r+ (if it was manually removed) except in the # trivial (and useless) case that the setter and the requestee are the # same person. We could set r? again, but in the event that the # reviewer is not accepting review requests, this will block # publishing, with no way for the author to fix it. So we'll just # ignore manually removed r+s. # This is sorted so behavior is deterministic (this mucks with test # output otherwise). for reviewer, keep in sorted(carry_forward.iteritems()): if not keep: flags.append({ 'name': 'review', 'status': '?', 'requestee': reviewer, 'new': True }) if rb_attachment: params['attachment_id'] = rb_attachment['id'] if rb_attachment['is_obsolete']: params['is_obsolete'] = False else: params['data'] = url params['content_type'] = 'text/x-review-board-request' params['file_name'] = 'reviewboard-%d-url.txt' % review_request_id params['summary'] = replace_reviewers(summary, None) params['comment'] = comment if flags: params['flags'] = flags if rb_attachment: self.updates.append(params) else: self.creates.append(params)