Chromium Code Reviews| Index: git_cl.py |
| diff --git a/git_cl.py b/git_cl.py |
| index 4eee3f152853d4a746bc901c465da0355397c3e5..a3f4d006629e7028036bed7809eb076e794b08c6 100755 |
| --- a/git_cl.py |
| +++ b/git_cl.py |
| @@ -11,6 +11,7 @@ from distutils.version import LooseVersion |
| from multiprocessing.pool import ThreadPool |
| import base64 |
| import glob |
| +import httplib |
| import json |
| import logging |
| import optparse |
| @@ -21,6 +22,8 @@ import stat |
| import sys |
| import tempfile |
| import textwrap |
| +import threading |
| +import time |
| import urllib2 |
| import urlparse |
| import webbrowser |
| @@ -33,13 +36,16 @@ except ImportError: |
| from third_party import colorama |
| +from third_party import httplib2 |
| from third_party import upload |
| +from third_party.google_api_python_client import apiclient |
|
Vadim Sh.
2015/04/10 02:24:29
it is not vendored properly:
Traceback (most rece
nodir
2015/04/13 16:04:53
"import apiclient" should work, we use it in other
sheyang
2015/04/14 01:30:22
Per discussion with Nodir@ I added the directory t
|
| import breakpad # pylint: disable=W0611 |
| import clang_format |
| import dart_format |
| import fix_encoding |
| import gclient_utils |
| import git_common |
| +import oauth2 |
|
Vadim Sh.
2015/04/10 02:24:29
this file has been replaced by auth.py in https://
sheyang
2015/04/14 01:30:22
Done.
|
| import owners |
| import owners_finder |
| import presubmit_support |
| @@ -61,6 +67,13 @@ REFS_THAT_ALIAS_TO_OTHER_REFS = { |
| 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master', |
| } |
| +# Buildbucket-related constants |
| +DISCOVERY_URL = ( |
| + 'https://cr-buildbucket.appspot.com/_ah/api/discovery/v1/apis/' |
| + '{api}/{apiVersion}/rest') |
| +DEFAULT_SCOPES = 'email' |
|
Vadim Sh.
2015/04/10 02:24:29
not used
sheyang
2015/04/14 01:30:22
Removed.
|
| +BUILDSET_STR = 'patch/rietveld/{hostname}/{issue}/{patch}' |
| + |
| # Valid extensions for files we want to lint. |
| DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" |
| DEFAULT_LINT_IGNORE_REGEX = r"$^" |
| @@ -215,6 +228,95 @@ def is_dirty_git_tree(cmd): |
| return False |
| +def _prefix_master(master): |
| + prefix = 'master.' |
| + if master.startswith(prefix): |
| + return master |
| + else: |
| + return '%s%s' % (prefix, master) |
| + |
| + |
| +def _get_buildbucket(credentials): |
|
Vadim Sh.
2015/04/10 02:24:29
with auth.py it would be:
def _get_buildbucket(ri
sheyang
2015/04/14 01:30:22
Done.
|
| + http = httplib2.Http() |
| + http = credentials.authorize(http) |
| + return apiclient.discovery.build( |
| + 'buildbucket', 'v1', |
| + http=http, |
| + discoveryServiceUrl=DISCOVERY_URL, |
| + ) |
| + |
| + |
| +def trigger_distributed_try_jobs( |
| + credentials, changelist, options, masters, category): |
| + buildbucket = _get_buildbucket(credentials) |
| + creds_props = json.loads(credentials.to_json()) |
| + requester = creds_props['id_token']['email'] |
|
Vadim Sh.
2015/04/10 02:24:29
this is currently not exposed via auth.py, though
sheyang
2015/04/14 01:30:22
This field will be populated from oauth2 credentia
|
| + issue_props = changelist.GetIssueProperties() |
| + rietveld_host = urlparse.urlparse(changelist.GetRietveldServer()).hostname |
| + issue = changelist.GetIssue() |
| + patchset = changelist.GetMostRecentPatchset() |
| + buildset = BUILDSET_STR.format( |
| + hostname=rietveld_host, |
| + issue=str(issue), |
| + patch=str(patchset)) |
| + print 'Tried jobs on:' |
| + for (master, builders_and_tests) in masters.iteritems(): |
| + print 'Master: %s' % master |
| + bucket = _prefix_master(master) |
| + for builder, tests in builders_and_tests.iteritems(): |
| + req = buildbucket.put(body={ |
| + 'bucket': bucket, |
| + 'parameters_json': json.dumps({ |
| + 'builder_name': builder, |
| + 'changes':[ |
| + {'author': {'email': issue_props['owner_email']}}, |
| + ], |
| + 'properties': { |
| + 'category': category, |
| + 'clobber': options.clobber, |
| + 'issue': issue, |
| + 'master': master, |
| + 'patch_project': issue_props['project'], |
| + 'patch_storage': 'rietveld', |
| + 'patchset': patchset, |
| + 'reason': options.name, |
| + 'requester': requester, |
|
Vadim Sh.
2015/04/10 02:24:29
the server should derive it from oauth credentials
nodir
2015/04/10 02:31:29
buidbucket does not read this property. It is inte
nodir
2015/04/13 16:04:53
This part was wrong because CQ sets requester to "
sheyang
2015/04/14 01:30:22
Acknowledged.
|
| + 'revision': options.revision, |
| + 'rietveld': changelist.GetRietveldServer(), |
| + 'testfilter': tests, |
| + }, |
| + }), |
| + 'tags': ['buildset:%s' % buildset, |
| + 'master:%s' % master, |
| + 'builder:%s' % builder, |
| + 'requester:%s' % requester] |
|
nodir
2015/04/13 16:04:53
requester tag is not the user, but a tool. Maybe w
sheyang
2015/04/14 01:30:22
Use 'user_agent' instead.
|
| + }) |
| + wait = 1 |
| + try_count = 3 |
| + while try_count > 0: |
| + try: |
| + try_count -= 1 |
| + response = req.execute() |
| + if response.get('error'): |
| + msg = 'Error in response. Reason: %s. Message: %s.' % ( |
| + response['error'].get('reason', ''), |
| + response['error'].get('message', '')) |
| + raise BuildbucketResponseException(msg) |
| + break |
| + except apiclient.errors.HttpError as ex: |
| + status = ex.resp.status if ex.resp else None |
| + if status >= 500: |
| + logging.debug('Transient errors when triggering tryjobs. ' |
| + 'Will retry in %d seconds.', wait) |
| + time.sleep(wait) |
| + wait *= 2 |
| + if try_count <= 0: |
| + raise |
| + else: |
| + raise |
| + print ' %s: %s' % (builder, tests) |
| + |
| + |
| def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards): |
| """Return the corresponding git ref if |base_url| together with |glob_spec| |
| matches the full |url|. |
| @@ -282,6 +384,10 @@ def print_stats(similarity, find_copies, args): |
| stdout=stdout, env=env) |
| +class BuildbucketResponseException(Exception): |
| + pass |
| + |
| + |
| class Settings(object): |
| def __init__(self): |
| self.default_server = None |
| @@ -2736,6 +2842,7 @@ def CMDtry(parser, args): |
| group.add_option( |
| "-n", "--name", help="Try job name; default to current branch name") |
| parser.add_option_group(group) |
| + oauth2.add_oauth2_options(parser) |
| options, args = parser.parse_args(args) |
| if args: |
| @@ -2831,22 +2938,26 @@ def CMDtry(parser, args): |
| 'upload fail?\ngit-cl try always uses latest patchset from rietveld. ' |
| 'Continuing using\npatchset %s.\n' % patchset) |
| try: |
| - cl.RpcServer().trigger_distributed_try_jobs( |
| - cl.GetIssue(), patchset, options.name, options.clobber, |
| - options.revision, masters) |
| - except urllib2.HTTPError, e: |
| - if e.code == 404: |
| - print('404 from rietveld; ' |
| - 'did you mean to use "git try" instead of "git cl try"?') |
| + creds = oauth2.get_oauth2_creds( |
| + options, |
| + urlparse.urlparse(cl.GetRietveldServer()).hostname) |
| + if not creds: |
| + print 'Failed to fetch credentials. Aborting...' |
| return 1 |
| - print('Tried jobs on:') |
| - |
| - for (master, builders) in masters.iteritems(): |
| - if master: |
| - print 'Master: %s' % master |
| - length = max(len(builder) for builder in builders) |
| - for builder in sorted(builders): |
| - print ' %*s: %s' % (length, builder, ','.join(builders[builder])) |
| + trigger_distributed_try_jobs(creds, cl, options, masters, 'git cl try') |
| + except apiclient.errors.HttpError as ex: |
| + status = ex.resp.status if ex.resp else None |
| + if status == httplib.FORBIDDEN: |
| + print 'ERROR: Access denied. Please verify you have tryjob access.' |
| + else: |
| + print 'ERROR: Tryjob request failed: %s.' % ex |
| + return 1 |
| + except BuildbucketResponseException as ex: |
| + print ex |
| + return 1 |
| + except Exception as e: |
| + print 'Unexpected error when trying to trigger tryjobs: %s.' % e |
| + return 1 |
| return 0 |