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.""" | 8 """A git-command for integrating reviews on Rietveld.""" |
9 | 9 |
10 import difflib | |
11 from distutils.version import LooseVersion | 10 from distutils.version import LooseVersion |
12 import json | 11 import json |
13 import logging | 12 import logging |
14 import optparse | 13 import optparse |
15 import os | 14 import os |
16 import Queue | 15 import Queue |
17 import re | 16 import re |
18 import stat | 17 import stat |
19 import sys | 18 import sys |
20 import textwrap | 19 import textwrap |
21 import threading | 20 import threading |
22 import urllib2 | 21 import urllib2 |
23 import urlparse | 22 import urlparse |
24 | 23 |
25 try: | 24 try: |
26 import readline # pylint: disable=F0401,W0611 | 25 import readline # pylint: disable=F0401,W0611 |
27 except ImportError: | 26 except ImportError: |
28 pass | 27 pass |
29 | 28 |
30 | 29 |
31 from third_party import colorama | 30 from third_party import colorama |
32 from third_party import upload | 31 from third_party import upload |
33 import breakpad # pylint: disable=W0611 | 32 import breakpad # pylint: disable=W0611 |
34 import fix_encoding | 33 import fix_encoding |
35 import gclient_utils | 34 import gclient_utils |
36 import presubmit_support | 35 import presubmit_support |
37 import rietveld | 36 import rietveld |
38 import scm | 37 import scm |
| 38 import subcommand |
39 import subprocess2 | 39 import subprocess2 |
40 import watchlists | 40 import watchlists |
41 | 41 |
| 42 __version__ = '1.0' |
42 | 43 |
43 DEFAULT_SERVER = 'https://codereview.appspot.com' | 44 DEFAULT_SERVER = 'https://codereview.appspot.com' |
44 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' | 45 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' |
45 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' | 46 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' |
46 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingNewGit' | 47 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingNewGit' |
47 CHANGE_ID = 'Change-Id:' | 48 CHANGE_ID = 'Change-Id:' |
48 | 49 |
49 # Shortcut since it quickly becomes redundant. | 50 # Shortcut since it quickly becomes redundant. |
50 Fore = colorama.Fore | 51 Fore = colorama.Fore |
51 | 52 |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
91 return 1, '' | 92 return 1, '' |
92 | 93 |
93 | 94 |
94 def IsGitVersionAtLeast(min_version): | 95 def IsGitVersionAtLeast(min_version): |
95 prefix = 'git version ' | 96 prefix = 'git version ' |
96 version = RunGit(['--version']).strip() | 97 version = RunGit(['--version']).strip() |
97 return (version.startswith(prefix) and | 98 return (version.startswith(prefix) and |
98 LooseVersion(version[len(prefix):]) >= LooseVersion(min_version)) | 99 LooseVersion(version[len(prefix):]) >= LooseVersion(min_version)) |
99 | 100 |
100 | 101 |
101 def usage(more): | |
102 def hook(fn): | |
103 fn.usage_more = more | |
104 return fn | |
105 return hook | |
106 | |
107 | |
108 def ask_for_data(prompt): | 102 def ask_for_data(prompt): |
109 try: | 103 try: |
110 return raw_input(prompt) | 104 return raw_input(prompt) |
111 except KeyboardInterrupt: | 105 except KeyboardInterrupt: |
112 # Hide the exception. | 106 # Hide the exception. |
113 sys.exit(1) | 107 sys.exit(1) |
114 | 108 |
115 | 109 |
116 def git_set_branch_value(key, value): | 110 def git_set_branch_value(key, value): |
117 branch = Changelist().GetBranch() | 111 branch = Changelist().GetBranch() |
(...skipping 906 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1024 os.remove(dst) | 1018 os.remove(dst) |
1025 try: | 1019 try: |
1026 urlretrieve(src, dst) | 1020 urlretrieve(src, dst) |
1027 os.chmod(dst, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) | 1021 os.chmod(dst, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) |
1028 except Exception: | 1022 except Exception: |
1029 if os.path.exists(dst): | 1023 if os.path.exists(dst): |
1030 os.remove(dst) | 1024 os.remove(dst) |
1031 DieWithError('\nFailed to download hooks from %s' % src) | 1025 DieWithError('\nFailed to download hooks from %s' % src) |
1032 | 1026 |
1033 | 1027 |
1034 @usage('[repo root containing codereview.settings]') | 1028 @subcommand.usage('[repo root containing codereview.settings]') |
1035 def CMDconfig(parser, args): | 1029 def CMDconfig(parser, args): |
1036 """Edits configuration for this tree.""" | 1030 """Edits configuration for this tree.""" |
1037 | 1031 |
1038 _, args = parser.parse_args(args) | 1032 _, args = parser.parse_args(args) |
1039 if len(args) == 0: | 1033 if len(args) == 0: |
1040 GetCodereviewSettingsInteractively() | 1034 GetCodereviewSettingsInteractively() |
1041 DownloadHooks(True) | 1035 DownloadHooks(True) |
1042 return 0 | 1036 return 0 |
1043 | 1037 |
1044 url = args[0] | 1038 url = args[0] |
(...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1182 if not cl.GetIssue(): | 1176 if not cl.GetIssue(): |
1183 print 'no issue assigned.' | 1177 print 'no issue assigned.' |
1184 return 0 | 1178 return 0 |
1185 print cl.GetBranch() | 1179 print cl.GetBranch() |
1186 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) | 1180 print 'Issue number: %s (%s)' % (cl.GetIssue(), cl.GetIssueURL()) |
1187 print 'Issue description:' | 1181 print 'Issue description:' |
1188 print cl.GetDescription(pretty=True) | 1182 print cl.GetDescription(pretty=True) |
1189 return 0 | 1183 return 0 |
1190 | 1184 |
1191 | 1185 |
1192 @usage('[issue_number]') | 1186 @subcommand.usage('[issue_number]') |
1193 def CMDissue(parser, args): | 1187 def CMDissue(parser, args): |
1194 """Sets or displays the current code review issue number. | 1188 """Sets or displays the current code review issue number. |
1195 | 1189 |
1196 Pass issue number 0 to clear the current issue. | 1190 Pass issue number 0 to clear the current issue. |
1197 """ | 1191 """ |
1198 _, args = parser.parse_args(args) | 1192 _, args = parser.parse_args(args) |
1199 | 1193 |
1200 cl = Changelist() | 1194 cl = Changelist() |
1201 if len(args) > 0: | 1195 if len(args) > 0: |
1202 try: | 1196 try: |
(...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1441 """Fixes a list so that comma separated items are put as individual items. | 1435 """Fixes a list so that comma separated items are put as individual items. |
1442 | 1436 |
1443 So that "--reviewers joe@c,john@c --reviewers joa@c" results in | 1437 So that "--reviewers joe@c,john@c --reviewers joa@c" results in |
1444 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']). | 1438 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']). |
1445 """ | 1439 """ |
1446 items = sum((i.split(',') for i in l), []) | 1440 items = sum((i.split(',') for i in l), []) |
1447 stripped_items = (i.strip() for i in items) | 1441 stripped_items = (i.strip() for i in items) |
1448 return sorted(filter(None, stripped_items)) | 1442 return sorted(filter(None, stripped_items)) |
1449 | 1443 |
1450 | 1444 |
1451 @usage('[args to "git diff"]') | 1445 @subcommand.usage('[args to "git diff"]') |
1452 def CMDupload(parser, args): | 1446 def CMDupload(parser, args): |
1453 """Uploads the current changelist to codereview.""" | 1447 """Uploads the current changelist to codereview.""" |
1454 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', | 1448 parser.add_option('--bypass-hooks', action='store_true', dest='bypass_hooks', |
1455 help='bypass upload presubmit hook') | 1449 help='bypass upload presubmit hook') |
1456 parser.add_option('--bypass-watchlists', action='store_true', | 1450 parser.add_option('--bypass-watchlists', action='store_true', |
1457 dest='bypass_watchlists', | 1451 dest='bypass_watchlists', |
1458 help='bypass watchlists auto CC-ing reviewers') | 1452 help='bypass watchlists auto CC-ing reviewers') |
1459 parser.add_option('-f', action='store_true', dest='force', | 1453 parser.add_option('-f', action='store_true', dest='force', |
1460 help="force yes to questions (don't prompt)") | 1454 help="force yes to questions (don't prompt)") |
1461 parser.add_option('-m', dest='message', help='message for patchset') | 1455 parser.add_option('-m', dest='message', help='message for patchset') |
(...skipping 302 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1764 cl.SetIssue(None) | 1758 cl.SetIssue(None) |
1765 | 1759 |
1766 if retcode == 0: | 1760 if retcode == 0: |
1767 hook = POSTUPSTREAM_HOOK_PATTERN % cmd | 1761 hook = POSTUPSTREAM_HOOK_PATTERN % cmd |
1768 if os.path.isfile(hook): | 1762 if os.path.isfile(hook): |
1769 RunCommand([hook, base_branch], error_ok=True) | 1763 RunCommand([hook, base_branch], error_ok=True) |
1770 | 1764 |
1771 return 0 | 1765 return 0 |
1772 | 1766 |
1773 | 1767 |
1774 @usage('[upstream branch to apply against]') | 1768 @subcommand.usage('[upstream branch to apply against]') |
1775 def CMDdcommit(parser, args): | 1769 def CMDdcommit(parser, args): |
1776 """Commits the current changelist via git-svn.""" | 1770 """Commits the current changelist via git-svn.""" |
1777 if not settings.GetIsGitSvn(): | 1771 if not settings.GetIsGitSvn(): |
1778 message = """This doesn't appear to be an SVN repository. | 1772 message = """This doesn't appear to be an SVN repository. |
1779 If your project has a git mirror with an upstream SVN master, you probably need | 1773 If your project has a git mirror with an upstream SVN master, you probably need |
1780 to run 'git svn init', see your project's git mirror documentation. | 1774 to run 'git svn init', see your project's git mirror documentation. |
1781 If your project has a true writeable upstream repository, you probably want | 1775 If your project has a true writeable upstream repository, you probably want |
1782 to run 'git cl push' instead. | 1776 to run 'git cl push' instead. |
1783 Choose wisely, if you get this wrong, your commit might appear to succeed but | 1777 Choose wisely, if you get this wrong, your commit might appear to succeed but |
1784 will instead be silently ignored.""" | 1778 will instead be silently ignored.""" |
1785 print(message) | 1779 print(message) |
1786 ask_for_data('[Press enter to dcommit or ctrl-C to quit]') | 1780 ask_for_data('[Press enter to dcommit or ctrl-C to quit]') |
1787 return SendUpstream(parser, args, 'dcommit') | 1781 return SendUpstream(parser, args, 'dcommit') |
1788 | 1782 |
1789 | 1783 |
1790 @usage('[upstream branch to apply against]') | 1784 @subcommand.usage('[upstream branch to apply against]') |
1791 def CMDpush(parser, args): | 1785 def CMDpush(parser, args): |
1792 """Commits the current changelist via git.""" | 1786 """Commits the current changelist via git.""" |
1793 if settings.GetIsGitSvn(): | 1787 if settings.GetIsGitSvn(): |
1794 print('This appears to be an SVN repository.') | 1788 print('This appears to be an SVN repository.') |
1795 print('Are you sure you didn\'t mean \'git cl dcommit\'?') | 1789 print('Are you sure you didn\'t mean \'git cl dcommit\'?') |
1796 ask_for_data('[Press enter to push or ctrl-C to quit]') | 1790 ask_for_data('[Press enter to push or ctrl-C to quit]') |
1797 return SendUpstream(parser, args, 'push') | 1791 return SendUpstream(parser, args, 'push') |
1798 | 1792 |
1799 | 1793 |
1800 @usage('<patch url or issue id>') | 1794 @subcommand.usage('<patch url or issue id>') |
1801 def CMDpatch(parser, args): | 1795 def CMDpatch(parser, args): |
1802 """Patchs in a code review.""" | 1796 """Patchs in a code review.""" |
1803 parser.add_option('-b', dest='newbranch', | 1797 parser.add_option('-b', dest='newbranch', |
1804 help='create a new branch off trunk for the patch') | 1798 help='create a new branch off trunk for the patch') |
1805 parser.add_option('-f', action='store_true', dest='force', | 1799 parser.add_option('-f', action='store_true', dest='force', |
1806 help='with -b, clobber any existing branch') | 1800 help='with -b, clobber any existing branch') |
1807 parser.add_option('--reject', action='store_true', dest='reject', | 1801 parser.add_option('--reject', action='store_true', dest='reject', |
1808 help='failed patches spew .rej files rather than ' | 1802 help='failed patches spew .rej files rather than ' |
1809 'attempting a 3-way merge') | 1803 'attempting a 3-way merge') |
1810 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit', | 1804 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit', |
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2037 cl.RpcServer().trigger_try_jobs( | 2031 cl.RpcServer().trigger_try_jobs( |
2038 cl.GetIssue(), patchset, options.name, options.clobber, options.revision, | 2032 cl.GetIssue(), patchset, options.name, options.clobber, options.revision, |
2039 builders_and_tests) | 2033 builders_and_tests) |
2040 print('Tried jobs on:') | 2034 print('Tried jobs on:') |
2041 length = max(len(builder) for builder in builders_and_tests) | 2035 length = max(len(builder) for builder in builders_and_tests) |
2042 for builder in sorted(builders_and_tests): | 2036 for builder in sorted(builders_and_tests): |
2043 print ' %*s: %s' % (length, builder, ','.join(builders_and_tests[builder])) | 2037 print ' %*s: %s' % (length, builder, ','.join(builders_and_tests[builder])) |
2044 return 0 | 2038 return 0 |
2045 | 2039 |
2046 | 2040 |
2047 @usage('[new upstream branch]') | 2041 @subcommand.usage('[new upstream branch]') |
2048 def CMDupstream(parser, args): | 2042 def CMDupstream(parser, args): |
2049 """Prints or sets the name of the upstream branch, if any.""" | 2043 """Prints or sets the name of the upstream branch, if any.""" |
2050 _, args = parser.parse_args(args) | 2044 _, args = parser.parse_args(args) |
2051 if len(args) > 1: | 2045 if len(args) > 1: |
2052 parser.error('Unrecognized args: %s' % ' '.join(args)) | 2046 parser.error('Unrecognized args: %s' % ' '.join(args)) |
2053 return 0 | 2047 return 0 |
2054 | 2048 |
2055 cl = Changelist() | 2049 cl = Changelist() |
2056 if args: | 2050 if args: |
2057 # One arg means set upstream branch. | 2051 # One arg means set upstream branch. |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2139 cfd_path = os.path.join('/usr', 'lib', 'clang-format', | 2133 cfd_path = os.path.join('/usr', 'lib', 'clang-format', |
2140 'clang-format-diff.py') | 2134 'clang-format-diff.py') |
2141 if not os.path.exists(cfd_path): | 2135 if not os.path.exists(cfd_path): |
2142 DieWithError('Could not find clang-format-diff at %s.' % cfd_path) | 2136 DieWithError('Could not find clang-format-diff at %s.' % cfd_path) |
2143 cmd = [sys.executable, cfd_path, '-style', 'Chromium'] | 2137 cmd = [sys.executable, cfd_path, '-style', 'Chromium'] |
2144 RunCommand(cmd, stdin=diff_output, cwd=top_dir) | 2138 RunCommand(cmd, stdin=diff_output, cwd=top_dir) |
2145 | 2139 |
2146 return 0 | 2140 return 0 |
2147 | 2141 |
2148 | 2142 |
2149 ### Glue code for subcommand handling. | |
2150 | |
2151 | |
2152 def Commands(): | |
2153 """Returns a dict of command and their handling function.""" | |
2154 module = sys.modules[__name__] | |
2155 cmds = (fn[3:] for fn in dir(module) if fn.startswith('CMD')) | |
2156 return dict((cmd, getattr(module, 'CMD' + cmd)) for cmd in cmds) | |
2157 | |
2158 | |
2159 def Command(name): | |
2160 """Retrieves the function to handle a command.""" | |
2161 commands = Commands() | |
2162 if name in commands: | |
2163 return commands[name] | |
2164 | |
2165 # Try to be smart and look if there's something similar. | |
2166 commands_with_prefix = [c for c in commands if c.startswith(name)] | |
2167 if len(commands_with_prefix) == 1: | |
2168 return commands[commands_with_prefix[0]] | |
2169 | |
2170 # A #closeenough approximation of levenshtein distance. | |
2171 def close_enough(a, b): | |
2172 return difflib.SequenceMatcher(a=a, b=b).ratio() | |
2173 | |
2174 hamming_commands = sorted( | |
2175 ((close_enough(c, name), c) for c in commands), | |
2176 reverse=True) | |
2177 if (hamming_commands[0][0] - hamming_commands[1][0]) < 0.3: | |
2178 # Too ambiguous. | |
2179 return | |
2180 | |
2181 if hamming_commands[0][0] < 0.8: | |
2182 # Not similar enough. Don't be a fool and run a random command. | |
2183 return | |
2184 | |
2185 return commands[hamming_commands[0][1]] | |
2186 | |
2187 | |
2188 def CMDhelp(parser, args): | |
2189 """Prints list of commands or help for a specific command.""" | |
2190 _, args = parser.parse_args(args) | |
2191 if len(args) == 1: | |
2192 return main(args + ['--help']) | |
2193 parser.print_help() | |
2194 return 0 | |
2195 | |
2196 | |
2197 def GenUsage(parser, command): | |
2198 """Modify an OptParse object with the function's documentation.""" | |
2199 obj = Command(command) | |
2200 # Get back the real command name in case Command() guess the actual command | |
2201 # name. | |
2202 command = obj.__name__[3:] | |
2203 more = getattr(obj, 'usage_more', '') | |
2204 if command == 'help': | |
2205 command = '<command>' | |
2206 else: | |
2207 parser.description = obj.__doc__ | |
2208 parser.set_usage('usage: %%prog %s [options] %s' % (command, more)) | |
2209 | |
2210 | |
2211 class OptionParser(optparse.OptionParser): | 2143 class OptionParser(optparse.OptionParser): |
2212 """Creates the option parse and add --verbose support.""" | 2144 """Creates the option parse and add --verbose support.""" |
2213 def __init__(self, *args, **kwargs): | 2145 def __init__(self, *args, **kwargs): |
2214 optparse.OptionParser.__init__(self, *args, **kwargs) | 2146 optparse.OptionParser.__init__( |
| 2147 self, *args, prog='git cl', version=__version__, **kwargs) |
2215 self.add_option( | 2148 self.add_option( |
2216 '-v', '--verbose', action='count', default=0, | 2149 '-v', '--verbose', action='count', default=0, |
2217 help='Use 2 times for more debugging info') | 2150 help='Use 2 times for more debugging info') |
2218 | 2151 |
2219 def parse_args(self, args=None, values=None): | 2152 def parse_args(self, args=None, values=None): |
2220 options, args = optparse.OptionParser.parse_args(self, args, values) | 2153 options, args = optparse.OptionParser.parse_args(self, args, values) |
2221 levels = [logging.WARNING, logging.INFO, logging.DEBUG] | 2154 levels = [logging.WARNING, logging.INFO, logging.DEBUG] |
2222 logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)]) | 2155 logging.basicConfig(level=levels[min(options.verbose, len(levels) - 1)]) |
2223 return options, args | 2156 return options, args |
2224 | 2157 |
2225 def format_description(self, _): | 2158 def format_description(self, _): |
2226 """Disables automatic reformatting.""" | 2159 """Disables automatic reformatting.""" |
2227 lines = self.description.rstrip().splitlines() | 2160 lines = self.description.rstrip().splitlines() |
2228 lines_fixed = [lines[0]] + [l[2:] if len(l) >= 2 else l for l in lines[1:]] | 2161 lines_fixed = [lines[0]] + [l[2:] if len(l) >= 2 else l for l in lines[1:]] |
2229 description = ''.join(l + '\n' for l in lines_fixed) | 2162 description = ''.join(l + '\n' for l in lines_fixed) |
2230 return description[0].upper() + description[1:] | 2163 return description[0].upper() + description[1:] |
2231 | 2164 |
2232 | 2165 |
2233 def main(argv): | 2166 def main(argv): |
2234 """Doesn't parse the arguments here, just find the right subcommand to | |
2235 execute.""" | |
2236 if sys.hexversion < 0x02060000: | 2167 if sys.hexversion < 0x02060000: |
2237 print >> sys.stderr, ( | 2168 print >> sys.stderr, ( |
2238 '\nYour python version %s is unsupported, please upgrade.\n' % | 2169 '\nYour python version %s is unsupported, please upgrade.\n' % |
2239 sys.version.split(' ', 1)[0]) | 2170 sys.version.split(' ', 1)[0]) |
2240 return 2 | 2171 return 2 |
2241 | 2172 |
2242 # Reload settings. | 2173 # Reload settings. |
2243 global settings | 2174 global settings |
2244 settings = Settings() | 2175 settings = Settings() |
2245 | 2176 |
2246 # Do it late so all commands are listed. | 2177 dispatcher = subcommand.CommandDispatcher(__name__) |
2247 commands = Commands() | 2178 try: |
2248 length = max(len(c) for c in commands) | 2179 return dispatcher.execute(OptionParser(), argv) |
2249 | 2180 except urllib2.HTTPError, e: |
2250 def gen_summary(x): | 2181 if e.code != 500: |
2251 """Creates a oneline summary from the docstring.""" | 2182 raise |
2252 line = x.split('\n', 1)[0].rstrip('.') | 2183 DieWithError( |
2253 return line[0].lower() + line[1:] | 2184 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
2254 | 2185 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
2255 docs = sorted( | |
2256 (name, gen_summary(handler.__doc__).strip()) | |
2257 for name, handler in commands.iteritems()) | |
2258 CMDhelp.usage_more = ('\n\nCommands are:\n' + '\n'.join( | |
2259 ' %-*s %s' % (length, name, doc) for name, doc in docs)) | |
2260 | |
2261 parser = OptionParser() | |
2262 if argv: | |
2263 command = Command(argv[0]) | |
2264 if command: | |
2265 # "fix" the usage and the description now that we know the subcommand. | |
2266 GenUsage(parser, argv[0]) | |
2267 try: | |
2268 return command(parser, argv[1:]) | |
2269 except urllib2.HTTPError, e: | |
2270 if e.code != 500: | |
2271 raise | |
2272 DieWithError( | |
2273 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | |
2274 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | |
2275 | |
2276 # Not a known command. Default to help. | |
2277 GenUsage(parser, 'help') | |
2278 return CMDhelp(parser, argv) | |
2279 | 2186 |
2280 | 2187 |
2281 if __name__ == '__main__': | 2188 if __name__ == '__main__': |
2282 # These affect sys.stdout so do it outside of main() to simplify mocks in | 2189 # These affect sys.stdout so do it outside of main() to simplify mocks in |
2283 # unit testing. | 2190 # unit testing. |
2284 fix_encoding.fix_encoding() | 2191 fix_encoding.fix_encoding() |
2285 colorama.init() | 2192 colorama.init() |
2286 sys.exit(main(sys.argv[1:])) | 2193 sys.exit(main(sys.argv[1:])) |
OLD | NEW |