OLD | NEW |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2010 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 """\ | 6 """\ |
7 Wrapper script around Rietveld's upload.py that simplifies working with groups | 7 Wrapper script around Rietveld's upload.py that simplifies working with groups |
8 of files. | 8 of files. |
9 """ | 9 """ |
10 | 10 |
(...skipping 17 matching lines...) Expand all Loading... | |
28 import json | 28 import json |
29 # Some versions of python2.5 have an incomplete json module. Check to make | 29 # Some versions of python2.5 have an incomplete json module. Check to make |
30 # sure loads exists. | 30 # sure loads exists. |
31 # pylint: disable=W0104 | 31 # pylint: disable=W0104 |
32 json.loads | 32 json.loads |
33 except (ImportError, AttributeError): | 33 except (ImportError, AttributeError): |
34 # Import the one included in depot_tools. | 34 # Import the one included in depot_tools. |
35 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party')) | 35 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party')) |
36 import simplejson as json # pylint: disable=F0401 | 36 import simplejson as json # pylint: disable=F0401 |
37 | 37 |
38 import breakpad # pylint: disable=W0611 | |
39 | |
40 # gcl now depends on gclient. | 38 # gcl now depends on gclient. |
41 from scm import SVN | 39 from scm import SVN |
40 | |
41 import breakpad # pylint: disable=W0611 | |
M-A Ruel
2011/03/15 19:11:57
breakpad _must_ be the first.
| |
42 import gclient_utils | 42 import gclient_utils |
43 import owners | |
44 import presubmit_support | |
43 | 45 |
44 __version__ = '1.2' | 46 __version__ = '1.2' |
45 | 47 |
46 | 48 |
47 CODEREVIEW_SETTINGS = { | 49 CODEREVIEW_SETTINGS = { |
48 # To make gcl send reviews to a server, check in a file named | 50 # To make gcl send reviews to a server, check in a file named |
49 # "codereview.settings" (see |CODEREVIEW_SETTINGS_FILE| below) to your | 51 # "codereview.settings" (see |CODEREVIEW_SETTINGS_FILE| below) to your |
50 # project's base directory and add the following line to codereview.settings: | 52 # project's base directory and add the following line to codereview.settings: |
51 # CODE_REVIEW_SERVER: codereview.yourserver.org | 53 # CODE_REVIEW_SERVER: codereview.yourserver.org |
52 } | 54 } |
(...skipping 10 matching lines...) Expand all Loading... | |
63 # Warning message when the change appears to be missing tests. | 65 # Warning message when the change appears to be missing tests. |
64 MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!" | 66 MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!" |
65 | 67 |
66 # Global cache of files cached in GetCacheDir(). | 68 # Global cache of files cached in GetCacheDir(). |
67 FILES_CACHE = {} | 69 FILES_CACHE = {} |
68 | 70 |
69 # Valid extensions for files we want to lint. | 71 # Valid extensions for files we want to lint. |
70 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" | 72 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" |
71 DEFAULT_LINT_IGNORE_REGEX = r"$^" | 73 DEFAULT_LINT_IGNORE_REGEX = r"$^" |
72 | 74 |
75 REVIEWERS_REGEX = r'\s*R=(.+)' | |
73 | 76 |
74 def CheckHomeForFile(filename): | 77 def CheckHomeForFile(filename): |
75 """Checks the users home dir for the existence of the given file. Returns | 78 """Checks the users home dir for the existence of the given file. Returns |
76 the path to the file if it's there, or None if it is not. | 79 the path to the file if it's there, or None if it is not. |
77 """ | 80 """ |
78 home_vars = ['HOME'] | 81 home_vars = ['HOME'] |
79 if sys.platform in ('cygwin', 'win32'): | 82 if sys.platform in ('cygwin', 'win32'): |
80 home_vars.append('USERPROFILE') | 83 home_vars.append('USERPROFILE') |
81 for home_var in home_vars: | 84 for home_var in home_vars: |
82 home = os.getenv(home_var) | 85 home = os.getenv(home_var) |
(...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
279 rietveld: rietveld server for this change | 282 rietveld: rietveld server for this change |
280 """ | 283 """ |
281 # Kept for unit test support. This is for the old format, it's deprecated. | 284 # Kept for unit test support. This is for the old format, it's deprecated. |
282 _SEPARATOR = "\n-----\n" | 285 _SEPARATOR = "\n-----\n" |
283 | 286 |
284 def __init__(self, name, issue, patchset, description, files, local_root, | 287 def __init__(self, name, issue, patchset, description, files, local_root, |
285 rietveld, needs_upload=False): | 288 rietveld, needs_upload=False): |
286 self.name = name | 289 self.name = name |
287 self.issue = int(issue) | 290 self.issue = int(issue) |
288 self.patchset = int(patchset) | 291 self.patchset = int(patchset) |
289 self.description = description | 292 self._description = None |
293 self._subject = None | |
294 self._reviewers = None | |
295 self._set_description(description) | |
290 if files is None: | 296 if files is None: |
291 files = [] | 297 files = [] |
292 self._files = files | 298 self._files = files |
293 self.patch = None | 299 self.patch = None |
294 self._local_root = local_root | 300 self._local_root = local_root |
295 self.needs_upload = needs_upload | 301 self.needs_upload = needs_upload |
296 self.rietveld = rietveld | 302 self.rietveld = rietveld |
297 if not self.rietveld: | 303 if not self.rietveld: |
298 # Set the default value. | 304 # Set the default value. |
299 self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER') | 305 self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER') |
300 | 306 |
307 def _get_description(self): | |
308 return self._description | |
309 | |
310 def _set_description(self, description): | |
311 # TODO(dpranke): Cloned from git_cl.py. These should be shared. | |
312 if not description: | |
313 self._description = description | |
314 return | |
315 | |
316 parsed_lines = [] | |
317 reviewers_re = re.compile(REVIEWERS_REGEX) | |
318 reviewers = '' | |
319 subject = '' | |
320 for l in description.splitlines(): | |
321 if not subject: | |
322 subject = l | |
323 matched_reviewers = reviewers_re.match(l) | |
324 if matched_reviewers: | |
325 reviewers = matched_reviewers.group(1) | |
326 parsed_lines.append(l) | |
327 | |
328 if subject.find("\r\n") != -1: | |
M-A Ruel
2011/03/15 19:11:57
http://docs.python.org/library/stdtypes.html#str.s
chase
2011/03/15 20:48:58
single quotes here and below, please
| |
329 subject = subject[:subject.find("\r\n")] | |
330 if subject.find("\n") != -1: | |
331 subject = subject[:subject.find("\n")] | |
332 if len(subject) > 77: | |
M-A Ruel
2011/03/15 19:11:57
The limit is in fact 100.
if len(subject) > 100
| |
333 subject = subject + "..." | |
M-A Ruel
2011/03/15 19:11:57
subject[:97]
| |
334 | |
335 self._subject = subject | |
336 self._reviewers = reviewers | |
337 self._description = '\n'.join(parsed_lines) | |
338 | |
339 description = property(_get_description, _set_description) | |
M-A Ruel
2011/03/15 19:11:57
Use python 2.2 format instead:
http://docs.python.
| |
340 | |
341 @property | |
342 def reviewers(self): | |
343 return self._reviewers | |
344 | |
345 @property | |
346 def subject(self): | |
347 return self._subject | |
348 | |
301 def NeedsUpload(self): | 349 def NeedsUpload(self): |
302 return self.needs_upload | 350 return self.needs_upload |
303 | 351 |
304 def GetFileNames(self): | 352 def GetFileNames(self): |
305 """Returns the list of file names included in this change.""" | 353 """Returns the list of file names included in this change.""" |
306 return [f[1] for f in self._files] | 354 return [f[1] for f in self._files] |
307 | 355 |
308 def GetFiles(self): | 356 def GetFiles(self): |
309 """Returns the list of files included in this change with their status.""" | 357 """Returns the list of files included in this change with their status.""" |
310 return self._files | 358 return self._files |
(...skipping 396 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
707 | 755 |
708 return editor | 756 return editor |
709 | 757 |
710 | 758 |
711 def GenerateDiff(files, root=None): | 759 def GenerateDiff(files, root=None): |
712 return SVN.GenerateDiff(files, root=root) | 760 return SVN.GenerateDiff(files, root=root) |
713 | 761 |
714 | 762 |
715 def OptionallyDoPresubmitChecks(change_info, committing, args): | 763 def OptionallyDoPresubmitChecks(change_info, committing, args): |
716 if FilterFlag(args, "--no_presubmit") or FilterFlag(args, "--force"): | 764 if FilterFlag(args, "--no_presubmit") or FilterFlag(args, "--force"): |
717 return True | 765 return presubmit_support.PresubmitOutput() |
718 return DoPresubmitChecks(change_info, committing, True) | 766 return DoPresubmitChecks(change_info, committing, True) |
719 | 767 |
720 | 768 |
769 def suggest_reviewers(change_info, affected_files): | |
770 owners_db = owners.Database(change_info.LocalRoot(), fopen=file, | |
771 os_path=os.path) | |
772 return owners_db.reviewers_for(affected_files) | |
773 | |
774 | |
721 def defer_attributes(a, b): | 775 def defer_attributes(a, b): |
722 """Copy attributes from an object (like a function) to another.""" | 776 """Copy attributes from an object (like a function) to another.""" |
723 for x in dir(a): | 777 for x in dir(a): |
724 if not getattr(b, x, None): | 778 if not getattr(b, x, None): |
725 setattr(b, x, getattr(a, x)) | 779 setattr(b, x, getattr(a, x)) |
726 | 780 |
727 | 781 |
728 def need_change(function): | 782 def need_change(function): |
729 """Converts args -> change_info.""" | 783 """Converts args -> change_info.""" |
730 # pylint: disable=W0612,W0621 | 784 # pylint: disable=W0612,W0621 |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
790 def CMDupload(change_info, args): | 844 def CMDupload(change_info, args): |
791 """Uploads the changelist to the server for review. | 845 """Uploads the changelist to the server for review. |
792 | 846 |
793 This does not submit a try job; use gcl try to submit a try job. | 847 This does not submit a try job; use gcl try to submit a try job. |
794 """ | 848 """ |
795 if '-s' in args or '--server' in args: | 849 if '-s' in args or '--server' in args: |
796 ErrorExit('Don\'t use the -s flag, fix codereview.settings instead') | 850 ErrorExit('Don\'t use the -s flag, fix codereview.settings instead') |
797 if not change_info.GetFiles(): | 851 if not change_info.GetFiles(): |
798 print "Nothing to upload, changelist is empty." | 852 print "Nothing to upload, changelist is empty." |
799 return 0 | 853 return 0 |
800 if not OptionallyDoPresubmitChecks(change_info, False, args): | 854 |
855 output = OptionallyDoPresubmitChecks(change_info, False, args) | |
856 if not output.should_continue(): | |
801 return 1 | 857 return 1 |
802 no_watchlists = (FilterFlag(args, "--no_watchlists") or | 858 no_watchlists = (FilterFlag(args, "--no_watchlists") or |
803 FilterFlag(args, "--no-watchlists")) | 859 FilterFlag(args, "--no-watchlists")) |
804 | 860 |
805 # Map --send-mail to --send_mail | 861 # Map --send-mail to --send_mail |
806 if FilterFlag(args, "--send-mail"): | 862 if FilterFlag(args, "--send-mail"): |
807 args.append("--send_mail") | 863 args.append("--send_mail") |
808 | 864 |
809 upload_arg = ["upload.py", "-y"] | 865 upload_arg = ["upload.py", "-y"] |
810 upload_arg.append("--server=%s" % change_info.rietveld) | 866 upload_arg.append("--server=%s" % change_info.rietveld) |
867 | |
868 reviewers = change_info.reviewers or output.reviewers | |
869 if (reviewers and | |
870 not any(arg.startswith('-r') or arg.startswith('--reviewer') for | |
871 arg in args)): | |
872 upload_arg.append('--reviewers=%s' % ','.join(reviewers)) | |
873 | |
811 upload_arg.extend(args) | 874 upload_arg.extend(args) |
812 | 875 |
813 desc_file = "" | 876 desc_file = "" |
814 if change_info.issue: # Uploading a new patchset. | 877 if change_info.issue: # Uploading a new patchset. |
815 found_message = False | 878 found_message = False |
816 for arg in args: | 879 for arg in args: |
817 if arg.startswith("--message") or arg.startswith("-m"): | 880 if arg.startswith("--message") or arg.startswith("-m"): |
818 found_message = True | 881 found_message = True |
819 break | 882 break |
820 | 883 |
(...skipping 13 matching lines...) Expand all Loading... | |
834 watchlist = watchlists.Watchlists(change_info.GetLocalRoot()) | 897 watchlist = watchlists.Watchlists(change_info.GetLocalRoot()) |
835 watchers = watchlist.GetWatchersForPaths(change_info.GetFileNames()) | 898 watchers = watchlist.GetWatchersForPaths(change_info.GetFileNames()) |
836 | 899 |
837 cc_list = GetCodeReviewSetting("CC_LIST") | 900 cc_list = GetCodeReviewSetting("CC_LIST") |
838 if not no_watchlists and watchers: | 901 if not no_watchlists and watchers: |
839 # Filter out all empty elements and join by ',' | 902 # Filter out all empty elements and join by ',' |
840 cc_list = ','.join(filter(None, [cc_list] + watchers)) | 903 cc_list = ','.join(filter(None, [cc_list] + watchers)) |
841 if cc_list: | 904 if cc_list: |
842 upload_arg.append("--cc=" + cc_list) | 905 upload_arg.append("--cc=" + cc_list) |
843 upload_arg.append("--description_file=" + desc_file + "") | 906 upload_arg.append("--description_file=" + desc_file + "") |
844 if change_info.description: | 907 if change_info.subject: |
845 subject = change_info.description[:77] | 908 upload_arg.append("--message=" + change_info.subject) |
846 if subject.find("\r\n") != -1: | |
847 subject = subject[:subject.find("\r\n")] | |
848 if subject.find("\n") != -1: | |
849 subject = subject[:subject.find("\n")] | |
850 if len(change_info.description) > 77: | |
851 subject = subject + "..." | |
852 upload_arg.append("--message=" + subject) | |
853 | 909 |
854 if GetCodeReviewSetting("PRIVATE") == "True": | 910 if GetCodeReviewSetting("PRIVATE") == "True": |
855 upload_arg.append("--private") | 911 upload_arg.append("--private") |
856 | 912 |
857 # Change the current working directory before calling upload.py so that it | 913 # Change the current working directory before calling upload.py so that it |
858 # shows the correct base. | 914 # shows the correct base. |
859 previous_cwd = os.getcwd() | 915 previous_cwd = os.getcwd() |
860 os.chdir(change_info.GetLocalRoot()) | 916 os.chdir(change_info.GetLocalRoot()) |
861 # If we have a lot of files with long paths, then we won't be able to fit | 917 # If we have a lot of files with long paths, then we won't be able to fit |
862 # the command to "svn diff". Instead, we generate the diff manually for | 918 # the command to "svn diff". Instead, we generate the diff manually for |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
977 os.remove(targets_filename) | 1033 os.remove(targets_filename) |
978 if output.find("Committed revision") != -1: | 1034 if output.find("Committed revision") != -1: |
979 change_info.Delete() | 1035 change_info.Delete() |
980 | 1036 |
981 if change_info.issue: | 1037 if change_info.issue: |
982 revision = re.compile(".*?\nCommitted revision (\d+)", | 1038 revision = re.compile(".*?\nCommitted revision (\d+)", |
983 re.DOTALL).match(output).group(1) | 1039 re.DOTALL).match(output).group(1) |
984 viewvc_url = GetCodeReviewSetting("VIEW_VC") | 1040 viewvc_url = GetCodeReviewSetting("VIEW_VC") |
985 change_info.description = change_info.description + '\n' | 1041 change_info.description = change_info.description + '\n' |
986 if viewvc_url: | 1042 if viewvc_url: |
987 change_info.description += "\nCommitted: " + viewvc_url + revision | 1043 change_info.description = (change_info.description + |
1044 "\nCommitted: " + viewvc_url + revision) | |
chase
2011/03/15 20:48:58
can this be reverted? seems like there's no diffe
| |
988 change_info.CloseIssue() | 1045 change_info.CloseIssue() |
989 os.chdir(previous_cwd) | 1046 os.chdir(previous_cwd) |
990 return 0 | 1047 return 0 |
991 | 1048 |
992 | 1049 |
993 def CMDchange(args): | 1050 def CMDchange(args): |
994 """Creates or edits a changelist. | 1051 """Creates or edits a changelist. |
995 | 1052 |
996 Only scans the current directory and subdirectories.""" | 1053 Only scans the current directory and subdirectories.""" |
997 if len(args) == 0: | 1054 if len(args) == 0: |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1037 description = change_info.description | 1094 description = change_info.description |
1038 | 1095 |
1039 other_files = GetFilesNotInCL() | 1096 other_files = GetFilesNotInCL() |
1040 | 1097 |
1041 # Edited files (as opposed to files with only changed properties) will have | 1098 # Edited files (as opposed to files with only changed properties) will have |
1042 # a letter for the first character in the status string. | 1099 # a letter for the first character in the status string. |
1043 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE) | 1100 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE) |
1044 affected_files = [x for x in other_files if file_re.match(x[0])] | 1101 affected_files = [x for x in other_files if file_re.match(x[0])] |
1045 unaffected_files = [x for x in other_files if not file_re.match(x[0])] | 1102 unaffected_files = [x for x in other_files if not file_re.match(x[0])] |
1046 | 1103 |
1104 suggested_reviewers = suggest_reviewers(change_info, affected_files) | |
1105 if suggested_reviewers: | |
1106 reviewers_re = re.compile(REVIEWERS_REGEX) | |
1107 if not any(reviewers_re.match(l) for | |
M-A Ruel
2011/03/15 19:11:57
if not any(
reviewers_re.match(l) for l in des
| |
1108 l in description.splitlines()): | |
1109 description += '\nR=' + ','.join(suggested_reviewers) + '\n' | |
1110 | |
1047 separator1 = ("\n---All lines above this line become the description.\n" | 1111 separator1 = ("\n---All lines above this line become the description.\n" |
1048 "---Repository Root: " + change_info.GetLocalRoot() + "\n" | 1112 "---Repository Root: " + change_info.GetLocalRoot() + "\n" |
1049 "---Paths in this changelist (" + change_info.name + "):\n") | 1113 "---Paths in this changelist (" + change_info.name + "):\n") |
1050 separator2 = "\n\n---Paths modified but not in any changelist:\n\n" | 1114 separator2 = "\n\n---Paths modified but not in any changelist:\n\n" |
1051 text = (description + separator1 + '\n' + | 1115 text = (description + separator1 + '\n' + |
1052 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()])) | 1116 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()])) |
1053 | 1117 |
1054 if change_info.Exists(): | 1118 if change_info.Exists(): |
1055 text += (separator2 + | 1119 text += (separator2 + |
1056 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n') | 1120 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n') |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1154 else: | 1218 else: |
1155 print "Skipping file %s" % filename | 1219 print "Skipping file %s" % filename |
1156 | 1220 |
1157 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count | 1221 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count |
1158 os.chdir(previous_cwd) | 1222 os.chdir(previous_cwd) |
1159 return 1 | 1223 return 1 |
1160 | 1224 |
1161 | 1225 |
1162 def DoPresubmitChecks(change_info, committing, may_prompt): | 1226 def DoPresubmitChecks(change_info, committing, may_prompt): |
1163 """Imports presubmit, then calls presubmit.DoPresubmitChecks.""" | 1227 """Imports presubmit, then calls presubmit.DoPresubmitChecks.""" |
1164 # Need to import here to avoid circular dependency. | |
1165 import presubmit_support | |
1166 root_presubmit = GetCachedFile('PRESUBMIT.py', use_root=True) | 1228 root_presubmit = GetCachedFile('PRESUBMIT.py', use_root=True) |
1167 change = presubmit_support.SvnChange(change_info.name, | 1229 change = presubmit_support.SvnChange(change_info.name, |
1168 change_info.description, | 1230 change_info.description, |
1169 change_info.GetLocalRoot(), | 1231 change_info.GetLocalRoot(), |
1170 change_info.GetFiles(), | 1232 change_info.GetFiles(), |
1171 change_info.issue, | 1233 change_info.issue, |
1172 change_info.patchset) | 1234 change_info.patchset) |
1173 output = presubmit_support.DoPresubmitChecks(change=change, | 1235 output = presubmit_support.DoPresubmitChecks(change=change, |
1174 committing=committing, | 1236 committing=committing, |
1175 verbose=False, | 1237 verbose=False, |
1176 output_stream=sys.stdout, | 1238 output_stream=sys.stdout, |
1177 input_stream=sys.stdin, | 1239 input_stream=sys.stdin, |
1178 default_presubmit=root_presubmit, | 1240 default_presubmit=root_presubmit, |
1179 may_prompt=may_prompt) | 1241 may_prompt=may_prompt, |
1242 tbr=False, | |
1243 host_url=change_info.rietveld) | |
1180 if not output.should_continue() and may_prompt: | 1244 if not output.should_continue() and may_prompt: |
1181 # TODO(dpranke): move into DoPresubmitChecks(), unify cmd line args. | 1245 # TODO(dpranke): move into DoPresubmitChecks(), unify cmd line args. |
1182 print "\nPresubmit errors, can't continue (use --no_presubmit to bypass)" | 1246 print "\nPresubmit errors, can't continue (use --no_presubmit to bypass)" |
1183 | 1247 |
1184 # TODO(dpranke): Return the output object and make use of it. | 1248 return output |
1185 return output.should_continue() | |
1186 | 1249 |
1187 | 1250 |
1188 @no_args | 1251 @no_args |
1189 def CMDchanges(): | 1252 def CMDchanges(): |
1190 """Lists all the changelists and their files.""" | 1253 """Lists all the changelists and their files.""" |
1191 for cl in GetCLs(): | 1254 for cl in GetCLs(): |
1192 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True) | 1255 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True) |
1193 print "\n--- Changelist " + change_info.name + ":" | 1256 print "\n--- Changelist " + change_info.name + ":" |
1194 for filename in change_info.GetFiles(): | 1257 for filename in change_info.GetFiles(): |
1195 print "".join(filename) | 1258 print "".join(filename) |
(...skipping 203 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1399 if e.code != 500: | 1462 if e.code != 500: |
1400 raise | 1463 raise |
1401 print >> sys.stderr, ( | 1464 print >> sys.stderr, ( |
1402 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 1465 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
1403 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)) | 1466 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)) |
1404 return 1 | 1467 return 1 |
1405 | 1468 |
1406 | 1469 |
1407 if __name__ == "__main__": | 1470 if __name__ == "__main__": |
1408 sys.exit(main(sys.argv[1:])) | 1471 sys.exit(main(sys.argv[1:])) |
OLD | NEW |