blob: edaa86da0b337d6f2bbb6898aedda27e99cc716a [file] [log] [blame]
#!/usr/bin/env 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.
### view commits not yet rolled to a layer
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
LAYERS = [
'vendor', # For checkouts building on top of Topaz.
'topaz',
'peridot',
'garnet',
'zircon',
]
# Authors whose commits are not displayed.
IGNORED_AUTHORS = [
'skia-fuchsia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com',
'third-party-roller',
'topaz-roller',
'peridot-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_lower_layer(layer):
"""Returns the layer immediately below the given layer."""
if layer not in LAYERS:
# Special case for "build" and "scripts".
return None
index = LAYERS.index(layer)
if index == len(LAYERS) - 1:
return None
return LAYERS[index + 1]
def get_lower_layer_commit(layer, at, lower_layer):
"""Returns the pinned revision of a layer below the given layer."""
url = ('https://fuchsia.googlesource.com/%s/+/%s/manifest/%s?format=TEXT'
% (layer, at, layer))
content = http_get(url)
content = base64.b64decode(content)
manifest = xml.fromstring(content)
nodes = manifest.findall('./imports/import[@name="%s"]' % lower_layer)
return (lower_layer, nodes[0].get('revision'))
def get_lower_layer_commits(layer, at):
"""Returns the pinned revision of the 'layers' below the given layer.
Multiple 'layers' are needed for the Garnet case, where we want to be
able to inspect the revisions of //build and //scripts.
"""
if layer != 'garnet':
lower_layer = get_lower_layer(layer)
if not lower_layer:
return []
return [get_lower_layer_commit(layer, at, lower_layer)]
return [
get_lower_layer_commit('garnet', at, 'zircon'),
get_lower_layer_commit('garnet', at, 'build'),
get_lower_layer_commit('garnet', at, 'scripts'),
]
def get_commits(layer, revision):
"""Returns the commits in the given layer up to a given commit."""
url = 'https://fuchsia.googlesource.com/%s/+log/master?format=JSON' % layer
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)
for commit in data['log']:
if commit['commit'] == revision:
return
result.append(commit)
get_more(result, start=content['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(layer, commits):
"""Prints the given commits in a user=pleasing format."""
commits = [c for c in commits if filter_commit(c)]
if commits:
timestamp = commits[-1]['committer']['time']
elapsed_time = get_time_since(timestamp)
else:
elapsed_time = ''
print('--------------')
print('| %s | %s' % ('{:^10}'.format(layer), elapsed_time))
print('--------------')
for commit in commits:
print('%s | %s | %s' % (commit['commit'][:7],
commit['author']['name'][:15].ljust(15),
commit['message'].splitlines()[0]))
if not commits:
print('None')
def main():
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter,
description="""Displays the commits not yet rolled to a given layer.
For vendor layers, specify the Topaz revision with --topaz-revision.""")
parser.add_argument('--layer',
help='Name of the layer to inspect',
choices=LAYERS,
default='topaz')
parser.add_argument('--topaz-revision',
help='(vendor only) revision of the Topaz layer')
args = parser.parse_args()
layer = args.layer
revision = args.topaz_revision
if layer != 'vendor' and revision:
print('Warning: Topaz revision only used with vendor layers.')
if layer == 'vendor' and not revision:
print('Error: vendor layers require a Topaz revision.')
return 1
print('Pending commits for: %s' % layer.upper())
if layer == 'vendor':
commits = get_commits('topaz', revision)
print_commits('topaz', commits)
layer = 'topaz'
else:
revision = 'master'
next_layers = [(layer, revision)]
while next_layers:
layer, revision = next_layers.pop(0)
layer_refs = get_lower_layer_commits(layer, revision)
if not layer_refs:
continue
for lower_layer, lower_revision in layer_refs:
commits = get_commits(lower_layer, lower_revision)
print_commits(lower_layer, commits)
next_layers.extend(layer_refs)
return 0
if __name__ == "__main__":
sys.exit(main())