| 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 and Gerrit.""" | 8 """A git-command for integrating reviews on Rietveld and Gerrit.""" |
| 9 | 9 |
| 10 from __future__ import print_function | 10 from __future__ import print_function |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 69 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit' | 69 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit' |
| 70 REFS_THAT_ALIAS_TO_OTHER_REFS = { | 70 REFS_THAT_ALIAS_TO_OTHER_REFS = { |
| 71 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master', | 71 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master', |
| 72 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master', | 72 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master', |
| 73 } | 73 } |
| 74 | 74 |
| 75 # Valid extensions for files we want to lint. | 75 # Valid extensions for files we want to lint. |
| 76 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" | 76 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" |
| 77 DEFAULT_LINT_IGNORE_REGEX = r"$^" | 77 DEFAULT_LINT_IGNORE_REGEX = r"$^" |
| 78 | 78 |
| 79 # Buildbucket master name prefix. |
| 80 MASTER_PREFIX = 'master.' |
| 81 |
| 79 # Shortcut since it quickly becomes redundant. | 82 # Shortcut since it quickly becomes redundant. |
| 80 Fore = colorama.Fore | 83 Fore = colorama.Fore |
| 81 | 84 |
| 82 # Initialized in main() | 85 # Initialized in main() |
| 83 settings = None | 86 settings = None |
| 84 | 87 |
| 85 | 88 |
| 86 def DieWithError(message): | 89 def DieWithError(message): |
| 87 print(message, file=sys.stderr) | 90 print(message, file=sys.stderr) |
| 88 sys.exit(1) | 91 sys.exit(1) |
| (...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 268 | 271 |
| 269 | 272 |
| 270 def _prefix_master(master): | 273 def _prefix_master(master): |
| 271 """Convert user-specified master name to full master name. | 274 """Convert user-specified master name to full master name. |
| 272 | 275 |
| 273 Buildbucket uses full master name(master.tryserver.chromium.linux) as bucket | 276 Buildbucket uses full master name(master.tryserver.chromium.linux) as bucket |
| 274 name, while the developers always use shortened master name | 277 name, while the developers always use shortened master name |
| 275 (tryserver.chromium.linux) by stripping off the prefix 'master.'. This | 278 (tryserver.chromium.linux) by stripping off the prefix 'master.'. This |
| 276 function does the conversion for buildbucket migration. | 279 function does the conversion for buildbucket migration. |
| 277 """ | 280 """ |
| 278 prefix = 'master.' | 281 if master.startswith(MASTER_PREFIX): |
| 279 if master.startswith(prefix): | |
| 280 return master | 282 return master |
| 281 return '%s%s' % (prefix, master) | 283 return '%s%s' % (MASTER_PREFIX, master) |
| 284 |
| 285 |
| 286 def _unprefix_master(bucket): |
| 287 """Convert bucket name to shortened master name. |
| 288 |
| 289 Buildbucket uses full master name(master.tryserver.chromium.linux) as bucket |
| 290 name, while the developers always use shortened master name |
| 291 (tryserver.chromium.linux) by stripping off the prefix 'master.'. This |
| 292 function does the conversion for buildbucket migration. |
| 293 """ |
| 294 if bucket.startswith(MASTER_PREFIX): |
| 295 return bucket[len(MASTER_PREFIX):] |
| 296 return bucket |
| 282 | 297 |
| 283 | 298 |
| 284 def _buildbucket_retry(operation_name, http, *args, **kwargs): | 299 def _buildbucket_retry(operation_name, http, *args, **kwargs): |
| 285 """Retries requests to buildbucket service and returns parsed json content.""" | 300 """Retries requests to buildbucket service and returns parsed json content.""" |
| 286 try_count = 0 | 301 try_count = 0 |
| 287 while True: | 302 while True: |
| 288 response, content = http.request(*args, **kwargs) | 303 response, content = http.request(*args, **kwargs) |
| 289 try: | 304 try: |
| 290 content_json = json.loads(content) | 305 content_json = json.loads(content) |
| 291 except ValueError: | 306 except ValueError: |
| (...skipping 19 matching lines...) Expand all Loading... |
| 311 if response.status < 500 or try_count >= 2: | 326 if response.status < 500 or try_count >= 2: |
| 312 raise httplib2.HttpLib2Error(content) | 327 raise httplib2.HttpLib2Error(content) |
| 313 | 328 |
| 314 # status >= 500 means transient failures. | 329 # status >= 500 means transient failures. |
| 315 logging.debug('Transient errors when %s. Will retry.', operation_name) | 330 logging.debug('Transient errors when %s. Will retry.', operation_name) |
| 316 time.sleep(0.5 + 1.5*try_count) | 331 time.sleep(0.5 + 1.5*try_count) |
| 317 try_count += 1 | 332 try_count += 1 |
| 318 assert False, 'unreachable' | 333 assert False, 'unreachable' |
| 319 | 334 |
| 320 | 335 |
| 321 def _trigger_try_jobs(auth_config, changelist, masters, options, | 336 def _trigger_try_jobs(auth_config, changelist, buckets, options, |
| 322 category='git_cl_try', patchset=None): | 337 category='git_cl_try', patchset=None): |
| 323 assert changelist.GetIssue(), 'CL must be uploaded first' | 338 assert changelist.GetIssue(), 'CL must be uploaded first' |
| 324 codereview_url = changelist.GetCodereviewServer() | 339 codereview_url = changelist.GetCodereviewServer() |
| 325 assert codereview_url, 'CL must be uploaded first' | 340 assert codereview_url, 'CL must be uploaded first' |
| 326 patchset = patchset or changelist.GetMostRecentPatchset() | 341 patchset = patchset or changelist.GetMostRecentPatchset() |
| 327 assert patchset, 'CL must be uploaded first' | 342 assert patchset, 'CL must be uploaded first' |
| 328 | 343 |
| 329 codereview_host = urlparse.urlparse(codereview_url).hostname | 344 codereview_host = urlparse.urlparse(codereview_url).hostname |
| 330 authenticator = auth.get_authenticator_for_host(codereview_host, auth_config) | 345 authenticator = auth.get_authenticator_for_host(codereview_host, auth_config) |
| 331 http = authenticator.authorize(httplib2.Http()) | 346 http = authenticator.authorize(httplib2.Http()) |
| (...skipping 11 matching lines...) Expand all Loading... |
| 343 buildset = 'patch/{codereview}/{hostname}/{issue}/{patch}'.format( | 358 buildset = 'patch/{codereview}/{hostname}/{issue}/{patch}'.format( |
| 344 codereview='gerrit' if changelist.IsGerrit() else 'rietveld', | 359 codereview='gerrit' if changelist.IsGerrit() else 'rietveld', |
| 345 hostname=codereview_host, | 360 hostname=codereview_host, |
| 346 issue=changelist.GetIssue(), | 361 issue=changelist.GetIssue(), |
| 347 patch=patchset) | 362 patch=patchset) |
| 348 extra_properties = _get_properties_from_options(options) | 363 extra_properties = _get_properties_from_options(options) |
| 349 | 364 |
| 350 batch_req_body = {'builds': []} | 365 batch_req_body = {'builds': []} |
| 351 print_text = [] | 366 print_text = [] |
| 352 print_text.append('Tried jobs on:') | 367 print_text.append('Tried jobs on:') |
| 353 for master, builders_and_tests in sorted(masters.iteritems()): | 368 for bucket, builders_and_tests in sorted(buckets.iteritems()): |
| 354 print_text.append('Master: %s' % master) | 369 print_text.append('Bucket: %s' % bucket) |
| 355 bucket = _prefix_master(master) | 370 master = None |
| 371 if bucket.startswith(MASTER_PREFIX): |
| 372 master = _unprefix_master(bucket) |
| 356 for builder, tests in sorted(builders_and_tests.iteritems()): | 373 for builder, tests in sorted(builders_and_tests.iteritems()): |
| 357 print_text.append(' %s: %s' % (builder, tests)) | 374 print_text.append(' %s: %s' % (builder, tests)) |
| 358 parameters = { | 375 parameters = { |
| 359 'builder_name': builder, | 376 'builder_name': builder, |
| 360 'changes': [{ | 377 'changes': [{ |
| 361 'author': {'email': owner_email}, | 378 'author': {'email': owner_email}, |
| 362 'revision': options.revision, | 379 'revision': options.revision, |
| 363 }], | 380 }], |
| 364 'properties': { | 381 'properties': { |
| 365 'category': category, | 382 'category': category, |
| 366 'issue': changelist.GetIssue(), | 383 'issue': changelist.GetIssue(), |
| 367 'master': master, | |
| 368 'patch_project': project, | 384 'patch_project': project, |
| 369 'patch_storage': 'rietveld', | 385 'patch_storage': 'rietveld', |
| 370 'patchset': patchset, | 386 'patchset': patchset, |
| 371 'reason': options.name, | 387 'reason': options.name, |
| 372 'rietveld': codereview_url, | 388 'rietveld': codereview_url, |
| 373 }, | 389 }, |
| 374 } | 390 } |
| 375 if 'presubmit' in builder.lower(): | 391 if 'presubmit' in builder.lower(): |
| 376 parameters['properties']['dry_run'] = 'true' | 392 parameters['properties']['dry_run'] = 'true' |
| 377 if tests: | 393 if tests: |
| 378 parameters['properties']['testfilter'] = tests | 394 parameters['properties']['testfilter'] = tests |
| 379 if extra_properties: | 395 if extra_properties: |
| 380 parameters['properties'].update(extra_properties) | 396 parameters['properties'].update(extra_properties) |
| 381 if options.clobber: | 397 if options.clobber: |
| 382 parameters['properties']['clobber'] = True | 398 parameters['properties']['clobber'] = True |
| 399 |
| 400 tags = [ |
| 401 'builder:%s' % builder, |
| 402 'buildset:%s' % buildset, |
| 403 'user_agent:git_cl_try', |
| 404 ] |
| 405 if master: |
| 406 parameters['properties']['master'] = master |
| 407 tags.append('master:%s' % master) |
| 408 |
| 383 batch_req_body['builds'].append( | 409 batch_req_body['builds'].append( |
| 384 { | 410 { |
| 385 'bucket': bucket, | 411 'bucket': bucket, |
| 386 'parameters_json': json.dumps(parameters), | 412 'parameters_json': json.dumps(parameters), |
| 387 'client_operation_id': str(uuid.uuid4()), | 413 'client_operation_id': str(uuid.uuid4()), |
| 388 'tags': ['builder:%s' % builder, | 414 'tags': tags, |
| 389 'buildset:%s' % buildset, | |
| 390 'master:%s' % master, | |
| 391 'user_agent:git_cl_try'] | |
| 392 } | 415 } |
| 393 ) | 416 ) |
| 394 | 417 |
| 395 _buildbucket_retry( | 418 _buildbucket_retry( |
| 396 'triggering try jobs', | 419 'triggering try jobs', |
| 397 http, | 420 http, |
| 398 buildbucket_put_url, | 421 buildbucket_put_url, |
| 399 'PUT', | 422 'PUT', |
| 400 body=json.dumps(batch_req_body), | 423 body=json.dumps(batch_req_body), |
| 401 headers={'Content-Type': 'application/json'} | 424 headers={'Content-Type': 'application/json'} |
| (...skipping 4304 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 4706 """ | 4729 """ |
| 4707 group = optparse.OptionGroup(parser, 'Try job options') | 4730 group = optparse.OptionGroup(parser, 'Try job options') |
| 4708 group.add_option( | 4731 group.add_option( |
| 4709 '-b', '--bot', action='append', | 4732 '-b', '--bot', action='append', |
| 4710 help=('IMPORTANT: specify ONE builder per --bot flag. Use it multiple ' | 4733 help=('IMPORTANT: specify ONE builder per --bot flag. Use it multiple ' |
| 4711 'times to specify multiple builders. ex: ' | 4734 'times to specify multiple builders. ex: ' |
| 4712 '"-b win_rel -b win_layout". See ' | 4735 '"-b win_rel -b win_layout". See ' |
| 4713 'the try server waterfall for the builders name and the tests ' | 4736 'the try server waterfall for the builders name and the tests ' |
| 4714 'available.')) | 4737 'available.')) |
| 4715 group.add_option( | 4738 group.add_option( |
| 4739 '-B', '--bucket', default='', |
| 4740 help=('Buildbucket bucket to send the try requests.')) |
| 4741 group.add_option( |
| 4716 '-m', '--master', default='', | 4742 '-m', '--master', default='', |
| 4717 help=('Specify a try master where to run the tries.')) | 4743 help=('Specify a try master where to run the tries.')) |
| 4718 # TODO(tandrii,nodir): add -B --bucket flag. | |
| 4719 group.add_option( | 4744 group.add_option( |
| 4720 '-r', '--revision', | 4745 '-r', '--revision', |
| 4721 help='Revision to use for the try job; default: the revision will ' | 4746 help='Revision to use for the try job; default: the revision will ' |
| 4722 'be determined by the try recipe that builder runs, which usually ' | 4747 'be determined by the try recipe that builder runs, which usually ' |
| 4723 'defaults to HEAD of origin/master') | 4748 'defaults to HEAD of origin/master') |
| 4724 group.add_option( | 4749 group.add_option( |
| 4725 '-c', '--clobber', action='store_true', default=False, | 4750 '-c', '--clobber', action='store_true', default=False, |
| 4726 help='Force a clobber before building; that is don\'t do an ' | 4751 help='Force a clobber before building; that is don\'t do an ' |
| 4727 'incremental build') | 4752 'incremental build') |
| 4728 group.add_option( | 4753 group.add_option( |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 4766 'If your project has Commit Queue, dry run is a workaround:\n' | 4791 'If your project has Commit Queue, dry run is a workaround:\n' |
| 4767 ' git cl set-commit --dry-run') | 4792 ' git cl set-commit --dry-run') |
| 4768 | 4793 |
| 4769 error_message = cl.CannotTriggerTryJobReason() | 4794 error_message = cl.CannotTriggerTryJobReason() |
| 4770 if error_message: | 4795 if error_message: |
| 4771 parser.error('Can\'t trigger try jobs: %s') | 4796 parser.error('Can\'t trigger try jobs: %s') |
| 4772 | 4797 |
| 4773 if not options.name: | 4798 if not options.name: |
| 4774 options.name = cl.GetBranch() | 4799 options.name = cl.GetBranch() |
| 4775 | 4800 |
| 4776 if options.bot and not options.master: | 4801 if options.bucket and options.master: |
| 4802 parser.error('Only one of --bucket and --master may be used.') |
| 4803 |
| 4804 if options.bot and not options.master and not options.bucket: |
| 4777 options.master, err_msg = GetBuilderMaster(options.bot) | 4805 options.master, err_msg = GetBuilderMaster(options.bot) |
| 4778 if err_msg: | 4806 if err_msg: |
| 4779 parser.error('Tryserver master cannot be found because: %s\n' | 4807 parser.error('Tryserver master cannot be found because: %s\n' |
| 4780 'Please manually specify the tryserver master' | 4808 'Please manually specify the tryserver master' |
| 4781 ', e.g. "-m tryserver.chromium.linux".' % err_msg) | 4809 ', e.g. "-m tryserver.chromium.linux".' % err_msg) |
| 4782 | 4810 |
| 4783 def GetMasterMap(): | 4811 def GetMasterMap(): |
| 4784 # Process --bot. | 4812 # Process --bot. |
| 4785 if not options.bot: | 4813 if not options.bot: |
| 4786 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None) | 4814 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None) |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 4823 parser.error('Specify one bot per --bot flag') | 4851 parser.error('Specify one bot per --bot flag') |
| 4824 else: | 4852 else: |
| 4825 builders_and_tests.setdefault(bot, []) | 4853 builders_and_tests.setdefault(bot, []) |
| 4826 | 4854 |
| 4827 for bot, tests in new_style: | 4855 for bot, tests in new_style: |
| 4828 builders_and_tests.setdefault(bot, []).extend(tests) | 4856 builders_and_tests.setdefault(bot, []).extend(tests) |
| 4829 | 4857 |
| 4830 # Return a master map with one master to be backwards compatible. The | 4858 # Return a master map with one master to be backwards compatible. The |
| 4831 # master name defaults to an empty string, which will cause the master | 4859 # master name defaults to an empty string, which will cause the master |
| 4832 # not to be set on rietveld (deprecated). | 4860 # not to be set on rietveld (deprecated). |
| 4833 return {options.master: builders_and_tests} | 4861 bucket = '' |
| 4862 if options.master: |
| 4863 # Add the "master." prefix to the master name to obtain the bucket name. |
| 4864 bucket = _prefix_master(options.master) |
| 4865 return {bucket: builders_and_tests} |
| 4834 | 4866 |
| 4835 masters = GetMasterMap() | 4867 if options.bucket: |
| 4836 if not masters: | 4868 buckets = {options.bucket: {b: [] for b in options.bot}} |
| 4837 # Default to triggering Dry Run (see http://crbug.com/625697). | 4869 else: |
| 4838 if options.verbose: | 4870 buckets = GetMasterMap() |
| 4839 print('git cl try with no bots now defaults to CQ Dry Run.') | 4871 if not buckets: |
| 4840 try: | 4872 # Default to triggering Dry Run (see http://crbug.com/625697). |
| 4841 cl.SetCQState(_CQState.DRY_RUN) | 4873 if options.verbose: |
| 4842 print('scheduled CQ Dry Run on %s' % cl.GetIssueURL()) | 4874 print('git cl try with no bots now defaults to CQ Dry Run.') |
| 4843 return 0 | 4875 try: |
| 4844 except KeyboardInterrupt: | 4876 cl.SetCQState(_CQState.DRY_RUN) |
| 4845 raise | 4877 print('scheduled CQ Dry Run on %s' % cl.GetIssueURL()) |
| 4846 except: | 4878 return 0 |
| 4847 print('WARNING: failed to trigger CQ Dry Run.\n' | 4879 except KeyboardInterrupt: |
| 4848 'Either:\n' | 4880 raise |
| 4849 ' * your project has no CQ\n' | 4881 except: |
| 4850 ' * you don\'t have permission to trigger Dry Run\n' | 4882 print('WARNING: failed to trigger CQ Dry Run.\n' |
| 4851 ' * bug in this code (see stack trace below).\n' | 4883 'Either:\n' |
| 4852 'Consider specifying which bots to trigger manually ' | 4884 ' * your project has no CQ\n' |
| 4853 'or asking your project owners for permissions ' | 4885 ' * you don\'t have permission to trigger Dry Run\n' |
| 4854 'or contacting Chrome Infrastructure team at ' | 4886 ' * bug in this code (see stack trace below).\n' |
| 4855 'https://www.chromium.org/infra\n\n') | 4887 'Consider specifying which bots to trigger manually ' |
| 4856 # Still raise exception so that stack trace is printed. | 4888 'or asking your project owners for permissions ' |
| 4857 raise | 4889 'or contacting Chrome Infrastructure team at ' |
| 4890 'https://www.chromium.org/infra\n\n') |
| 4891 # Still raise exception so that stack trace is printed. |
| 4892 raise |
| 4858 | 4893 |
| 4859 for builders in masters.itervalues(): | 4894 for builders in buckets.itervalues(): |
| 4860 if any('triggered' in b for b in builders): | 4895 if any('triggered' in b for b in builders): |
| 4861 print('ERROR You are trying to send a job to a triggered bot. This type ' | 4896 print('ERROR You are trying to send a job to a triggered bot. This type ' |
| 4862 'of bot requires an initial job from a parent (usually a builder). ' | 4897 'of bot requires an initial job from a parent (usually a builder). ' |
| 4863 'Instead send your job to the parent.\n' | 4898 'Instead send your job to the parent.\n' |
| 4864 'Bot list: %s' % builders, file=sys.stderr) | 4899 'Bot list: %s' % builders, file=sys.stderr) |
| 4865 return 1 | 4900 return 1 |
| 4866 | 4901 |
| 4867 patchset = cl.GetMostRecentPatchset() | 4902 patchset = cl.GetMostRecentPatchset() |
| 4868 if patchset != cl.GetPatchset(): | 4903 if patchset != cl.GetPatchset(): |
| 4869 print('Warning: Codereview server has newer patchsets (%s) than most ' | 4904 print('Warning: Codereview server has newer patchsets (%s) than most ' |
| 4870 'recent upload from local checkout (%s). Did a previous upload ' | 4905 'recent upload from local checkout (%s). Did a previous upload ' |
| 4871 'fail?\n' | 4906 'fail?\n' |
| 4872 'By default, git cl try uses the latest patchset from ' | 4907 'By default, git cl try uses the latest patchset from ' |
| 4873 'codereview, continuing to use patchset %s.\n' % | 4908 'codereview, continuing to use patchset %s.\n' % |
| 4874 (patchset, cl.GetPatchset(), patchset)) | 4909 (patchset, cl.GetPatchset(), patchset)) |
| 4875 try: | 4910 try: |
| 4876 _trigger_try_jobs(auth_config, cl, masters, options, 'git_cl_try', | 4911 _trigger_try_jobs(auth_config, cl, buckets, options, 'git_cl_try', |
| 4877 patchset) | 4912 patchset) |
| 4878 except BuildbucketResponseException as ex: | 4913 except BuildbucketResponseException as ex: |
| 4879 print('ERROR: %s' % ex) | 4914 print('ERROR: %s' % ex) |
| 4880 return 1 | 4915 return 1 |
| 4881 return 0 | 4916 return 0 |
| 4882 | 4917 |
| 4883 | 4918 |
| 4884 def CMDtry_results(parser, args): | 4919 def CMDtry_results(parser, args): |
| 4885 """Prints info about try jobs associated with current CL.""" | 4920 """Prints info about try jobs associated with current CL.""" |
| 4886 group = optparse.OptionGroup(parser, 'Try job results options') | 4921 group = optparse.OptionGroup(parser, 'Try job results options') |
| 4887 group.add_option( | 4922 group.add_option( |
| (...skipping 485 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 5373 if __name__ == '__main__': | 5408 if __name__ == '__main__': |
| 5374 # These affect sys.stdout so do it outside of main() to simplify mocks in | 5409 # These affect sys.stdout so do it outside of main() to simplify mocks in |
| 5375 # unit testing. | 5410 # unit testing. |
| 5376 fix_encoding.fix_encoding() | 5411 fix_encoding.fix_encoding() |
| 5377 setup_color.init() | 5412 setup_color.init() |
| 5378 try: | 5413 try: |
| 5379 sys.exit(main(sys.argv[1:])) | 5414 sys.exit(main(sys.argv[1:])) |
| 5380 except KeyboardInterrupt: | 5415 except KeyboardInterrupt: |
| 5381 sys.stderr.write('interrupted\n') | 5416 sys.stderr.write('interrupted\n') |
| 5382 sys.exit(1) | 5417 sys.exit(1) |
| OLD | NEW |