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 322 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
333 if response.status < 500 or try_count >= 2: | 333 if response.status < 500 or try_count >= 2: |
334 raise httplib2.HttpLib2Error(content) | 334 raise httplib2.HttpLib2Error(content) |
335 | 335 |
336 # status >= 500 means transient failures. | 336 # status >= 500 means transient failures. |
337 logging.debug('Transient errors when %s. Will retry.', operation_name) | 337 logging.debug('Transient errors when %s. Will retry.', operation_name) |
338 time_sleep(0.5 + 1.5*try_count) | 338 time_sleep(0.5 + 1.5*try_count) |
339 try_count += 1 | 339 try_count += 1 |
340 assert False, 'unreachable' | 340 assert False, 'unreachable' |
341 | 341 |
342 | 342 |
343 def _get_bucket_map(changelist, options, option_parser): | |
344 """Returns a dict mapping bucket names (or master names) to | |
345 builders and tests, for triggering try jobs. | |
346 """ | |
347 if not options.bot: | |
348 change = changelist.GetChange( | |
349 changelist.GetCommonAncestorWithUpstream(), None) | |
350 | |
351 # Get try masters from PRESUBMIT.py files. | |
352 masters = presubmit_support.DoGetTryMasters( | |
353 change=change, | |
354 changed_files=change.LocalPaths(), | |
355 repository_root=settings.GetRoot(), | |
356 default_presubmit=None, | |
357 project=None, | |
358 verbose=options.verbose, | |
359 output_stream=sys.stdout) | |
360 | |
361 if masters: | |
362 return masters | |
nodir
2016/11/04 21:49:40
this returns masters, not buckets. Caused https://
nodir
2016/11/04 21:55:42
https://codereview.chromium.org/2482523002
| |
363 | |
364 # Fall back to deprecated method: get try slaves from PRESUBMIT.py | |
365 # files. | |
366 options.bot = presubmit_support.DoGetTrySlaves( | |
367 change=change, | |
368 changed_files=change.LocalPaths(), | |
369 repository_root=settings.GetRoot(), | |
370 default_presubmit=None, | |
371 project=None, | |
372 verbose=options.verbose, | |
373 output_stream=sys.stdout) | |
374 | |
375 if not options.bot: | |
376 return {} | |
377 | |
378 if options.bucket: | |
379 return {options.bucket: {b: [] for b in options.bot}} | |
380 | |
381 builders_and_tests = {} | |
382 | |
383 # TODO(machenbach): The old style command-line options don't support | |
384 # multiple try masters yet. | |
385 old_style = filter(lambda x: isinstance(x, basestring), options.bot) | |
386 new_style = filter(lambda x: isinstance(x, tuple), options.bot) | |
387 | |
388 for bot in old_style: | |
389 if ':' in bot: | |
390 option_parser.error('Specifying testfilter is no longer supported') | |
391 elif ',' in bot: | |
392 option_parser.error('Specify one bot per --bot flag') | |
393 else: | |
394 builders_and_tests.setdefault(bot, []) | |
395 | |
396 for bot, tests in new_style: | |
397 builders_and_tests.setdefault(bot, []).extend(tests) | |
398 | |
399 if not options.master: | |
400 # TODO(qyearsley): crbug.com/640740 | |
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) | |
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 | |
451 | |
452 | |
343 def _trigger_try_jobs(auth_config, changelist, buckets, options, | 453 def _trigger_try_jobs(auth_config, changelist, buckets, options, |
344 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 """ | |
345 assert changelist.GetIssue(), 'CL must be uploaded first' | 463 assert changelist.GetIssue(), 'CL must be uploaded first' |
346 codereview_url = changelist.GetCodereviewServer() | 464 codereview_url = changelist.GetCodereviewServer() |
347 assert codereview_url, 'CL must be uploaded first' | 465 assert codereview_url, 'CL must be uploaded first' |
348 patchset = patchset or changelist.GetMostRecentPatchset() | 466 patchset = patchset or changelist.GetMostRecentPatchset() |
349 assert patchset, 'CL must be uploaded first' | 467 assert patchset, 'CL must be uploaded first' |
350 | 468 |
351 codereview_host = urlparse.urlparse(codereview_url).hostname | 469 codereview_host = urlparse.urlparse(codereview_url).hostname |
352 authenticator = auth.get_authenticator_for_host(codereview_host, auth_config) | 470 authenticator = auth.get_authenticator_for_host(codereview_host, auth_config) |
353 http = authenticator.authorize(httplib2.Http()) | 471 http = authenticator.authorize(httplib2.Http()) |
354 http.force_exception_to_status_code = True | 472 http.force_exception_to_status_code = True |
(...skipping 1220 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1575 | 1693 |
1576 def SetCQState(self, new_state): | 1694 def SetCQState(self, new_state): |
1577 """Update the CQ state for latest patchset. | 1695 """Update the CQ state for latest patchset. |
1578 | 1696 |
1579 Issue must have been already uploaded and known. | 1697 Issue must have been already uploaded and known. |
1580 """ | 1698 """ |
1581 assert new_state in _CQState.ALL_STATES | 1699 assert new_state in _CQState.ALL_STATES |
1582 assert self.GetIssue() | 1700 assert self.GetIssue() |
1583 return self._codereview_impl.SetCQState(new_state) | 1701 return self._codereview_impl.SetCQState(new_state) |
1584 | 1702 |
1703 def TriggerDryRun(self): | |
1704 """Triggers a dry run and prints a warning on failure.""" | |
1705 # TODO(qyearsley): Either re-use this method in CMDset_commit | |
1706 # and CMDupload, or change CMDtry to trigger dry runs with | |
1707 # just SetCQState, and catch keyboard interrupt and other | |
1708 # errors in that method. | |
1709 try: | |
1710 self.SetCQState(_CQState.DRY_RUN) | |
1711 print('scheduled CQ Dry Run on %s' % self.GetIssueURL()) | |
1712 return 0 | |
1713 except KeyboardInterrupt: | |
1714 raise | |
1715 except: | |
1716 print('WARNING: failed to trigger CQ Dry Run.\n' | |
1717 'Either:\n' | |
1718 ' * your project has no CQ\n' | |
1719 ' * you don\'t have permission to trigger Dry Run\n' | |
1720 ' * bug in this code (see stack trace below).\n' | |
1721 'Consider specifying which bots to trigger manually ' | |
1722 'or asking your project owners for permissions ' | |
1723 'or contacting Chrome Infrastructure team at ' | |
1724 'https://www.chromium.org/infra\n\n') | |
1725 # Still raise exception so that stack trace is printed. | |
1726 raise | |
1727 | |
1585 # Forward methods to codereview specific implementation. | 1728 # Forward methods to codereview specific implementation. |
1586 | 1729 |
1587 def CloseIssue(self): | 1730 def CloseIssue(self): |
1588 return self._codereview_impl.CloseIssue() | 1731 return self._codereview_impl.CloseIssue() |
1589 | 1732 |
1590 def GetStatus(self): | 1733 def GetStatus(self): |
1591 return self._codereview_impl.GetStatus() | 1734 return self._codereview_impl.GetStatus() |
1592 | 1735 |
1593 def GetCodereviewServer(self): | 1736 def GetCodereviewServer(self): |
1594 return self._codereview_impl.GetCodereviewServer() | 1737 return self._codereview_impl.GetCodereviewServer() |
(...skipping 3046 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4641 """Fetches the tree status from a json url and returns the message | 4784 """Fetches the tree status from a json url and returns the message |
4642 with the reason for the tree to be opened or closed.""" | 4785 with the reason for the tree to be opened or closed.""" |
4643 url = settings.GetTreeStatusUrl() | 4786 url = settings.GetTreeStatusUrl() |
4644 json_url = urlparse.urljoin(url, '/current?format=json') | 4787 json_url = urlparse.urljoin(url, '/current?format=json') |
4645 connection = urllib2.urlopen(json_url) | 4788 connection = urllib2.urlopen(json_url) |
4646 status = json.loads(connection.read()) | 4789 status = json.loads(connection.read()) |
4647 connection.close() | 4790 connection.close() |
4648 return status['message'] | 4791 return status['message'] |
4649 | 4792 |
4650 | 4793 |
4651 def GetBuilderMaster(bot_list): | |
4652 """For a given builder, fetch the master from AE if available.""" | |
4653 map_url = 'https://builders-map.appspot.com/' | |
4654 try: | |
4655 master_map = json.load(urllib2.urlopen(map_url)) | |
4656 except urllib2.URLError as e: | |
4657 return None, ('Failed to fetch builder-to-master map from %s. Error: %s.' % | |
4658 (map_url, e)) | |
4659 except ValueError as e: | |
4660 return None, ('Invalid json string from %s. Error: %s.' % (map_url, e)) | |
4661 if not master_map: | |
4662 return None, 'Failed to build master map.' | |
4663 | |
4664 result_master = '' | |
4665 for bot in bot_list: | |
4666 builder = bot.split(':', 1)[0] | |
4667 master_list = master_map.get(builder, []) | |
4668 if not master_list: | |
4669 return None, ('No matching master for builder %s.' % builder) | |
4670 elif len(master_list) > 1: | |
4671 return None, ('The builder name %s exists in multiple masters %s.' % | |
4672 (builder, master_list)) | |
4673 else: | |
4674 cur_master = master_list[0] | |
4675 if not result_master: | |
4676 result_master = cur_master | |
4677 elif result_master != cur_master: | |
4678 return None, 'The builders do not belong to the same master.' | |
4679 return result_master, None | |
4680 | |
4681 | |
4682 def CMDtree(parser, args): | 4794 def CMDtree(parser, args): |
4683 """Shows the status of the tree.""" | 4795 """Shows the status of the tree.""" |
4684 _, args = parser.parse_args(args) | 4796 _, args = parser.parse_args(args) |
4685 status = GetTreeStatus() | 4797 status = GetTreeStatus() |
4686 if 'unset' == status: | 4798 if 'unset' == status: |
4687 print('You must configure your tree status URL by running "git cl config".') | 4799 print('You must configure your tree status URL by running "git cl config".') |
4688 return 2 | 4800 return 2 |
4689 | 4801 |
4690 print('The tree is %s' % status) | 4802 print('The tree is %s' % status) |
4691 print() | 4803 print() |
4692 print(GetTreeStatusReason()) | 4804 print(GetTreeStatusReason()) |
4693 if status != 'open': | 4805 if status != 'open': |
4694 return 1 | 4806 return 1 |
4695 return 0 | 4807 return 0 |
4696 | 4808 |
4697 | 4809 |
4698 def CMDtry(parser, args): | 4810 def CMDtry(parser, args): |
4699 """Triggers try jobs using CQ dry run or BuildBucket for individual builders. | 4811 """Triggers try jobs using either BuildBucket or CQ dry run.""" |
4700 """ | |
4701 group = optparse.OptionGroup(parser, 'Try job options') | 4812 group = optparse.OptionGroup(parser, 'Try job options') |
4702 group.add_option( | 4813 group.add_option( |
4703 '-b', '--bot', action='append', | 4814 '-b', '--bot', action='append', |
4704 help=('IMPORTANT: specify ONE builder per --bot flag. Use it multiple ' | 4815 help=('IMPORTANT: specify ONE builder per --bot flag. Use it multiple ' |
4705 'times to specify multiple builders. ex: ' | 4816 'times to specify multiple builders. ex: ' |
4706 '"-b win_rel -b win_layout". See ' | 4817 '"-b win_rel -b win_layout". See ' |
4707 'the try server waterfall for the builders name and the tests ' | 4818 'the try server waterfall for the builders name and the tests ' |
4708 'available.')) | 4819 'available.')) |
4709 group.add_option( | 4820 group.add_option( |
4710 '-B', '--bucket', default='', | 4821 '-B', '--bucket', default='', |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4765 error_message = cl.CannotTriggerTryJobReason() | 4876 error_message = cl.CannotTriggerTryJobReason() |
4766 if error_message: | 4877 if error_message: |
4767 parser.error('Can\'t trigger try jobs: %s' % error_message) | 4878 parser.error('Can\'t trigger try jobs: %s' % error_message) |
4768 | 4879 |
4769 if not options.name: | 4880 if not options.name: |
4770 options.name = cl.GetBranch() | 4881 options.name = cl.GetBranch() |
4771 | 4882 |
4772 if options.bucket and options.master: | 4883 if options.bucket and options.master: |
4773 parser.error('Only one of --bucket and --master may be used.') | 4884 parser.error('Only one of --bucket and --master may be used.') |
4774 | 4885 |
4775 if options.bot and not options.master and not options.bucket: | 4886 buckets = _get_bucket_map(cl, options, parser) |
4776 options.master, err_msg = GetBuilderMaster(options.bot) | |
4777 if err_msg: | |
4778 parser.error('Tryserver master cannot be found because: %s\n' | |
4779 'Please manually specify the tryserver master' | |
4780 ', e.g. "-m tryserver.chromium.linux".' % err_msg) | |
4781 | 4887 |
4782 def GetMasterMap(): | 4888 if not buckets: |
4783 """Returns {master: {builder_name: [test_names]}}. Not buckets!""" | 4889 # Default to triggering Dry Run (see http://crbug.com/625697). |
4784 # Process --bot. | 4890 if options.verbose: |
4785 if not options.bot: | 4891 print('git cl try with no bots now defaults to CQ Dry Run.') |
4786 change = cl.GetChange(cl.GetCommonAncestorWithUpstream(), None) | 4892 return cl.TriggerDryRun() |
4787 | |
4788 # Get try masters from PRESUBMIT.py files. | |
4789 masters = presubmit_support.DoGetTryMasters( | |
4790 change, | |
4791 change.LocalPaths(), | |
4792 settings.GetRoot(), | |
4793 None, | |
4794 None, | |
4795 options.verbose, | |
4796 sys.stdout) | |
4797 if masters: | |
4798 return masters | |
4799 | |
4800 # Fall back to deprecated method: get try slaves from PRESUBMIT.py files. | |
4801 options.bot = presubmit_support.DoGetTrySlaves( | |
4802 change, | |
4803 change.LocalPaths(), | |
4804 settings.GetRoot(), | |
4805 None, | |
4806 None, | |
4807 options.verbose, | |
4808 sys.stdout) | |
4809 | |
4810 if not options.bot: | |
4811 return {} | |
4812 | |
4813 builders_and_tests = {} | |
4814 # TODO(machenbach): The old style command-line options don't support | |
4815 # multiple try masters yet. | |
4816 old_style = filter(lambda x: isinstance(x, basestring), options.bot) | |
4817 new_style = filter(lambda x: isinstance(x, tuple), options.bot) | |
4818 | |
4819 for bot in old_style: | |
4820 if ':' in bot: | |
4821 parser.error('Specifying testfilter is no longer supported') | |
4822 elif ',' in bot: | |
4823 parser.error('Specify one bot per --bot flag') | |
4824 else: | |
4825 builders_and_tests.setdefault(bot, []) | |
4826 | |
4827 for bot, tests in new_style: | |
4828 builders_and_tests.setdefault(bot, []).extend(tests) | |
4829 | |
4830 # 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 | |
4832 # not to be set on rietveld (deprecated). | |
4833 return {options.master: builders_and_tests} | |
4834 | |
4835 if options.bucket: | |
4836 buckets = {options.bucket: {b: [] for b in options.bot}} | |
4837 else: | |
4838 buckets = {} | |
4839 for master, data in GetMasterMap().iteritems(): | |
4840 # Add the "master." prefix to the master name to obtain the bucket name. | |
4841 bucket = _prefix_master(master) if master else '' | |
4842 buckets[bucket] = data | |
4843 | |
4844 if not buckets: | |
4845 # Default to triggering Dry Run (see http://crbug.com/625697). | |
4846 if options.verbose: | |
4847 print('git cl try with no bots now defaults to CQ Dry Run.') | |
4848 try: | |
4849 cl.SetCQState(_CQState.DRY_RUN) | |
4850 print('scheduled CQ Dry Run on %s' % cl.GetIssueURL()) | |
4851 return 0 | |
4852 except KeyboardInterrupt: | |
4853 raise | |
4854 except: | |
4855 print('WARNING: failed to trigger CQ Dry Run.\n' | |
4856 'Either:\n' | |
4857 ' * your project has no CQ\n' | |
4858 ' * you don\'t have permission to trigger Dry Run\n' | |
4859 ' * bug in this code (see stack trace below).\n' | |
4860 'Consider specifying which bots to trigger manually ' | |
4861 'or asking your project owners for permissions ' | |
4862 'or contacting Chrome Infrastructure team at ' | |
4863 'https://www.chromium.org/infra\n\n') | |
4864 # Still raise exception so that stack trace is printed. | |
4865 raise | |
4866 | 4893 |
4867 for builders in buckets.itervalues(): | 4894 for builders in buckets.itervalues(): |
4868 if any('triggered' in b for b in builders): | 4895 if any('triggered' in b for b in builders): |
4869 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 ' |
4870 '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). ' |
4871 'Instead send your job to the parent.\n' | 4898 'Instead send your job to the parent.\n' |
4872 'Bot list: %s' % builders, file=sys.stderr) | 4899 'Bot list: %s' % builders, file=sys.stderr) |
4873 return 1 | 4900 return 1 |
4874 | 4901 |
4875 patchset = cl.GetMostRecentPatchset() | 4902 patchset = cl.GetMostRecentPatchset() |
4876 if patchset != cl.GetPatchset(): | 4903 if patchset != cl.GetPatchset(): |
4877 print('Warning: Codereview server has newer patchsets (%s) than most ' | 4904 print('Warning: Codereview server has newer patchsets (%s) than most ' |
4878 'recent upload from local checkout (%s). Did a previous upload ' | 4905 'recent upload from local checkout (%s). Did a previous upload ' |
4879 'fail?\n' | 4906 'fail?\n' |
4880 'By default, git cl try uses the latest patchset from ' | 4907 'By default, git cl try uses the latest patchset from ' |
4881 'codereview, continuing to use patchset %s.\n' % | 4908 'codereview, continuing to use patchset %s.\n' % |
4882 (patchset, cl.GetPatchset(), patchset)) | 4909 (patchset, cl.GetPatchset(), patchset)) |
4910 | |
4883 try: | 4911 try: |
4884 _trigger_try_jobs(auth_config, cl, buckets, options, 'git_cl_try', | 4912 _trigger_try_jobs(auth_config, cl, buckets, options, 'git_cl_try', |
4885 patchset) | 4913 patchset) |
4886 except BuildbucketResponseException as ex: | 4914 except BuildbucketResponseException as ex: |
4887 print('ERROR: %s' % ex) | 4915 print('ERROR: %s' % ex) |
4888 return 1 | 4916 return 1 |
4889 return 0 | 4917 return 0 |
4890 | 4918 |
4891 | 4919 |
4892 def CMDtry_results(parser, args): | 4920 def CMDtry_results(parser, args): |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4994 if args: | 5022 if args: |
4995 parser.error('Unrecognized args: %s' % ' '.join(args)) | 5023 parser.error('Unrecognized args: %s' % ' '.join(args)) |
4996 if options.dry_run and options.clear: | 5024 if options.dry_run and options.clear: |
4997 parser.error('Make up your mind: both --dry-run and --clear not allowed') | 5025 parser.error('Make up your mind: both --dry-run and --clear not allowed') |
4998 | 5026 |
4999 cl = Changelist(auth_config=auth_config, issue=options.issue, | 5027 cl = Changelist(auth_config=auth_config, issue=options.issue, |
5000 codereview=options.forced_codereview) | 5028 codereview=options.forced_codereview) |
5001 if options.clear: | 5029 if options.clear: |
5002 state = _CQState.NONE | 5030 state = _CQState.NONE |
5003 elif options.dry_run: | 5031 elif options.dry_run: |
5032 # TODO(qyearsley): Use cl.TriggerDryRun. | |
5004 state = _CQState.DRY_RUN | 5033 state = _CQState.DRY_RUN |
5005 else: | 5034 else: |
5006 state = _CQState.COMMIT | 5035 state = _CQState.COMMIT |
5007 if not cl.GetIssue(): | 5036 if not cl.GetIssue(): |
5008 parser.error('Must upload the issue first') | 5037 parser.error('Must upload the issue first') |
5009 cl.SetCQState(state) | 5038 cl.SetCQState(state) |
5010 return 0 | 5039 return 0 |
5011 | 5040 |
5012 | 5041 |
5013 def CMDset_close(parser, args): | 5042 def CMDset_close(parser, args): |
(...skipping 367 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5381 if __name__ == '__main__': | 5410 if __name__ == '__main__': |
5382 # These affect sys.stdout so do it outside of main() to simplify mocks in | 5411 # These affect sys.stdout so do it outside of main() to simplify mocks in |
5383 # unit testing. | 5412 # unit testing. |
5384 fix_encoding.fix_encoding() | 5413 fix_encoding.fix_encoding() |
5385 setup_color.init() | 5414 setup_color.init() |
5386 try: | 5415 try: |
5387 sys.exit(main(sys.argv[1:])) | 5416 sys.exit(main(sys.argv[1:])) |
5388 except KeyboardInterrupt: | 5417 except KeyboardInterrupt: |
5389 sys.stderr.write('interrupted\n') | 5418 sys.stderr.write('interrupted\n') |
5390 sys.exit(1) | 5419 sys.exit(1) |
OLD | NEW |