| 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 |
| 11 import sys | 11 import sys |
| 12 import tempfile | |
| 13 import textwrap | 12 import textwrap |
| 14 import urlparse | 13 import urlparse |
| 15 import urllib2 | 14 import urllib2 |
| 16 | 15 |
| 17 try: | 16 try: |
| 18 import readline # pylint: disable=W0611 | 17 import readline # pylint: disable=W0611 |
| 19 except ImportError: | 18 except ImportError: |
| 20 pass | 19 pass |
| 21 | 20 |
| 22 # TODO(dpranke): don't use relative import. | 21 # TODO(dpranke): don't use relative import. |
| 23 import upload # pylint: disable=W0403 | 22 import upload # pylint: disable=W0403 |
| 24 try: | 23 |
| 25 # TODO(dpranke): We wrap this in a try block for a limited form of | 24 # TODO(dpranke): move this file up a directory so we don't need this. |
| 26 # backwards-compatibility with older versions of git-cl that weren't | 25 depot_tools_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 27 # dependent on depot_tools. This version should still work outside of | 26 sys.path.append(depot_tools_path) |
| 28 # depot_tools as long as --bypass-hooks is used. We should remove this | 27 |
| 29 # once this has baked for a while and things seem safe. | 28 import breakpad # pylint: disable=W0611 |
| 30 depot_tools_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | 29 |
| 31 sys.path.append(depot_tools_path) | 30 import presubmit_support |
| 32 import breakpad # pylint: disable=W0611 | 31 import scm |
| 33 except ImportError: | 32 import watchlists |
| 34 pass | 33 |
| 35 | 34 |
| 36 DEFAULT_SERVER = 'http://codereview.appspot.com' | 35 DEFAULT_SERVER = 'http://codereview.appspot.com' |
| 37 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' | 36 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' |
| 38 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' | 37 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' |
| 39 | 38 |
| 40 def DieWithError(message): | 39 def DieWithError(message): |
| 41 print >> sys.stderr, message | 40 print >> sys.stderr, message |
| 42 sys.exit(1) | 41 sys.exit(1) |
| 43 | 42 |
| 44 | 43 |
| (...skipping 281 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 326 else: | 325 else: |
| 327 self.branch = None | 326 self.branch = None |
| 328 self.rietveld_server = None | 327 self.rietveld_server = None |
| 329 self.upstream_branch = None | 328 self.upstream_branch = None |
| 330 self.has_issue = False | 329 self.has_issue = False |
| 331 self.issue = None | 330 self.issue = None |
| 332 self.has_description = False | 331 self.has_description = False |
| 333 self.description = None | 332 self.description = None |
| 334 self.has_patchset = False | 333 self.has_patchset = False |
| 335 self.patchset = None | 334 self.patchset = None |
| 335 self.tbr = False |
| 336 | 336 |
| 337 def GetBranch(self): | 337 def GetBranch(self): |
| 338 """Returns the short branch name, e.g. 'master'.""" | 338 """Returns the short branch name, e.g. 'master'.""" |
| 339 if not self.branch: | 339 if not self.branch: |
| 340 self.branchref = RunGit(['symbolic-ref', 'HEAD']).strip() | 340 self.branchref = RunGit(['symbolic-ref', 'HEAD']).strip() |
| 341 self.branch = ShortBranchName(self.branchref) | 341 self.branch = ShortBranchName(self.branchref) |
| 342 return self.branch | 342 return self.branch |
| 343 | 343 |
| 344 def GetBranchRef(self): | 344 def GetBranchRef(self): |
| 345 """Returns the full branch name, e.g. 'refs/heads/master'.""" | 345 """Returns the full branch name, e.g. 'refs/heads/master'.""" |
| (...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 528 | 528 |
| 529 SetProperty(settings.GetCCList(), 'CC list', 'cc') | 529 SetProperty(settings.GetCCList(), 'CC list', 'cc') |
| 530 SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL', | 530 SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL', |
| 531 'tree-status-url') | 531 'tree-status-url') |
| 532 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url') | 532 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url') |
| 533 | 533 |
| 534 # TODO: configure a default branch to diff against, rather than this | 534 # TODO: configure a default branch to diff against, rather than this |
| 535 # svn-based hackery. | 535 # svn-based hackery. |
| 536 | 536 |
| 537 | 537 |
| 538 class ChangeDescription(object): | |
| 539 """Contains a parsed form of the change description.""" | |
| 540 def __init__(self, subject, log_desc, reviewers): | |
| 541 self.subject = subject | |
| 542 self.log_desc = log_desc | |
| 543 self.reviewers = reviewers | |
| 544 self.description = self.log_desc | |
| 545 | |
| 546 def Update(self): | |
| 547 initial_text = """# Enter a description of the change. | |
| 548 # This will displayed on the codereview site. | |
| 549 # The first line will also be used as the subject of the review. | |
| 550 """ | |
| 551 initial_text += self.description | |
| 552 if 'R=' not in self.description and self.reviewers: | |
| 553 initial_text += '\nR=' + self.reviewers | |
| 554 if 'BUG=' not in self.description: | |
| 555 initial_text += '\nBUG=' | |
| 556 if 'TEST=' not in self.description: | |
| 557 initial_text += '\nTEST=' | |
| 558 self._ParseDescription(UserEditedLog(initial_text)) | |
| 559 | |
| 560 def _ParseDescription(self, description): | |
| 561 if not description: | |
| 562 self.description = description | |
| 563 return | |
| 564 | |
| 565 parsed_lines = [] | |
| 566 reviewers_regexp = re.compile('\s*R=(.+)') | |
| 567 reviewers = '' | |
| 568 subject = '' | |
| 569 for l in description.splitlines(): | |
| 570 if not subject: | |
| 571 subject = l | |
| 572 matched_reviewers = reviewers_regexp.match(l) | |
| 573 if matched_reviewers: | |
| 574 reviewers = matched_reviewers.group(1) | |
| 575 parsed_lines.append(l) | |
| 576 | |
| 577 self.description = '\n'.join(parsed_lines) + '\n' | |
| 578 self.subject = subject | |
| 579 self.reviewers = reviewers | |
| 580 | |
| 581 def IsEmpty(self): | |
| 582 return not self.description | |
| 583 | |
| 584 | |
| 585 def FindCodereviewSettingsFile(filename='codereview.settings'): | 538 def FindCodereviewSettingsFile(filename='codereview.settings'): |
| 586 """Finds the given file starting in the cwd and going up. | 539 """Finds the given file starting in the cwd and going up. |
| 587 | 540 |
| 588 Only looks up to the top of the repository unless an | 541 Only looks up to the top of the repository unless an |
| 589 'inherit-review-settings-ok' file exists in the root of the repository. | 542 'inherit-review-settings-ok' file exists in the root of the repository. |
| 590 """ | 543 """ |
| 591 inherit_ok_file = 'inherit-review-settings-ok' | 544 inherit_ok_file = 'inherit-review-settings-ok' |
| 592 cwd = os.getcwd() | 545 cwd = os.getcwd() |
| 593 root = os.path.abspath(RunGit(['rev-parse', '--show-cdup']).strip()) | 546 root = os.path.abspath(RunGit(['rev-parse', '--show-cdup']).strip()) |
| 594 if os.path.isfile(os.path.join(root, inherit_ok_file)): | 547 if os.path.isfile(os.path.join(root, inherit_ok_file)): |
| (...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 724 log_args = [args[0] + '..'] | 677 log_args = [args[0] + '..'] |
| 725 elif len(args) == 1 and args[0].endswith('...'): | 678 elif len(args) == 1 and args[0].endswith('...'): |
| 726 log_args = [args[0][:-1]] | 679 log_args = [args[0][:-1]] |
| 727 elif len(args) == 2: | 680 elif len(args) == 2: |
| 728 log_args = [args[0] + '..' + args[1]] | 681 log_args = [args[0] + '..' + args[1]] |
| 729 else: | 682 else: |
| 730 log_args = args[:] # Hope for the best! | 683 log_args = args[:] # Hope for the best! |
| 731 return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args) | 684 return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args) |
| 732 | 685 |
| 733 | 686 |
| 734 def UserEditedLog(starting_text): | |
| 735 """Given some starting text, let the user edit it and return the result.""" | |
| 736 editor = os.getenv('EDITOR', 'vi') | |
| 737 | |
| 738 (file_handle, filename) = tempfile.mkstemp() | |
| 739 fileobj = os.fdopen(file_handle, 'w') | |
| 740 fileobj.write(starting_text) | |
| 741 fileobj.close() | |
| 742 | |
| 743 # Open up the default editor in the system to get the CL description. | |
| 744 try: | |
| 745 cmd = '%s %s' % (editor, filename) | |
| 746 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys': | |
| 747 # Msysgit requires the usage of 'env' to be present. | |
| 748 cmd = 'env ' + cmd | |
| 749 # shell=True to allow the shell to handle all forms of quotes in $EDITOR. | |
| 750 subprocess.check_call(cmd, shell=True) | |
| 751 fileobj = open(filename) | |
| 752 text = fileobj.read() | |
| 753 fileobj.close() | |
| 754 finally: | |
| 755 os.remove(filename) | |
| 756 | |
| 757 if not text: | |
| 758 return | |
| 759 | |
| 760 stripcomment_re = re.compile(r'^#.*$', re.MULTILINE) | |
| 761 return stripcomment_re.sub('', text).strip() | |
| 762 | |
| 763 | |
| 764 def ConvertToInteger(inputval): | 687 def ConvertToInteger(inputval): |
| 765 """Convert a string to integer, but returns either an int or None.""" | 688 """Convert a string to integer, but returns either an int or None.""" |
| 766 try: | 689 try: |
| 767 return int(inputval) | 690 return int(inputval) |
| 768 except (TypeError, ValueError): | 691 except (TypeError, ValueError): |
| 769 return None | 692 return None |
| 770 | 693 |
| 771 | 694 |
| 695 class GitChangeDescription(presubmit_support.ChangeDescription): |
| 696 def UserEdit(self): |
| 697 header = ( |
| 698 "# Enter a description of the change.\n" |
| 699 "# This will displayed on the codereview site.\n" |
| 700 "# The first line will also be used as the subject of the review.\n" |
| 701 "\n") |
| 702 edited_text = self.editor(header + self.EditableDescription()) |
| 703 stripcomment_re = re.compile(r'^#.*$', re.MULTILINE) |
| 704 self.Parse(stripcomment_re.sub('', edited_text).strip()) |
| 705 |
| 706 |
| 772 def RunHook(committing, upstream_branch, rietveld_server, tbr, may_prompt): | 707 def RunHook(committing, upstream_branch, rietveld_server, tbr, may_prompt): |
| 773 """Calls sys.exit() if the hook fails; returns a HookResults otherwise.""" | 708 """Calls sys.exit() if the hook fails; returns a HookResults otherwise.""" |
| 774 import presubmit_support | |
| 775 import scm | |
| 776 import watchlists | |
| 777 | |
| 778 root = RunCommand(['git', 'rev-parse', '--show-cdup']).strip() | 709 root = RunCommand(['git', 'rev-parse', '--show-cdup']).strip() |
| 779 if not root: | 710 if not root: |
| 780 root = '.' | 711 root = '.' |
| 781 absroot = os.path.abspath(root) | 712 absroot = os.path.abspath(root) |
| 782 if not root: | 713 if not root: |
| 783 raise Exception('Could not get root directory.') | 714 raise Exception('Could not get root directory.') |
| 784 | 715 |
| 785 # We use the sha1 of HEAD as a name of this change. | 716 # We use the sha1 of HEAD as a name of this change. |
| 786 name = RunCommand(['git', 'rev-parse', 'HEAD']).strip() | 717 name = RunCommand(['git', 'rev-parse', 'HEAD']).strip() |
| 787 files = scm.GIT.CaptureStatus([root], upstream_branch) | 718 files = scm.GIT.CaptureStatus([root], upstream_branch) |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 836 cl = Changelist() | 767 cl = Changelist() |
| 837 if args: | 768 if args: |
| 838 base_branch = args[0] | 769 base_branch = args[0] |
| 839 else: | 770 else: |
| 840 # Default to diffing against the "upstream" branch. | 771 # Default to diffing against the "upstream" branch. |
| 841 base_branch = cl.GetUpstreamBranch() | 772 base_branch = cl.GetUpstreamBranch() |
| 842 | 773 |
| 843 if options.upload: | 774 if options.upload: |
| 844 print '*** Presubmit checks for UPLOAD would report: ***' | 775 print '*** Presubmit checks for UPLOAD would report: ***' |
| 845 RunHook(committing=False, upstream_branch=base_branch, | 776 RunHook(committing=False, upstream_branch=base_branch, |
| 846 rietveld_server=cl.GetRietveldServer(), tbr=False, | 777 rietveld_server=cl.GetRietveldServer(), tbr=cl.tbr, |
| 847 may_prompt=False) | 778 may_prompt=False) |
| 848 return 0 | 779 return 0 |
| 849 else: | 780 else: |
| 850 print '*** Presubmit checks for DCOMMIT would report: ***' | 781 print '*** Presubmit checks for DCOMMIT would report: ***' |
| 851 RunHook(committing=True, upstream_branch=base_branch, | 782 RunHook(committing=True, upstream_branch=base_branch, |
| 852 rietveld_server=cl.GetRietveldServer, tbr=False, | 783 rietveld_server=cl.GetRietveldServer, tbr=cl.tbr, |
| 853 may_prompt=False) | 784 may_prompt=False) |
| 854 return 0 | 785 return 0 |
| 855 | 786 |
| 856 | 787 |
| 857 @usage('[args to "git diff"]') | 788 @usage('[args to "git diff"]') |
| 858 def CMDupload(parser, args): | 789 def CMDupload(parser, args): |
| 859 """upload the current changelist to codereview""" | 790 """upload the current changelist to codereview""" |
| 860 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 791 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
| 861 help='bypass upload presubmit hook') | 792 help='bypass upload presubmit hook') |
| 862 parser.add_option('-f', action='store_true', dest='force', | 793 parser.add_option('-f', action='store_true', dest='force', |
| (...skipping 22 matching lines...) Expand all Loading... |
| 885 | 816 |
| 886 cl = Changelist() | 817 cl = Changelist() |
| 887 if args: | 818 if args: |
| 888 base_branch = args[0] | 819 base_branch = args[0] |
| 889 else: | 820 else: |
| 890 # Default to diffing against the "upstream" branch. | 821 # Default to diffing against the "upstream" branch. |
| 891 base_branch = cl.GetUpstreamBranch() | 822 base_branch = cl.GetUpstreamBranch() |
| 892 args = [base_branch + "..."] | 823 args = [base_branch + "..."] |
| 893 | 824 |
| 894 if not options.bypass_hooks and not options.force: | 825 if not options.bypass_hooks and not options.force: |
| 895 hook_results = RunHook(committing=False, upstream_branch=base_branch, | 826 RunHook(committing=False, upstream_branch=base_branch, |
| 896 rietveld_server=cl.GetRietveldServer(), tbr=False, | 827 rietveld_server=cl.GetRietveldServer(), tbr=cl.tbr, |
| 897 may_prompt=True) | 828 may_prompt=True) |
| 898 if not options.reviewers and hook_results.reviewers: | |
| 899 options.reviewers = hook_results.reviewers | |
| 900 | |
| 901 | 829 |
| 902 # --no-ext-diff is broken in some versions of Git, so try to work around | 830 # --no-ext-diff is broken in some versions of Git, so try to work around |
| 903 # this by overriding the environment (but there is still a problem if the | 831 # this by overriding the environment (but there is still a problem if the |
| 904 # git config key "diff.external" is used). | 832 # git config key "diff.external" is used). |
| 905 env = os.environ.copy() | 833 env = os.environ.copy() |
| 906 if 'GIT_EXTERNAL_DIFF' in env: | 834 if 'GIT_EXTERNAL_DIFF' in env: |
| 907 del env['GIT_EXTERNAL_DIFF'] | 835 del env['GIT_EXTERNAL_DIFF'] |
| 908 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, | 836 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', '-M'] + args, |
| 909 env=env) | 837 env=env) |
| 910 | 838 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 923 change_desc = None | 851 change_desc = None |
| 924 | 852 |
| 925 if cl.GetIssue(): | 853 if cl.GetIssue(): |
| 926 if options.message: | 854 if options.message: |
| 927 upload_args.extend(['--message', options.message]) | 855 upload_args.extend(['--message', options.message]) |
| 928 upload_args.extend(['--issue', cl.GetIssue()]) | 856 upload_args.extend(['--issue', cl.GetIssue()]) |
| 929 print ("This branch is associated with issue %s. " | 857 print ("This branch is associated with issue %s. " |
| 930 "Adding patch to that issue." % cl.GetIssue()) | 858 "Adding patch to that issue." % cl.GetIssue()) |
| 931 else: | 859 else: |
| 932 log_desc = CreateDescriptionFromLog(args) | 860 log_desc = CreateDescriptionFromLog(args) |
| 933 change_desc = ChangeDescription(options.message, log_desc, | 861 change_desc = GitChangeDescription(subject=options.message, |
| 934 options.reviewers) | 862 description=log_desc, reviewers=options.reviewers, tbr=cl.tbr) |
| 935 if not options.from_logs: | 863 if not options.from_logs and (not options.force): |
| 936 change_desc.Update() | 864 change_desc.UserEdit() |
| 937 | 865 |
| 938 if change_desc.IsEmpty(): | 866 if change_desc.IsEmpty(): |
| 939 print "Description is empty; aborting." | 867 print "Description is empty; aborting." |
| 940 return 1 | 868 return 1 |
| 941 | 869 |
| 942 upload_args.extend(['--message', change_desc.subject]) | 870 upload_args.extend(['--message', change_desc.subject]) |
| 943 upload_args.extend(['--description', change_desc.description]) | 871 upload_args.extend(['--description', change_desc.description]) |
| 944 if change_desc.reviewers: | 872 if change_desc.reviewers: |
| 945 upload_args.extend(['--reviewers', change_desc.reviewers]) | 873 upload_args.extend(['--reviewers', change_desc.reviewers]) |
| 946 cc = ','.join(filter(None, (settings.GetCCList(), options.cc))) | 874 cc = ','.join(filter(None, (settings.GetCCList(), options.cc))) |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1037 extra_commits = RunGit(['rev-list', '^' + svn_head, base_branch]) | 965 extra_commits = RunGit(['rev-list', '^' + svn_head, base_branch]) |
| 1038 if extra_commits: | 966 if extra_commits: |
| 1039 print ('This branch has %d additional commits not upstreamed yet.' | 967 print ('This branch has %d additional commits not upstreamed yet.' |
| 1040 % len(extra_commits.splitlines())) | 968 % len(extra_commits.splitlines())) |
| 1041 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' | 969 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' |
| 1042 'before attempting to %s.' % (base_branch, cmd)) | 970 'before attempting to %s.' % (base_branch, cmd)) |
| 1043 return 1 | 971 return 1 |
| 1044 | 972 |
| 1045 if not options.bypass_hooks and not options.force: | 973 if not options.bypass_hooks and not options.force: |
| 1046 RunHook(committing=True, upstream_branch=base_branch, | 974 RunHook(committing=True, upstream_branch=base_branch, |
| 1047 rietveld_server=cl.GetRietveldServer(), tbr=options.tbr, | 975 rietveld_server=cl.GetRietveldServer(), tbr=(cl.tbr or options.tbr), |
| 1048 may_prompt=True) | 976 may_prompt=True) |
| 1049 | 977 |
| 1050 if cmd == 'dcommit': | 978 if cmd == 'dcommit': |
| 1051 # Check the tree status if the tree status URL is set. | 979 # Check the tree status if the tree status URL is set. |
| 1052 status = GetTreeStatus() | 980 status = GetTreeStatus() |
| 1053 if 'closed' == status: | 981 if 'closed' == status: |
| 1054 print ('The tree is closed. Please wait for it to reopen. Use ' | 982 print ('The tree is closed. Please wait for it to reopen. Use ' |
| 1055 '"git cl dcommit -f" to commit on a closed tree.') | 983 '"git cl dcommit -f" to commit on a closed tree.') |
| 1056 return 1 | 984 return 1 |
| 1057 elif 'unknown' == status: | 985 elif 'unknown' == status: |
| (...skipping 18 matching lines...) Expand all Loading... |
| 1076 print 'Visit %s/edit to set it.' % (cl.GetIssueURL()) | 1004 print 'Visit %s/edit to set it.' % (cl.GetIssueURL()) |
| 1077 return 1 | 1005 return 1 |
| 1078 | 1006 |
| 1079 description += "\n\nReview URL: %s" % cl.GetIssueURL() | 1007 description += "\n\nReview URL: %s" % cl.GetIssueURL() |
| 1080 else: | 1008 else: |
| 1081 if not description: | 1009 if not description: |
| 1082 # Submitting TBR. See if there's already a description in Rietveld, else | 1010 # Submitting TBR. See if there's already a description in Rietveld, else |
| 1083 # create a template description. Eitherway, give the user a chance to edit | 1011 # create a template description. Eitherway, give the user a chance to edit |
| 1084 # it to fill in the TBR= field. | 1012 # it to fill in the TBR= field. |
| 1085 if cl.GetIssue(): | 1013 if cl.GetIssue(): |
| 1086 description = cl.GetDescription() | 1014 change_desc = GitChangeDescription(description=cl.GetDescription()) |
| 1087 | 1015 |
| 1088 # TODO(dpranke): Update to use ChangeDescription object. | |
| 1089 if not description: | 1016 if not description: |
| 1090 description = """# Enter a description of the change. | 1017 log_desc = CreateDescriptionFromLog(args) |
| 1091 # This will be used as the change log for the commit. | 1018 change_desc = GitChangeDescription(description=log_desc, tbr=True) |
| 1092 | 1019 |
| 1093 """ | 1020 if not options.force: |
| 1094 description += CreateDescriptionFromLog(args) | 1021 change_desc.UserEdit() |
| 1095 | 1022 description = change_desc.description |
| 1096 description = UserEditedLog(description + '\nTBR=') | |
| 1097 | 1023 |
| 1098 if not description: | 1024 if not description: |
| 1099 print "Description empty; aborting." | 1025 print "Description empty; aborting." |
| 1100 return 1 | 1026 return 1 |
| 1101 | 1027 |
| 1102 if options.contributor: | 1028 if options.contributor: |
| 1103 if not re.match('^.*\s<\S+@\S+>$', options.contributor): | 1029 if not re.match('^.*\s<\S+@\S+>$', options.contributor): |
| 1104 print "Please provide contibutor as 'First Last <email@example.com>'" | 1030 print "Please provide contibutor as 'First Last <email@example.com>'" |
| 1105 return 1 | 1031 return 1 |
| 1106 description += "\nPatch from %s." % options.contributor | 1032 description += "\nPatch from %s." % options.contributor |
| (...skipping 316 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1423 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 1349 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
| 1424 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1350 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
| 1425 | 1351 |
| 1426 # Not a known command. Default to help. | 1352 # Not a known command. Default to help. |
| 1427 GenUsage(parser, 'help') | 1353 GenUsage(parser, 'help') |
| 1428 return CMDhelp(parser, argv) | 1354 return CMDhelp(parser, argv) |
| 1429 | 1355 |
| 1430 | 1356 |
| 1431 if __name__ == '__main__': | 1357 if __name__ == '__main__': |
| 1432 sys.exit(main(sys.argv[1:])) | 1358 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |