Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 6 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
| 7 | 7 |
| 8 """A git-command for integrating reviews on Rietveld.""" | 8 """A git-command for integrating reviews on Rietveld.""" |
| 9 | 9 |
| 10 from distutils.version import LooseVersion | 10 from distutils.version import LooseVersion |
| 11 import base64 | 11 import base64 |
| 12 import glob | 12 import glob |
| 13 import httplib | |
| 13 import json | 14 import json |
| 14 import logging | 15 import logging |
| 15 import optparse | 16 import optparse |
| 16 import os | 17 import os |
| 17 import Queue | 18 import Queue |
| 18 import re | 19 import re |
| 19 import stat | 20 import stat |
| 20 import sys | 21 import sys |
| 21 import tempfile | 22 import tempfile |
| 22 import textwrap | 23 import textwrap |
| 23 import threading | 24 import threading |
| 25 import time | |
| 24 import urllib2 | 26 import urllib2 |
| 25 import urlparse | 27 import urlparse |
| 26 import webbrowser | 28 import webbrowser |
| 27 import zlib | 29 import zlib |
| 28 | 30 |
| 29 try: | 31 try: |
| 30 import readline # pylint: disable=F0401,W0611 | 32 import readline # pylint: disable=F0401,W0611 |
| 31 except ImportError: | 33 except ImportError: |
| 32 pass | 34 pass |
| 33 | 35 |
| 34 | 36 |
| 35 from third_party import colorama | 37 from third_party import colorama |
| 38 from third_party import httplib2 | |
| 36 from third_party import upload | 39 from third_party import upload |
| 40 from third_party.google_api_python_client import apiclient | |
| 37 import breakpad # pylint: disable=W0611 | 41 import breakpad # pylint: disable=W0611 |
| 38 import clang_format | 42 import clang_format |
| 39 import dart_format | 43 import dart_format |
| 40 import fix_encoding | 44 import fix_encoding |
| 41 import gclient_utils | 45 import gclient_utils |
| 46 import git_cl_oauth2 | |
| 42 import git_common | 47 import git_common |
| 43 import owners | 48 import owners |
| 44 import owners_finder | 49 import owners_finder |
| 45 import presubmit_support | 50 import presubmit_support |
| 46 import rietveld | 51 import rietveld |
| 47 import scm | 52 import scm |
| 48 import subcommand | 53 import subcommand |
| 49 import subprocess2 | 54 import subprocess2 |
| 50 import watchlists | 55 import watchlists |
| 51 | 56 |
| 52 __version__ = '1.0' | 57 __version__ = '1.0' |
| 53 | 58 |
| 54 DEFAULT_SERVER = 'https://codereview.appspot.com' | 59 DEFAULT_SERVER = 'https://codereview.appspot.com' |
| 55 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' | 60 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' |
| 56 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' | 61 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' |
| 57 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit' | 62 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit' |
| 58 CHANGE_ID = 'Change-Id:' | 63 CHANGE_ID = 'Change-Id:' |
| 59 REFS_THAT_ALIAS_TO_OTHER_REFS = { | 64 REFS_THAT_ALIAS_TO_OTHER_REFS = { |
| 60 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master', | 65 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master', |
| 61 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master', | 66 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master', |
| 62 } | 67 } |
| 63 | 68 |
| 69 # Buildbucket-related constants | |
| 70 DISCOVERY_URL = ( | |
| 71 'https://cr-buildbucket.appspot.com/_ah/api/discovery/v1/apis/' | |
| 72 '{api}/{apiVersion}/rest') | |
| 73 DEFAULT_SCOPES = 'email' | |
| 74 BUILDSET_STR = 'patch/rietveld/{hostname}/{issue}/{patch}' | |
| 75 | |
| 64 # Valid extensions for files we want to lint. | 76 # Valid extensions for files we want to lint. |
| 65 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" | 77 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" |
| 66 DEFAULT_LINT_IGNORE_REGEX = r"$^" | 78 DEFAULT_LINT_IGNORE_REGEX = r"$^" |
| 67 | 79 |
| 68 # Shortcut since it quickly becomes redundant. | 80 # Shortcut since it quickly becomes redundant. |
| 69 Fore = colorama.Fore | 81 Fore = colorama.Fore |
| 70 | 82 |
| 71 # Initialized in main() | 83 # Initialized in main() |
| 72 settings = None | 84 settings = None |
| 73 | 85 |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 208 if dirty: | 220 if dirty: |
| 209 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd | 221 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd |
| 210 print 'Uncommitted files: (git diff-index --name-status HEAD)' | 222 print 'Uncommitted files: (git diff-index --name-status HEAD)' |
| 211 print dirty[:4096] | 223 print dirty[:4096] |
| 212 if len(dirty) > 4096: | 224 if len(dirty) > 4096: |
| 213 print '... (run "git diff-index --name-status HEAD" to see full output).' | 225 print '... (run "git diff-index --name-status HEAD" to see full output).' |
| 214 return True | 226 return True |
| 215 return False | 227 return False |
| 216 | 228 |
| 217 | 229 |
| 230 def _prefix_master(master): | |
| 231 prefix = 'master.' | |
| 232 if master.startswith(prefix): | |
| 233 return master | |
| 234 else: | |
| 235 return '%s%s' % (prefix, master) | |
| 236 | |
| 237 | |
| 238 def _get_buildbucket(credentials): | |
| 239 http = httplib2.Http() | |
| 240 http = credentials.authorize(http) | |
| 241 return apiclient.discovery.build( | |
| 242 'buildbucket', 'v1', | |
| 243 http=http, | |
| 244 discoveryServiceUrl=DISCOVERY_URL, | |
| 245 ) | |
| 246 | |
| 247 | |
| 248 def trigger_distributed_try_jobs( | |
| 249 credentials, changelist, options, masters, category): | |
| 250 buildbucket = _get_buildbucket(credentials) | |
| 251 creds_props = json.loads(credentials.to_json()) | |
| 252 requester = creds_props['id_token']['email'] | |
|
nodir
2015/03/10 22:39:40
I think you can get requester like this: credentia
sheyang
2015/03/13 20:58:27
No it doesn't work.
| |
| 253 issue_props = changelist.GetIssueProperties() | |
| 254 rietveld_host = urlparse.urlparse(changelist.GetRietveldServer()).hostname | |
| 255 issue = changelist.GetIssue() | |
| 256 patchset = changelist.GetMostRecentPatchset() | |
| 257 buildset = BUILDSET_STR.format( | |
| 258 hostname=rietveld_host, | |
| 259 issue=str(issue), | |
| 260 patch=str(patchset)) | |
| 261 print 'Tried jobs on:' | |
| 262 for (master, builders_and_tests) in masters.iteritems(): | |
| 263 print 'Master: %s' % master | |
| 264 bucket = _prefix_master(master) | |
| 265 for builder, tests in builders_and_tests.iteritems(): | |
| 266 req = buildbucket.put(body={ | |
| 267 'bucket': bucket, | |
| 268 'parameters_json': json.dumps({ | |
| 269 'builder_name': builder, | |
| 270 'changes':[ | |
| 271 {'author': {'email': issue_props['owner_email']}}, | |
| 272 ], | |
| 273 'properties': { | |
| 274 'category': category, | |
| 275 'clobber': options.clobber, | |
| 276 'issue': issue, | |
| 277 'master': master, | |
| 278 'patch_project': issue_props['project'], | |
| 279 'patch_storage': 'rietveld', | |
| 280 'patchset': patchset, | |
| 281 'reason': options.name, | |
| 282 'requester': requester, | |
| 283 'revision': options.revision, | |
| 284 'rietveld': changelist.GetRietveldServer(), | |
| 285 'testfilter': tests, | |
| 286 }, | |
| 287 }), | |
| 288 'tags': ['buildset:%s' % buildset, | |
| 289 'master:%s' % master, | |
| 290 'builder:%s' % builder, | |
| 291 'requester:%s' % requester] | |
| 292 }) | |
| 293 wait = 1 | |
| 294 try_count = 3 | |
| 295 while try_count > 0: | |
| 296 try: | |
| 297 response = req.execute() | |
|
nodir
2015/03/10 22:39:40
do try_count -=1 after this
sheyang
2015/03/13 20:58:27
You mean before this right? Otherwise we may never
nodir
2015/03/18 23:49:15
Right
| |
| 298 if response.get('error'): | |
| 299 msg = 'Error in response. Reason: %s. Message: %s.' % ( | |
| 300 response['error'].get('reason', ''), | |
| 301 response['error'].get('message', '')) | |
| 302 raise BuildbucketResponseException(msg) | |
| 303 try_count = 0 | |
|
nodir
2015/03/10 22:39:40
If this intends to break the loop, please do `brea
sheyang
2015/03/13 20:58:27
Done.
| |
| 304 except apiclient.errors.HttpError as ex: | |
| 305 status = ex.resp.status if ex.resp else None | |
| 306 if status >= 500: | |
| 307 logging.debug('Transient errors when triggering tryjobs. ' | |
| 308 'Will retry in %d seconds.', wait) | |
| 309 time.sleep(wait) | |
| 310 wait *= 2 | |
| 311 try_count -= 1 | |
|
nodir
2015/03/10 22:39:40
not needed if try_count -=1 right after trying
sheyang
2015/03/13 20:58:27
Done.
| |
| 312 if try_count <= 0: | |
| 313 raise | |
| 314 else: | |
| 315 raise | |
| 316 print ' %s: %s' % (builder, tests) | |
| 317 | |
| 318 | |
| 218 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards): | 319 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards): |
| 219 """Return the corresponding git ref if |base_url| together with |glob_spec| | 320 """Return the corresponding git ref if |base_url| together with |glob_spec| |
| 220 matches the full |url|. | 321 matches the full |url|. |
| 221 | 322 |
| 222 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below). | 323 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below). |
| 223 """ | 324 """ |
| 224 fetch_suburl, as_ref = glob_spec.split(':') | 325 fetch_suburl, as_ref = glob_spec.split(':') |
| 225 if allow_wildcards: | 326 if allow_wildcards: |
| 226 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl) | 327 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl) |
| 227 if glob_match: | 328 if glob_match: |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 275 try: | 376 try: |
| 276 stdout = sys.stdout.fileno() | 377 stdout = sys.stdout.fileno() |
| 277 except AttributeError: | 378 except AttributeError: |
| 278 stdout = None | 379 stdout = None |
| 279 return subprocess2.call( | 380 return subprocess2.call( |
| 280 ['git', | 381 ['git', |
| 281 'diff', '--no-ext-diff', '--stat'] + similarity_options + args, | 382 'diff', '--no-ext-diff', '--stat'] + similarity_options + args, |
| 282 stdout=stdout, env=env) | 383 stdout=stdout, env=env) |
| 283 | 384 |
| 284 | 385 |
| 386 class BuildbucketResponseException(Exception): | |
| 387 pass | |
| 388 | |
| 389 | |
| 285 class Settings(object): | 390 class Settings(object): |
| 286 def __init__(self): | 391 def __init__(self): |
| 287 self.default_server = None | 392 self.default_server = None |
| 288 self.cc = None | 393 self.cc = None |
| 289 self.root = None | 394 self.root = None |
| 290 self.is_git_svn = None | 395 self.is_git_svn = None |
| 291 self.svn_branch = None | 396 self.svn_branch = None |
| 292 self.tree_status_url = None | 397 self.tree_status_url = None |
| 293 self.viewvc_url = None | 398 self.viewvc_url = None |
| 294 self.updated = False | 399 self.updated = False |
| (...skipping 2401 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2696 "-c", "--clobber", action="store_true", default=False, | 2801 "-c", "--clobber", action="store_true", default=False, |
| 2697 help="Force a clobber before building; e.g. don't do an " | 2802 help="Force a clobber before building; e.g. don't do an " |
| 2698 "incremental build") | 2803 "incremental build") |
| 2699 group.add_option( | 2804 group.add_option( |
| 2700 "--project", | 2805 "--project", |
| 2701 help="Override which project to use. Projects are defined " | 2806 help="Override which project to use. Projects are defined " |
| 2702 "server-side to define what default bot set to use") | 2807 "server-side to define what default bot set to use") |
| 2703 group.add_option( | 2808 group.add_option( |
| 2704 "-n", "--name", help="Try job name; default to current branch name") | 2809 "-n", "--name", help="Try job name; default to current branch name") |
| 2705 parser.add_option_group(group) | 2810 parser.add_option_group(group) |
| 2811 git_cl_oauth2.add_oauth2_options(parser) | |
| 2706 options, args = parser.parse_args(args) | 2812 options, args = parser.parse_args(args) |
| 2707 | 2813 |
| 2708 if args: | 2814 if args: |
| 2709 parser.error('Unknown arguments: %s' % args) | 2815 parser.error('Unknown arguments: %s' % args) |
| 2710 | 2816 |
| 2711 cl = Changelist() | 2817 cl = Changelist() |
| 2712 if not cl.GetIssue(): | 2818 if not cl.GetIssue(): |
| 2713 parser.error('Need to upload first') | 2819 parser.error('Need to upload first') |
| 2714 | 2820 |
| 2715 props = cl.GetIssueProperties() | 2821 props = cl.GetIssueProperties() |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2791 'Bot list: %s' % builders) | 2897 'Bot list: %s' % builders) |
| 2792 return 1 | 2898 return 1 |
| 2793 | 2899 |
| 2794 patchset = cl.GetMostRecentPatchset() | 2900 patchset = cl.GetMostRecentPatchset() |
| 2795 if patchset and patchset != cl.GetPatchset(): | 2901 if patchset and patchset != cl.GetPatchset(): |
| 2796 print( | 2902 print( |
| 2797 '\nWARNING Mismatch between local config and server. Did a previous ' | 2903 '\nWARNING Mismatch between local config and server. Did a previous ' |
| 2798 'upload fail?\ngit-cl try always uses latest patchset from rietveld. ' | 2904 'upload fail?\ngit-cl try always uses latest patchset from rietveld. ' |
| 2799 'Continuing using\npatchset %s.\n' % patchset) | 2905 'Continuing using\npatchset %s.\n' % patchset) |
| 2800 try: | 2906 try: |
| 2801 cl.RpcServer().trigger_distributed_try_jobs( | 2907 creds = git_cl_oauth2.get_oauth2_creds( |
| 2802 cl.GetIssue(), patchset, options.name, options.clobber, | 2908 options, |
| 2803 options.revision, masters) | 2909 urlparse.urlparse(cl.GetRietveldServer()).hostname) |
| 2804 except urllib2.HTTPError, e: | 2910 trigger_distributed_try_jobs(creds, cl, options, masters, 'git cl try') |
| 2805 if e.code == 404: | 2911 except apiclient.errors.HttpError as ex: |
| 2806 print('404 from rietveld; ' | 2912 status = ex.resp.status if ex.resp else None |
| 2807 'did you mean to use "git try" instead of "git cl try"?') | 2913 if status == httplib.FORBIDDEN: |
| 2808 return 1 | 2914 print 'ERROR: Access denied. Please verify you have tryjob access.' |
| 2809 print('Tried jobs on:') | 2915 else: |
| 2810 | 2916 print 'ERROR: Tryjob request failed: %s.' % ex |
| 2811 for (master, builders) in masters.iteritems(): | 2917 return 1 |
| 2812 if master: | 2918 except BuildbucketResponseException as ex: |
| 2813 print 'Master: %s' % master | 2919 print ex |
| 2814 length = max(len(builder) for builder in builders) | 2920 return 1 |
| 2815 for builder in sorted(builders): | 2921 except Exception as e: |
| 2816 print ' %*s: %s' % (length, builder, ','.join(builders[builder])) | 2922 print 'Unexcpected error when trying to trigger tryjobs: %s.' % e |
| 2923 return 1 | |
| 2817 return 0 | 2924 return 0 |
| 2818 | 2925 |
| 2819 | 2926 |
| 2820 @subcommand.usage('[new upstream branch]') | 2927 @subcommand.usage('[new upstream branch]') |
| 2821 def CMDupstream(parser, args): | 2928 def CMDupstream(parser, args): |
| 2822 """Prints or sets the name of the upstream branch, if any.""" | 2929 """Prints or sets the name of the upstream branch, if any.""" |
| 2823 _, args = parser.parse_args(args) | 2930 _, args = parser.parse_args(args) |
| 2824 if len(args) > 1: | 2931 if len(args) > 1: |
| 2825 parser.error('Unrecognized args: %s' % ' '.join(args)) | 2932 parser.error('Unrecognized args: %s' % ' '.join(args)) |
| 2826 | 2933 |
| (...skipping 291 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3118 if __name__ == '__main__': | 3225 if __name__ == '__main__': |
| 3119 # These affect sys.stdout so do it outside of main() to simplify mocks in | 3226 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 3120 # unit testing. | 3227 # unit testing. |
| 3121 fix_encoding.fix_encoding() | 3228 fix_encoding.fix_encoding() |
| 3122 colorama.init() | 3229 colorama.init() |
| 3123 try: | 3230 try: |
| 3124 sys.exit(main(sys.argv[1:])) | 3231 sys.exit(main(sys.argv[1:])) |
| 3125 except KeyboardInterrupt: | 3232 except KeyboardInterrupt: |
| 3126 sys.stderr.write('interrupted\n') | 3233 sys.stderr.write('interrupted\n') |
| 3127 sys.exit(1) | 3234 sys.exit(1) |
| OLD | NEW |