Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # git-cl -- a git-command for integrating reviews on Rietveld | 2 # git-cl -- a git-command for integrating reviews on Rietveld |
| 3 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 3 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
| 4 | 4 |
| 5 import errno | 5 import errno |
| 6 import logging | 6 import logging |
| 7 import optparse | 7 import optparse |
| 8 import os | 8 import os |
| 9 import re | 9 import re |
| 10 import subprocess | 10 import subprocess |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 46 return subprocess.Popen(cmd, **kwargs) | 46 return subprocess.Popen(cmd, **kwargs) |
| 47 except OSError, e: | 47 except OSError, e: |
| 48 if e.errno == errno.EAGAIN and sys.platform == 'cygwin': | 48 if e.errno == errno.EAGAIN and sys.platform == 'cygwin': |
| 49 DieWithError( | 49 DieWithError( |
| 50 'Visit ' | 50 'Visit ' |
| 51 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure to ' | 51 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure to ' |
| 52 'learn how to fix this error; you need to rebase your cygwin dlls') | 52 'learn how to fix this error; you need to rebase your cygwin dlls') |
| 53 raise | 53 raise |
| 54 | 54 |
| 55 | 55 |
| 56 def RunCommand(cmd, error_ok=False, error_message=None, | 56 def RunCommand(cmd, error_ok=False, error_message=None, |
|
M-A Ruel
2011/03/08 19:58:32
I want to merge this code into gclient_utils.Check
Dirk Pranke
2011/03/08 22:26:03
I agree that would be good. I can look at doing th
| |
| 57 redirect_stdout=True, swallow_stderr=False, **kwargs): | 57 redirect_stdout=True, swallow_stderr=False, |
| 58 tee_filter=None, **kwargs): | |
| 58 if redirect_stdout: | 59 if redirect_stdout: |
| 59 stdout = subprocess.PIPE | 60 stdout = subprocess.PIPE |
| 60 else: | 61 else: |
| 61 stdout = None | 62 stdout = None |
| 62 if swallow_stderr: | 63 if swallow_stderr: |
| 63 stderr = subprocess.PIPE | 64 stderr = subprocess.PIPE |
| 64 else: | 65 else: |
| 65 stderr = None | 66 stderr = None |
| 66 proc = Popen(cmd, stdout=stdout, stderr=stderr, **kwargs) | 67 proc = Popen(cmd, stdout=stdout, stderr=stderr, **kwargs) |
| 67 output = proc.communicate()[0] | 68 if tee_filter: |
| 69 output = '' | |
| 70 while True: | |
| 71 line = proc.stdout.readline() | |
| 72 if line == '' and proc.poll() != None: | |
| 73 break | |
| 74 if not tee_filter(line): | |
| 75 print line | |
| 76 output += line + '\n' | |
| 77 else: | |
| 78 output = proc.communicate()[0] | |
| 79 | |
| 68 if not error_ok and proc.returncode != 0: | 80 if not error_ok and proc.returncode != 0: |
| 69 DieWithError('Command "%s" failed.\n' % (' '.join(cmd)) + | 81 DieWithError('Command "%s" failed.\n' % (' '.join(cmd)) + |
| 70 (error_message or output or '')) | 82 (error_message or output or '')) |
| 71 return output | 83 return output |
| 72 | 84 |
| 73 | 85 |
| 74 def RunGit(args, **kwargs): | 86 def RunGit(args, **kwargs): |
| 75 cmd = ['git'] + args | 87 cmd = ['git'] + args |
| 76 return RunCommand(cmd, **kwargs) | 88 return RunCommand(cmd, **kwargs) |
| 77 | 89 |
| (...skipping 394 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 472 | 484 |
| 473 SetProperty(settings.GetCCList(), 'CC list', 'cc') | 485 SetProperty(settings.GetCCList(), 'CC list', 'cc') |
| 474 SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL', | 486 SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL', |
| 475 'tree-status-url') | 487 'tree-status-url') |
| 476 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url') | 488 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url') |
| 477 | 489 |
| 478 # TODO: configure a default branch to diff against, rather than this | 490 # TODO: configure a default branch to diff against, rather than this |
| 479 # svn-based hackery. | 491 # svn-based hackery. |
| 480 | 492 |
| 481 | 493 |
| 494 class HookResults(object): | |
| 495 """First stab at a framework for letting hooks alter what happens next. | |
|
M-A Ruel
2011/03/08 19:58:32
Can you define what it is instead?
Same for Chang
Dirk Pranke
2011/03/08 22:26:03
Will do.
| |
| 496 For now, all they can do is suggest reviewers.""" | |
| 497 def __init__(self, output_from_hooks=None): | |
| 498 self.reviewers = [] | |
| 499 self.output = None | |
| 500 self._ParseOutputFromHooks(output_from_hooks) | |
| 501 | |
| 502 def print_output(self): | |
| 503 if self.output: | |
| 504 print '\n'.join(self.output) | |
| 505 | |
| 506 def _ParseOutputFromHooks(self, output_from_hooks): | |
| 507 if not output_from_hooks: | |
| 508 return | |
| 509 lines = [] | |
| 510 reviewer_regexp = re.compile('ADD: R=(.+)') | |
| 511 for l in output_from_hooks.split('\n'): | |
|
M-A Ruel
2011/03/08 19:58:32
splitlines()
Dirk Pranke
2011/03/08 22:26:03
I wasn't sure if this was a builtin on strings, an
| |
| 512 m = reviewer_regexp.match(l) | |
| 513 if m: | |
| 514 self.reviewers = m.group(1) | |
|
M-A Ruel
2011/03/08 19:58:32
What about multiple R= lines?
Dirk Pranke
2011/03/08 22:26:03
Obviously, this version doesn't support that. I'm
| |
| 515 else: | |
| 516 lines.append(l) | |
| 517 self.output = '\n'.join(lines) | |
| 518 | |
| 519 | |
| 520 class ChangeDescription(object): | |
| 521 def __init__(self, subject, log_desc, reviewers): | |
| 522 self.subject = subject | |
| 523 self.log_desc = log_desc | |
| 524 self.reviewers = reviewers | |
| 525 self.description = self.log_desc | |
| 526 | |
| 527 def Update(self): | |
| 528 initial_text = """# Enter a description of the change. | |
| 529 # This will displayed on the codereview site. | |
| 530 # The first line will also be used as the subject of the review. | |
| 531 """ | |
| 532 initial_text += self.description | |
| 533 if 'R=' not in self.description and self.reviewers: | |
| 534 initial_text += '\nR=' + self.reviewers | |
| 535 if 'BUG=' not in self.description: | |
| 536 initial_text += '\nBUG=' | |
| 537 if 'TEST=' not in self.description: | |
| 538 initial_text += '\nTEST=' | |
| 539 self._ParseDescription(UserEditedLog(initial_text)) | |
| 540 | |
| 541 def _ParseDescription(self, description): | |
| 542 if not description: | |
| 543 self.description = description | |
| 544 return | |
| 545 | |
| 546 parsed_lines = [] | |
| 547 reviewers_regexp = re.compile('\s*R=(.+)') | |
| 548 reviewers = '' | |
| 549 subject = '' | |
| 550 for l in description.splitlines(): | |
| 551 if not subject: | |
| 552 subject = l | |
| 553 matched_reviewers = reviewers_regexp.match(l) | |
| 554 if matched_reviewers: | |
| 555 reviewers = matched_reviewers.group(1) | |
| 556 parsed_lines.append(l) | |
| 557 | |
| 558 self.description = '\n'.join(parsed_lines) + '\n' | |
| 559 self.subject = subject | |
| 560 self.reviewers = reviewers | |
| 561 | |
| 562 def IsEmpty(self): | |
| 563 return not self.description | |
| 564 | |
| 565 | |
| 482 def FindCodereviewSettingsFile(filename='codereview.settings'): | 566 def FindCodereviewSettingsFile(filename='codereview.settings'): |
| 483 """Finds the given file starting in the cwd and going up. | 567 """Finds the given file starting in the cwd and going up. |
| 484 | 568 |
| 485 Only looks up to the top of the repository unless an | 569 Only looks up to the top of the repository unless an |
| 486 'inherit-review-settings-ok' file exists in the root of the repository. | 570 'inherit-review-settings-ok' file exists in the root of the repository. |
| 487 """ | 571 """ |
| 488 inherit_ok_file = 'inherit-review-settings-ok' | 572 inherit_ok_file = 'inherit-review-settings-ok' |
| 489 cwd = os.getcwd() | 573 cwd = os.getcwd() |
| 490 root = os.path.abspath(RunGit(['rev-parse', '--show-cdup']).strip()) | 574 root = os.path.abspath(RunGit(['rev-parse', '--show-cdup']).strip()) |
| 491 if os.path.isfile(os.path.join(root, inherit_ok_file)): | 575 if os.path.isfile(os.path.join(root, inherit_ok_file)): |
| (...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 666 fileobj.close() | 750 fileobj.close() |
| 667 os.remove(filename) | 751 os.remove(filename) |
| 668 stripcomment_re = re.compile(r'^#.*$', re.MULTILINE) | 752 stripcomment_re = re.compile(r'^#.*$', re.MULTILINE) |
| 669 return stripcomment_re.sub('', text).strip() | 753 return stripcomment_re.sub('', text).strip() |
| 670 | 754 |
| 671 | 755 |
| 672 def RunHook(hook, upstream_branch, error_ok=False): | 756 def RunHook(hook, upstream_branch, error_ok=False): |
| 673 """Run a given hook if it exists. By default, we fail on errors.""" | 757 """Run a given hook if it exists. By default, we fail on errors.""" |
| 674 hook = '%s/%s' % (settings.GetRoot(), hook) | 758 hook = '%s/%s' % (settings.GetRoot(), hook) |
| 675 if not os.path.exists(hook): | 759 if not os.path.exists(hook): |
| 676 return | 760 return HookResults() |
| 677 return RunCommand([hook, upstream_branch], error_ok=error_ok, | 761 |
| 678 redirect_stdout=False) | 762 output = RunCommand([hook, upstream_branch], error_ok=error_ok, |
| 763 redirect_stdout=True, | |
| 764 tee_filter=lambda l: l.startswith('ADD:')) | |
| 765 return HookResults(output) | |
| 679 | 766 |
| 680 | 767 |
| 681 def CMDpresubmit(parser, args): | 768 def CMDpresubmit(parser, args): |
| 682 """run presubmit tests on the current changelist""" | 769 """run presubmit tests on the current changelist""" |
| 683 parser.add_option('--upload', action='store_true', | 770 parser.add_option('--upload', action='store_true', |
| 684 help='Run upload hook instead of the push/dcommit hook') | 771 help='Run upload hook instead of the push/dcommit hook') |
| 685 (options, args) = parser.parse_args(args) | 772 (options, args) = parser.parse_args(args) |
| 686 | 773 |
| 687 # Make sure index is up-to-date before running diff-index. | 774 # Make sure index is up-to-date before running diff-index. |
| 688 RunGit(['update-index', '--refresh', '-q'], error_ok=True) | 775 RunGit(['update-index', '--refresh', '-q'], error_ok=True) |
| 689 if RunGit(['diff-index', 'HEAD']): | 776 if RunGit(['diff-index', 'HEAD']): |
| 690 # TODO(maruel): Is this really necessary? | 777 # TODO(maruel): Is this really necessary? |
| 691 print 'Cannot presubmit with a dirty tree. You must commit locally first.' | 778 print 'Cannot presubmit with a dirty tree. You must commit locally first.' |
| 692 return 1 | 779 return 1 |
| 693 | 780 |
| 694 if args: | 781 if args: |
| 695 base_branch = args[0] | 782 base_branch = args[0] |
| 696 else: | 783 else: |
| 697 # Default to diffing against the "upstream" branch. | 784 # Default to diffing against the "upstream" branch. |
| 698 base_branch = Changelist().GetUpstreamBranch() | 785 base_branch = Changelist().GetUpstreamBranch() |
| 699 | 786 |
| 700 if options.upload: | 787 if options.upload: |
| 701 print '*** Presubmit checks for UPLOAD would report: ***' | 788 print '*** Presubmit checks for UPLOAD would report: ***' |
| 702 return not RunHook(PREUPLOAD_HOOK, upstream_branch=base_branch, | 789 return not RunHook(PREUPLOAD_HOOK, upstream_branch=base_branch, |
| 703 error_ok=True) | 790 error_ok=True).output |
| 704 else: | 791 else: |
| 705 print '*** Presubmit checks for DCOMMIT would report: ***' | 792 print '*** Presubmit checks for DCOMMIT would report: ***' |
| 706 return not RunHook(PREDCOMMIT_HOOK, upstream_branch=base_branch, | 793 return not RunHook(PREDCOMMIT_HOOK, upstream_branch=base_branch, |
| 707 error_ok=True) | 794 error_ok=True).output |
| 708 | 795 |
| 709 | 796 |
| 710 @usage('[args to "git diff"]') | 797 @usage('[args to "git diff"]') |
| 711 def CMDupload(parser, args): | 798 def CMDupload(parser, args): |
| 712 """upload the current changelist to codereview""" | 799 """upload the current changelist to codereview""" |
| 713 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 800 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
| 714 help='bypass upload presubmit hook') | 801 help='bypass upload presubmit hook') |
| 715 parser.add_option('-m', dest='message', help='message for patch') | 802 parser.add_option('-m', dest='message', help='message for patch') |
| 716 parser.add_option('-r', '--reviewers', | 803 parser.add_option('-r', '--reviewers', |
| 717 help='reviewer email addresses') | 804 help='reviewer email addresses') |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 736 | 823 |
| 737 cl = Changelist() | 824 cl = Changelist() |
| 738 if args: | 825 if args: |
| 739 base_branch = args[0] | 826 base_branch = args[0] |
| 740 else: | 827 else: |
| 741 # Default to diffing against the "upstream" branch. | 828 # Default to diffing against the "upstream" branch. |
| 742 base_branch = cl.GetUpstreamBranch() | 829 base_branch = cl.GetUpstreamBranch() |
| 743 args = [base_branch + "..."] | 830 args = [base_branch + "..."] |
| 744 | 831 |
| 745 if not options.bypass_hooks: | 832 if not options.bypass_hooks: |
| 746 RunHook(PREUPLOAD_HOOK, upstream_branch=base_branch, error_ok=False) | 833 hook_results = RunHook(PREUPLOAD_HOOK, upstream_branch=base_branch, |
| 834 error_ok=False) | |
| 835 else: | |
| 836 hook_results = HookResults() | |
| 837 | |
| 838 if not options.reviewers and hook_results.reviewers: | |
| 839 options.reviewers = hook_results.reviewers | |
| 747 | 840 |
| 748 # --no-ext-diff is broken in some versions of Git, so try to work around | 841 # --no-ext-diff is broken in some versions of Git, so try to work around |
| 749 # this by overriding the environment (but there is still a problem if the | 842 # this by overriding the environment (but there is still a problem if the |
| 750 # git config key "diff.external" is used). | 843 # git config key "diff.external" is used). |
| 751 env = os.environ.copy() | 844 env = os.environ.copy() |
| 752 if 'GIT_EXTERNAL_DIFF' in env: | 845 if 'GIT_EXTERNAL_DIFF' in env: |
| 753 del env['GIT_EXTERNAL_DIFF'] | 846 del env['GIT_EXTERNAL_DIFF'] |
| 754 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, | 847 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, |
| 755 env=env) | 848 env=env) |
| 756 | 849 |
| 757 upload_args = ['--assume_yes'] # Don't ask about untracked files. | 850 upload_args = ['--assume_yes'] # Don't ask about untracked files. |
| 758 upload_args.extend(['--server', cl.GetRietveldServer()]) | 851 upload_args.extend(['--server', cl.GetRietveldServer()]) |
| 759 if options.reviewers: | |
| 760 upload_args.extend(['--reviewers', options.reviewers]) | |
| 761 if options.emulate_svn_auto_props: | 852 if options.emulate_svn_auto_props: |
| 762 upload_args.append('--emulate_svn_auto_props') | 853 upload_args.append('--emulate_svn_auto_props') |
| 763 if options.send_mail: | 854 if options.send_mail: |
| 764 if not options.reviewers: | 855 if not options.reviewers: |
| 765 DieWithError("Must specify reviewers to send email.") | 856 DieWithError("Must specify reviewers to send email.") |
| 766 upload_args.append('--send_mail') | 857 upload_args.append('--send_mail') |
| 767 if options.from_logs and not options.message: | 858 if options.from_logs and not options.message: |
| 768 print 'Must set message for subject line if using desc_from_logs' | 859 print 'Must set message for subject line if using desc_from_logs' |
| 769 return 1 | 860 return 1 |
| 770 | 861 |
| 771 change_desc = None | 862 change_desc = None |
| 772 | 863 |
| 773 if cl.GetIssue(): | 864 if cl.GetIssue(): |
| 774 if options.message: | 865 if options.message: |
| 775 upload_args.extend(['--message', options.message]) | 866 upload_args.extend(['--message', options.message]) |
| 776 upload_args.extend(['--issue', cl.GetIssue()]) | 867 upload_args.extend(['--issue', cl.GetIssue()]) |
| 777 print ("This branch is associated with issue %s. " | 868 print ("This branch is associated with issue %s. " |
| 778 "Adding patch to that issue." % cl.GetIssue()) | 869 "Adding patch to that issue." % cl.GetIssue()) |
| 779 else: | 870 else: |
| 780 log_desc = CreateDescriptionFromLog(args) | 871 log_desc = CreateDescriptionFromLog(args) |
| 781 if options.from_logs: | 872 change_desc = ChangeDescription(options.message, log_desc, |
| 782 # Uses logs as description and message as subject. | 873 options.reviewers) |
| 783 subject = options.message | 874 if not options.from_logs: |
| 784 change_desc = subject + '\n\n' + log_desc | 875 change_desc.Update() |
| 785 else: | 876 |
| 786 initial_text = """# Enter a description of the change. | 877 if change_desc.IsEmpty(): |
| 787 # This will displayed on the codereview site. | |
| 788 # The first line will also be used as the subject of the review. | |
| 789 """ | |
| 790 if 'BUG=' not in log_desc: | |
| 791 log_desc += '\nBUG=' | |
| 792 if 'TEST=' not in log_desc: | |
| 793 log_desc += '\nTEST=' | |
| 794 change_desc = UserEditedLog(initial_text + log_desc) | |
| 795 subject = '' | |
| 796 if change_desc: | |
| 797 subject = change_desc.splitlines()[0] | |
| 798 if not change_desc: | |
| 799 print "Description is empty; aborting." | 878 print "Description is empty; aborting." |
| 800 return 1 | 879 return 1 |
| 801 upload_args.extend(['--message', subject]) | 880 |
| 802 upload_args.extend(['--description', change_desc]) | 881 upload_args.extend(['--message', change_desc.subject]) |
| 882 upload_args.extend(['--description', change_desc.description]) | |
| 883 if change_desc.reviewers: | |
| 884 upload_args.extend(['--reviewers', change_desc.reviewers]) | |
| 803 cc = ','.join(filter(None, (settings.GetCCList(), options.cc))) | 885 cc = ','.join(filter(None, (settings.GetCCList(), options.cc))) |
| 804 if cc: | 886 if cc: |
| 805 upload_args.extend(['--cc', cc]) | 887 upload_args.extend(['--cc', cc]) |
| 806 | 888 |
| 807 # Include the upstream repo's URL in the change -- this is useful for | 889 # Include the upstream repo's URL in the change -- this is useful for |
| 808 # projects that have their source spread across multiple repos. | 890 # projects that have their source spread across multiple repos. |
| 809 remote_url = None | 891 remote_url = None |
| 810 if settings.GetIsGitSvn(): | 892 if settings.GetIsGitSvn(): |
| 811 # URL is dependent on the current directory. | 893 # URL is dependent on the current directory. |
| 812 data = RunGit(['svn', 'info'], cwd=settings.GetRoot()) | 894 data = RunGit(['svn', 'info'], cwd=settings.GetRoot()) |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 824 try: | 906 try: |
| 825 issue, patchset = upload.RealMain(['upload'] + upload_args + args) | 907 issue, patchset = upload.RealMain(['upload'] + upload_args + args) |
| 826 except: | 908 except: |
| 827 # If we got an exception after the user typed a description for their | 909 # If we got an exception after the user typed a description for their |
| 828 # change, back up the description before re-raising. | 910 # change, back up the description before re-raising. |
| 829 if change_desc: | 911 if change_desc: |
| 830 backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE) | 912 backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE) |
| 831 print '\nGot exception while uploading -- saving description to %s\n' \ | 913 print '\nGot exception while uploading -- saving description to %s\n' \ |
| 832 % backup_path | 914 % backup_path |
| 833 backup_file = open(backup_path, 'w') | 915 backup_file = open(backup_path, 'w') |
| 834 backup_file.write(change_desc) | 916 backup_file.write(change_desc.description) |
| 835 backup_file.close() | 917 backup_file.close() |
| 836 raise | 918 raise |
| 837 | 919 |
| 838 if not cl.GetIssue(): | 920 if not cl.GetIssue(): |
| 839 cl.SetIssue(issue) | 921 cl.SetIssue(issue) |
| 840 cl.SetPatchset(patchset) | 922 cl.SetPatchset(patchset) |
| 841 return 0 | 923 return 0 |
| 842 | 924 |
| 843 | 925 |
| 844 def SendUpstream(parser, args, cmd): | 926 def SendUpstream(parser, args, cmd): |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 933 | 1015 |
| 934 description += "\n\nReview URL: %s" % cl.GetIssueURL() | 1016 description += "\n\nReview URL: %s" % cl.GetIssueURL() |
| 935 else: | 1017 else: |
| 936 if not description: | 1018 if not description: |
| 937 # Submitting TBR. See if there's already a description in Rietveld, else | 1019 # Submitting TBR. See if there's already a description in Rietveld, else |
| 938 # create a template description. Eitherway, give the user a chance to edit | 1020 # create a template description. Eitherway, give the user a chance to edit |
| 939 # it to fill in the TBR= field. | 1021 # it to fill in the TBR= field. |
| 940 if cl.GetIssue(): | 1022 if cl.GetIssue(): |
| 941 description = cl.GetDescription() | 1023 description = cl.GetDescription() |
| 942 | 1024 |
| 1025 # TODO(dpranke): Update to use ChangeDescription object. | |
| 943 if not description: | 1026 if not description: |
| 944 description = """# Enter a description of the change. | 1027 description = """# Enter a description of the change. |
| 945 # This will be used as the change log for the commit. | 1028 # This will be used as the change log for the commit. |
| 946 | 1029 |
| 947 """ | 1030 """ |
| 948 description += CreateDescriptionFromLog(args) | 1031 description += CreateDescriptionFromLog(args) |
| 949 | 1032 |
| 950 description = UserEditedLog(description + '\nTBR=') | 1033 description = UserEditedLog(description + '\nTBR=') |
| 951 | 1034 |
| 952 if not description: | 1035 if not description: |
| (...skipping 326 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1279 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 1362 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
| 1280 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1363 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
| 1281 | 1364 |
| 1282 # Not a known command. Default to help. | 1365 # Not a known command. Default to help. |
| 1283 GenUsage(parser, 'help') | 1366 GenUsage(parser, 'help') |
| 1284 return CMDhelp(parser, argv) | 1367 return CMDhelp(parser, argv) |
| 1285 | 1368 |
| 1286 | 1369 |
| 1287 if __name__ == '__main__': | 1370 if __name__ == '__main__': |
| 1288 sys.exit(main(sys.argv[1:])) | 1371 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |