| #!/usr/bin/env fuchsia-vendored-python | 
 | # Copyright 2017 The Fuchsia Authors. All rights reserved. | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 |  | 
 | #### CATEGORY=Source tree | 
 | ### view commits not yet published to global integration | 
 |  | 
 | import argparse | 
 | from argparse import RawTextHelpFormatter | 
 | import base64 | 
 | from datetime import datetime, timedelta, tzinfo | 
 | import json | 
 | import sys | 
 | import urllib2 | 
 | import xml.etree.ElementTree as xml | 
 |  | 
 |  | 
 | PETALS = [ | 
 |     ('experiences', 'https://fuchsia.googlesource.com/integration/+/HEAD/flower?format=TEXT'), | 
 |     ('fuchsia', 'https://fuchsia.googlesource.com/integration/+/HEAD/stem?format=TEXT'), | 
 | ] | 
 |  | 
 |  | 
 | # Authors whose commits are not displayed. | 
 | IGNORED_AUTHORS = [ | 
 |     'skia-fuchsia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com', | 
 |     'third-party-roller', | 
 |     'garnet-roller', | 
 |     'zircon-roller', | 
 | ] | 
 |  | 
 |  | 
 | def http_get(url): | 
 |     """Fetches the content at a given URL.""" | 
 |     try: | 
 |         target = urllib2.urlopen(url) | 
 |         return target.read() | 
 |     finally: | 
 |         if target: | 
 |             target.close() | 
 |  | 
 |  | 
 | def get_published_commit_for(petal): | 
 |     """Returns the pinned revision of a petal in global integration.""" | 
 |     name = petal[0] | 
 |     url = petal[1] | 
 |     content = http_get(url) | 
 |     content = base64.b64decode(content) | 
 |     manifest = xml.fromstring(content) | 
 |     nodes = manifest.findall('./projects/project[@name="%s"]' % name) | 
 |     return (name, nodes[0].get('revision')) | 
 |  | 
 |  | 
 | def get_published_commits(): | 
 |     """Returns the published revision of all the petals.""" | 
 |     return [get_published_commit_for(petal) for petal in PETALS] | 
 |  | 
 |  | 
 | def get_commits(petal, revision): | 
 |     """Returns the commits in the given petal up to a given commit.""" | 
 |     url = 'https://fuchsia.googlesource.com/%s/+log/master?format=JSON' % petal | 
 |     def get_more(result, start=None): | 
 |         get_url = url | 
 |         if start: | 
 |             get_url = '%s&s=%s' % (url, start) | 
 |         content = http_get(get_url) | 
 |         # Remove the anti-XSSI header. | 
 |         content = content[5:] | 
 |         data = json.loads(content.decode('utf-8', errors='replace')) | 
 |         for commit in data['log']: | 
 |             if commit['commit'] == revision: | 
 |                 return | 
 |             result.append(commit) | 
 |         get_more(result, start=data['next']) | 
 |     result = [] | 
 |     get_more(result) | 
 |     return result | 
 |  | 
 |  | 
 | def filter_commit(commit): | 
 |     """Returns True if a commit should be listed.""" | 
 |     return commit['author']['name'] not in IGNORED_AUTHORS | 
 |  | 
 |  | 
 | class MyTimezone(tzinfo): | 
 |     """Simple timezone implementation, since for some reason Python 2.7 doesn't | 
 |        provide one. | 
 |        """ | 
 |  | 
 |     def __init__(self, data=None): | 
 |         self.data = data if data else '+0000' | 
 |  | 
 |     def utcoffset(self, dt): | 
 |         hours = int(self.data[1:3]) | 
 |         minutes = int(self.data[3:5]) | 
 |         delta = timedelta(hours=hours, minutes=minutes) | 
 |         if self.data[0] == '-': | 
 |             delta = -delta | 
 |         return delta | 
 |  | 
 |     def tzname(self, dt): | 
 |         return 'Bogus' | 
 |  | 
 |     def dst(self, dt): | 
 |         return timedelta(0) | 
 |  | 
 |  | 
 | def get_time_since(timestamp): | 
 |     """Returns a string describing the amount of time elapsed since the given | 
 |        timestamp. | 
 |        Timestamp format: Sat Feb 10 03:17:06 2018 +0000 | 
 |        """ | 
 |     timestamp_no_tz = timestamp[:-6] | 
 |     date_no_tz = datetime.strptime(timestamp_no_tz, '%a %b %d %H:%M:%S %Y') | 
 |     date = date_no_tz.replace(tzinfo=MyTimezone(timestamp[-5:])) | 
 |     now = datetime.utcnow().replace(tzinfo=MyTimezone()) | 
 |     delta = now - date | 
 |     if delta.days >= 1: | 
 |         return '>1d' | 
 |     hours = delta.seconds / 3600 | 
 |     if hours >= 1: | 
 |         return '%sh' % hours | 
 |     minutes = (delta.seconds % 3600) / 60 | 
 |     return '%sm' % minutes | 
 |  | 
 |  | 
 | def print_commits(petal, commits, print_all=False): | 
 |     """Prints the given commits in a user=pleasing format.""" | 
 |     commit_filter = (lambda c: c) if print_all else filter_commit | 
 |     commits = filter(commit_filter, commits) | 
 |     if commits: | 
 |         timestamp = commits[-1]['committer']['time'] | 
 |         elapsed_time = get_time_since(timestamp) | 
 |     else: | 
 |         elapsed_time = '' | 
 |     print('--------------') | 
 |     print('| %s | %s | %s commits' % ('{:^10}'.format(petal), elapsed_time, len(commits))) | 
 |     print('--------------') | 
 |     for commit in commits: | 
 |         line = u'%s | %s | %s' % (commit['commit'][:7], | 
 |                                 commit['author']['name'][:15].ljust(15), | 
 |                                 commit['message'].splitlines()[0]) | 
 |         print(line.encode('utf-8', errors='replace')) | 
 |     if not commits: | 
 |         print('None') | 
 |  | 
 |  | 
 | def main(): | 
 |     parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, | 
 |         description="""Displays the commits not yet published to global integration.""") | 
 |     parser.add_argument('--all', | 
 |                         help='Whether to print all commits, including rollers', | 
 |                         action='store_true') | 
 |     args = parser.parse_args() | 
 |  | 
 |     for (petal, published_commit) in get_published_commits(): | 
 |         commits = get_commits(petal, published_commit) | 
 |         print_commits(petal, commits, print_all=args.all) | 
 |  | 
 |     return 0 | 
 |  | 
 |  | 
 | if __name__ == "__main__": | 
 |     sys.exit(main()) |