Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: git_cl.py

Issue 2442153002: Refactoring: Extract helper functions from CMDtry in git_cl.py. (Closed)
Patch Set: - Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 321 matching lines...) Expand 10 before | Expand all | Expand 10 after
332 if response.status < 500 or try_count >= 2: 332 if response.status < 500 or try_count >= 2:
333 raise httplib2.HttpLib2Error(content) 333 raise httplib2.HttpLib2Error(content)
334 334
335 # status >= 500 means transient failures. 335 # status >= 500 means transient failures.
336 logging.debug('Transient errors when %s. Will retry.', operation_name) 336 logging.debug('Transient errors when %s. Will retry.', operation_name)
337 time_sleep(0.5 + 1.5*try_count) 337 time_sleep(0.5 + 1.5*try_count)
338 try_count += 1 338 try_count += 1
339 assert False, 'unreachable' 339 assert False, 'unreachable'
340 340
341 341
342 def _get_bucket_map(changelist, options, option_parser):
343 """Returns a dict mapping bucket names (or master names) to
344 builders and tests, for triggering try jobs.
345 """
346 if not options.bot:
347 change = changelist.GetChange(
348 changelist.GetCommonAncestorWithUpstream(), None)
349
350 # Get try masters from PRESUBMIT.py files.
351 masters = presubmit_support.DoGetTryMasters(
352 change=change,
353 changed_files=change.LocalPaths(),
354 repository_root=settings.GetRoot(),
355 default_presubmit=None,
356 project=None,
357 verbose=options.verbose,
358 output_stream=sys.stdout)
359
360 if masters:
361 return masters
362
363 # Fall back to deprecated method: get try slaves from PRESUBMIT.py
tandrii(chromium) 2016/10/23 16:55:55 we should kill this, it has lived for too long, bu
qyearsley 2016/10/24 19:09:52 Sounds good!
364 # files.
365 options.bot = presubmit_support.DoGetTrySlaves(
366 change=change,
367 changed_files=change.LocalPaths(),
368 repository_root=settings.GetRoot(),
369 default_presubmit=None,
370 project=None,
371 verbose=options.verbose,
372 output_stream=sys.stdout)
373
374 if not options.bot:
375 return {}
376
377 if options.bucket:
378 return {options.bucket: {b: [] for b in options.bot}}
379
380 builders_and_tests = {}
381
382 # TODO(machenbach): The old style command-line options don't support
383 # multiple try masters yet.
384 old_style = filter(lambda x: isinstance(x, basestring), options.bot)
385 new_style = filter(lambda x: isinstance(x, tuple), options.bot)
386
387 for bot in old_style:
388 if ':' in bot:
389 option_parser.error('Specifying testfilter is no longer supported')
390 elif ',' in bot:
391 option_parser.error('Specify one bot per --bot flag')
392 else:
393 builders_and_tests.setdefault(bot, [])
394
395 for bot, tests in new_style:
396 builders_and_tests.setdefault(bot, []).extend(tests)
397
398 if not options.master:
399 # TODO(crbug.com/640740): git cl try should be able to trigger
tandrii(chromium) 2016/10/23 16:55:55 lol :) I think styleguide mandates TODO(qyearsley)
qyearsley 2016/10/24 19:09:51 Ah, makes sense; done.
400 # builders on multiple masters if no master is given.
401 options.master, error_message = _get_builder_master(options.bot)
402 if error_message:
403 option_parser.error(
404 'Tryserver master cannot be found because: %s\n'
405 'Please manually specify the tryserver master, e.g. '
406 '"-m tryserver.chromium.linux".' % error_message)
qyearsley 2016/10/22 19:14:31 Note: This block is moved from lines 4810-4815 and
407
408 # Return a master map with one master to be backwards compatible. The
409 # master name defaults to an empty string, which will cause the master
410 # not to be set on rietveld (deprecated).
411 bucket = ''
412 if options.master:
413 # Add the "master." prefix to the master name to obtain the bucket name.
414 bucket = _prefix_master(options.master)
415 return {bucket: builders_and_tests}
416
417
418 def _get_builder_master(bot_list):
419 """Fetches a master for the given list of builders.
420
421 Returns a pair (master, error_message), where either master or
422 error_message is None.
423 """
424 map_url = 'https://builders-map.appspot.com/'
425 try:
426 master_map = json.load(urllib2.urlopen(map_url))
427 except urllib2.URLError as e:
428 return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' %
429 (map_url, e))
430 except ValueError as e:
431 return None, ('Invalid json string from %s. Error: %s.' % (map_url, e))
432 if not master_map:
433 return None, 'Failed to build master map.'
434
435 result_master = ''
436 for bot in bot_list:
437 builder = bot.split(':', 1)[0]
438 master_list = master_map.get(builder, [])
439 if not master_list:
440 return None, ('No matching master for builder %s.' % builder)
441 elif len(master_list) > 1:
442 return None, ('The builder name %s exists in multiple masters %s.' %
443 (builder, master_list))
444 else:
445 cur_master = master_list[0]
446 if not result_master:
447 result_master = cur_master
448 elif result_master != cur_master:
449 return None, 'The builders do not belong to the same master.'
450 return result_master, None
qyearsley 2016/10/22 19:14:31 This function is moved from below and renamed; in
tandrii(chromium) 2016/10/23 16:55:55 SGTM. +1 for "_" name.
451
452
342 def _trigger_try_jobs(auth_config, changelist, buckets, options, 453 def _trigger_try_jobs(auth_config, changelist, buckets, options,
343 category='git_cl_try', patchset=None): 454 category='git_cl_try', patchset=None):
455 """Sends a request to Buildbucket to trigger try jobs for a changelist.
456
457 Args:
458 auth_config: AuthConfig for Rietveld.
459 changelist: Changelist that the try jobs are associated with.
460 buckets: A nested dict mapping bucket names to builders to tests.
461 options: Command-line options.
462 """
344 assert changelist.GetIssue(), 'CL must be uploaded first' 463 assert changelist.GetIssue(), 'CL must be uploaded first'
345 codereview_url = changelist.GetCodereviewServer() 464 codereview_url = changelist.GetCodereviewServer()
346 assert codereview_url, 'CL must be uploaded first' 465 assert codereview_url, 'CL must be uploaded first'
347 patchset = patchset or changelist.GetMostRecentPatchset() 466 patchset = patchset or changelist.GetMostRecentPatchset()
348 assert patchset, 'CL must be uploaded first' 467 assert patchset, 'CL must be uploaded first'
349 468
350 codereview_host = urlparse.urlparse(codereview_url).hostname 469 codereview_host = urlparse.urlparse(codereview_url).hostname
351 authenticator = auth.get_authenticator_for_host(codereview_host, auth_config) 470 authenticator = auth.get_authenticator_for_host(codereview_host, auth_config)
352 http = authenticator.authorize(httplib2.Http()) 471 http = authenticator.authorize(httplib2.Http())
353 http.force_exception_to_status_code = True 472 http.force_exception_to_status_code = True
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
427 buildbucket_put_url, 546 buildbucket_put_url,
428 'PUT', 547 'PUT',
429 body=json.dumps(batch_req_body), 548 body=json.dumps(batch_req_body),
430 headers={'Content-Type': 'application/json'} 549 headers={'Content-Type': 'application/json'}
431 ) 550 )
432 print_text.append('To see results here, run: git cl try-results') 551 print_text.append('To see results here, run: git cl try-results')
433 print_text.append('To see results in browser, run: git cl web') 552 print_text.append('To see results in browser, run: git cl web')
434 print('\n'.join(print_text)) 553 print('\n'.join(print_text))
435 554
436 555
556 def _trigger_dry_run(changelist):
557 """Triggers a dry run and prints a warning on failure."""
558 try:
559 changelist.SetCQState(_CQState.DRY_RUN)
560 print('scheduled CQ Dry Run on %s' % changelist.GetIssueURL())
561 return 0
562 except KeyboardInterrupt:
563 raise
564 except:
565 print('WARNING: failed to trigger CQ Dry Run.\n'
566 'Either:\n'
567 ' * your project has no CQ\n'
568 ' * you don\'t have permission to trigger Dry Run\n'
569 ' * bug in this code (see stack trace below).\n'
570 'Consider specifying which bots to trigger manually '
571 'or asking your project owners for permissions '
572 'or contacting Chrome Infrastructure team at '
573 'https://www.chromium.org/infra\n\n')
574 # Still raise exception so that stack trace is printed.
575 raise
qyearsley 2016/10/22 19:14:31 This block is extracted from below; potentially, i
tandrii(chromium) 2016/10/23 16:55:55 SGTM, how about making it method of ChangeList its
qyearsley 2016/10/24 19:09:51 Done, and added TODO note to make use of this func
576
577
437 def fetch_try_jobs(auth_config, changelist, buildbucket_host, 578 def fetch_try_jobs(auth_config, changelist, buildbucket_host,
438 patchset=None): 579 patchset=None):
439 """Fetches try jobs from buildbucket. 580 """Fetches try jobs from buildbucket.
440 581
441 Returns a map from build id to build info as a dictionary. 582 Returns a map from build id to build info as a dictionary.
442 """ 583 """
443 assert buildbucket_host 584 assert buildbucket_host
444 assert changelist.GetIssue(), 'CL must be uploaded first' 585 assert changelist.GetIssue(), 'CL must be uploaded first'
445 assert changelist.GetCodereviewServer(), 'CL must be uploaded first' 586 assert changelist.GetCodereviewServer(), 'CL must be uploaded first'
446 patchset = patchset or changelist.GetMostRecentPatchset() 587 patchset = patchset or changelist.GetMostRecentPatchset()
(...skipping 4229 matching lines...) Expand 10 before | Expand all | Expand 10 after
4676 """Fetches the tree status from a json url and returns the message 4817 """Fetches the tree status from a json url and returns the message
4677 with the reason for the tree to be opened or closed.""" 4818 with the reason for the tree to be opened or closed."""
4678 url = settings.GetTreeStatusUrl() 4819 url = settings.GetTreeStatusUrl()
4679 json_url = urlparse.urljoin(url, '/current?format=json') 4820 json_url = urlparse.urljoin(url, '/current?format=json')
4680 connection = urllib2.urlopen(json_url) 4821 connection = urllib2.urlopen(json_url)
4681 status = json.loads(connection.read()) 4822 status = json.loads(connection.read())
4682 connection.close() 4823 connection.close()
4683 return status['message'] 4824 return status['message']
4684 4825
4685 4826
4686 def GetBuilderMaster(bot_list):
4687 """For a given builder, fetch the master from AE if available."""
4688 map_url = 'https://builders-map.appspot.com/'
4689 try:
4690 master_map = json.load(urllib2.urlopen(map_url))
4691 except urllib2.URLError as e:
4692 return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' %
4693 (map_url, e))
4694 except ValueError as e:
4695 return None, ('Invalid json string from %s. Error: %s.' % (map_url, e))
4696 if not master_map:
4697 return None, 'Failed to build master map.'
4698
4699 result_master = ''
4700 for bot in bot_list:
4701 builder = bot.split(':', 1)[0]
4702 master_list = master_map.get(builder, [])
4703 if not master_list:
4704 return None, ('No matching master for builder %s.' % builder)
4705 elif len(master_list) > 1:
4706 return None, ('The builder name %s exists in multiple masters %s.' %
4707 (builder, master_list))
4708 else:
4709 cur_master = master_list[0]
4710 if not result_master:
4711 result_master = cur_master
4712 elif result_master != cur_master:
4713 return None, 'The builders do not belong to the same master.'
4714 return result_master, None
4715
4716
4717 def CMDtree(parser, args): 4827 def CMDtree(parser, args):
4718 """Shows the status of the tree.""" 4828 """Shows the status of the tree."""
4719 _, args = parser.parse_args(args) 4829 _, args = parser.parse_args(args)
4720 status = GetTreeStatus() 4830 status = GetTreeStatus()
4721 if 'unset' == status: 4831 if 'unset' == status:
4722 print('You must configure your tree status URL by running "git cl config".') 4832 print('You must configure your tree status URL by running "git cl config".')
4723 return 2 4833 return 2
4724 4834
4725 print('The tree is %s' % status) 4835 print('The tree is %s' % status)
4726 print() 4836 print()
4727 print(GetTreeStatusReason()) 4837 print(GetTreeStatusReason())
4728 if status != 'open': 4838 if status != 'open':
4729 return 1 4839 return 1
4730 return 0 4840 return 0
4731 4841
4732 4842
4733 def CMDtry(parser, args): 4843 def CMDtry(parser, args):
4734 """Triggers try jobs using CQ dry run or BuildBucket for individual builders. 4844 """Triggers try jobs using either BuildBucket or CQ dry run."""
4735 """
4736 group = optparse.OptionGroup(parser, 'Try job options') 4845 group = optparse.OptionGroup(parser, 'Try job options')
4737 group.add_option( 4846 group.add_option(
4738 '-b', '--bot', action='append', 4847 '-b', '--bot', action='append',
4739 help=('IMPORTANT: specify ONE builder per --bot flag. Use it multiple ' 4848 help=('IMPORTANT: specify ONE builder per --bot flag. Use it multiple '
4740 'times to specify multiple builders. ex: ' 4849 'times to specify multiple builders. ex: '
4741 '"-b win_rel -b win_layout". See ' 4850 '"-b win_rel -b win_layout". See '
4742 'the try server waterfall for the builders name and the tests ' 4851 'the try server waterfall for the builders name and the tests '
4743 'available.')) 4852 'available.'))
4744 group.add_option( 4853 group.add_option(
4745 '-B', '--bucket', default='', 4854 '-B', '--bucket', default='',
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
4800 error_message = cl.CannotTriggerTryJobReason() 4909 error_message = cl.CannotTriggerTryJobReason()
4801 if error_message: 4910 if error_message:
4802 parser.error('Can\'t trigger try jobs: %s') 4911 parser.error('Can\'t trigger try jobs: %s')
4803 4912
4804 if not options.name: 4913 if not options.name:
4805 options.name = cl.GetBranch() 4914 options.name = cl.GetBranch()
4806 4915
4807 if options.bucket and options.master: 4916 if options.bucket and options.master:
4808 parser.error('Only one of --bucket and --master may be used.') 4917 parser.error('Only one of --bucket and --master may be used.')
4809 4918
4810 if options.bot and not options.master and not options.bucket: 4919 buckets = _get_bucket_map(cl, options, parser)
4811 options.master, err_msg = GetBuilderMaster(options.bot)
4812 if err_msg:
4813 parser.error('Tryserver master cannot be found because: %s\n'
4814 'Please manually specify the tryserver master'
4815 ', e.g. "-m tryserver.chromium.linux".' % err_msg)
4816 4920
4817 def GetMasterMap(): 4921 if not buckets:
4818 # Process --bot. 4922 # Default to triggering Dry Run (see http://crbug.com/625697).
4819 if not options.bot: 4923 if options.verbose:
4820 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None) 4924 print('git cl try with no bots now defaults to CQ Dry Run.')
4821 4925 return _trigger_dry_run(cl)
4822 # Get try masters from PRESUBMIT.py files.
4823 masters = presubmit_support.DoGetTryMasters(
4824 change,
4825 change.LocalPaths(),
4826 settings.GetRoot(),
4827 None,
4828 None,
4829 options.verbose,
4830 sys.stdout)
4831 if masters:
4832 return masters
4833
4834 # Fall back to deprecated method: get try slaves from PRESUBMIT.py files.
4835 options.bot = presubmit_support.DoGetTrySlaves(
4836 change,
4837 change.LocalPaths(),
4838 settings.GetRoot(),
4839 None,
4840 None,
4841 options.verbose,
4842 sys.stdout)
4843
4844 if not options.bot:
4845 return {}
4846
4847 builders_and_tests = {}
4848 # TODO(machenbach): The old style command-line options don't support
4849 # multiple try masters yet.
4850 old_style = filter(lambda x: isinstance(x, basestring), options.bot)
4851 new_style = filter(lambda x: isinstance(x, tuple), options.bot)
4852
4853 for bot in old_style:
4854 if ':' in bot:
4855 parser.error('Specifying testfilter is no longer supported')
4856 elif ',' in bot:
4857 parser.error('Specify one bot per --bot flag')
4858 else:
4859 builders_and_tests.setdefault(bot, [])
4860
4861 for bot, tests in new_style:
4862 builders_and_tests.setdefault(bot, []).extend(tests)
4863
4864 # Return a master map with one master to be backwards compatible. The
4865 # master name defaults to an empty string, which will cause the master
4866 # not to be set on rietveld (deprecated).
4867 bucket = ''
4868 if options.master:
4869 # Add the "master." prefix to the master name to obtain the bucket name.
4870 bucket = _prefix_master(options.master)
4871 return {bucket: builders_and_tests}
4872
4873 if options.bucket:
4874 buckets = {options.bucket: {b: [] for b in options.bot}}
4875 else:
4876 buckets = GetMasterMap()
4877 if not buckets:
4878 # Default to triggering Dry Run (see http://crbug.com/625697).
4879 if options.verbose:
4880 print('git cl try with no bots now defaults to CQ Dry Run.')
4881 try:
4882 cl.SetCQState(_CQState.DRY_RUN)
4883 print('scheduled CQ Dry Run on %s' % cl.GetIssueURL())
4884 return 0
4885 except KeyboardInterrupt:
4886 raise
4887 except:
4888 print('WARNING: failed to trigger CQ Dry Run.\n'
4889 'Either:\n'
4890 ' * your project has no CQ\n'
4891 ' * you don\'t have permission to trigger Dry Run\n'
4892 ' * bug in this code (see stack trace below).\n'
4893 'Consider specifying which bots to trigger manually '
4894 'or asking your project owners for permissions '
4895 'or contacting Chrome Infrastructure team at '
4896 'https://www.chromium.org/infra\n\n')
4897 # Still raise exception so that stack trace is printed.
4898 raise
4899 4926
4900 for builders in buckets.itervalues(): 4927 for builders in buckets.itervalues():
4901 if any('triggered' in b for b in builders): 4928 if any('triggered' in b for b in builders):
4902 print('ERROR You are trying to send a job to a triggered bot. This type ' 4929 print('ERROR You are trying to send a job to a triggered bot. This type '
4903 'of bot requires an initial job from a parent (usually a builder). ' 4930 'of bot requires an initial job from a parent (usually a builder). '
4904 'Instead send your job to the parent.\n' 4931 'Instead send your job to the parent.\n'
4905 'Bot list: %s' % builders, file=sys.stderr) 4932 'Bot list: %s' % builders, file=sys.stderr)
4906 return 1 4933 return 1
4907 4934
4908 patchset = cl.GetMostRecentPatchset() 4935 patchset = cl.GetMostRecentPatchset()
4909 if patchset != cl.GetPatchset(): 4936 if patchset != cl.GetPatchset():
4910 print('Warning: Codereview server has newer patchsets (%s) than most ' 4937 print('Warning: Codereview server has newer patchsets (%s) than most '
4911 'recent upload from local checkout (%s). Did a previous upload ' 4938 'recent upload from local checkout (%s). Did a previous upload '
4912 'fail?\n' 4939 'fail?\n'
4913 'By default, git cl try uses the latest patchset from ' 4940 'By default, git cl try uses the latest patchset from '
4914 'codereview, continuing to use patchset %s.\n' % 4941 'codereview, continuing to use patchset %s.\n' %
4915 (patchset, cl.GetPatchset(), patchset)) 4942 (patchset, cl.GetPatchset(), patchset))
4943
4916 try: 4944 try:
4917 _trigger_try_jobs(auth_config, cl, buckets, options, 'git_cl_try', 4945 _trigger_try_jobs(auth_config, cl, buckets, options, 'git_cl_try',
4918 patchset) 4946 patchset)
4919 except BuildbucketResponseException as ex: 4947 except BuildbucketResponseException as ex:
4920 print('ERROR: %s' % ex) 4948 print('ERROR: %s' % ex)
4921 return 1 4949 return 1
4922 return 0 4950 return 0
4923 4951
4924 4952
4925 def CMDtry_results(parser, args): 4953 def CMDtry_results(parser, args):
(...skipping 488 matching lines...) Expand 10 before | Expand all | Expand 10 after
5414 if __name__ == '__main__': 5442 if __name__ == '__main__':
5415 # These affect sys.stdout so do it outside of main() to simplify mocks in 5443 # These affect sys.stdout so do it outside of main() to simplify mocks in
5416 # unit testing. 5444 # unit testing.
5417 fix_encoding.fix_encoding() 5445 fix_encoding.fix_encoding()
5418 setup_color.init() 5446 setup_color.init()
5419 try: 5447 try:
5420 sys.exit(main(sys.argv[1:])) 5448 sys.exit(main(sys.argv[1:]))
5421 except KeyboardInterrupt: 5449 except KeyboardInterrupt:
5422 sys.stderr.write('interrupted\n') 5450 sys.stderr.write('interrupted\n')
5423 sys.exit(1) 5451 sys.exit(1)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698