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 272 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
283 rietveld: rietveld server for this change | 283 rietveld: rietveld server for this change |
284 """ | 284 """ |
285 # Kept for unit test support. This is for the old format, it's deprecated. | 285 # Kept for unit test support. This is for the old format, it's deprecated. |
286 _SEPARATOR = "\n-----\n" | 286 _SEPARATOR = "\n-----\n" |
287 | 287 |
288 def __init__(self, name, issue, patchset, description, files, local_root, | 288 def __init__(self, name, issue, patchset, description, files, local_root, |
289 rietveld, needs_upload=False): | 289 rietveld, needs_upload=False): |
290 self.name = name | 290 self.name = name |
291 self.issue = int(issue) | 291 self.issue = int(issue) |
292 self.patchset = int(patchset) | 292 self.patchset = int(patchset) |
293 self._description = None | 293 self._change_desc = None |
294 self._subject = None | |
295 self._reviewers = None | |
296 self._set_description(description) | 294 self._set_description(description) |
297 if files is None: | 295 if files is None: |
298 files = [] | 296 files = [] |
299 self._files = files | 297 self._files = files |
300 self.patch = None | 298 self.patch = None |
301 self._local_root = local_root | 299 self._local_root = local_root |
302 self.needs_upload = needs_upload | 300 self.needs_upload = needs_upload |
303 self.rietveld = rietveld | 301 self.rietveld = rietveld |
304 if not self.rietveld: | 302 if not self.rietveld: |
305 # Set the default value. | 303 # Set the default value. |
306 self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER') | 304 self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER') |
307 | 305 |
308 def _get_description(self): | 306 def _get_description(self): |
309 return self._description | 307 return self._change_desc.description |
310 | 308 |
311 def _set_description(self, description): | 309 def _set_description(self, description): |
312 # TODO(dpranke): Cloned from git_cl.py. These should be shared. | 310 self._change_desc = presubmit_support.ChangeDescription( |
313 if not description: | 311 description=description) |
314 self._description = description | |
315 return | |
316 | |
317 parsed_lines = [] | |
318 reviewers_re = re.compile(REVIEWERS_REGEX) | |
319 reviewers = '' | |
320 subject = '' | |
321 for l in description.splitlines(): | |
322 if not subject: | |
323 subject = l | |
324 matched_reviewers = reviewers_re.match(l) | |
325 if matched_reviewers: | |
326 reviewers = matched_reviewers.group(1).split(',') | |
327 parsed_lines.append(l) | |
328 | |
329 if len(subject) > 100: | |
330 subject = subject[:97] + '...' | |
331 | |
332 self._subject = subject | |
333 self._reviewers = reviewers | |
334 self._description = '\n'.join(parsed_lines) | |
335 | 312 |
336 description = property(_get_description, _set_description) | 313 description = property(_get_description, _set_description) |
337 | 314 |
338 @property | 315 @property |
339 def reviewers(self): | 316 def reviewers(self): |
340 return self._reviewers | 317 return self._change_desc.reviewers |
341 | 318 |
342 @property | 319 @property |
343 def subject(self): | 320 def subject(self): |
344 return self._subject | 321 return self._change_desc.subject |
345 | 322 |
346 def NeedsUpload(self): | 323 def NeedsUpload(self): |
347 return self.needs_upload | 324 return self.needs_upload |
348 | 325 |
349 def GetFileNames(self): | 326 def GetFileNames(self): |
350 """Returns the list of file names included in this change.""" | 327 """Returns the list of file names included in this change.""" |
351 return [f[1] for f in self._files] | 328 return [f[1] for f in self._files] |
352 | 329 |
353 def GetFiles(self): | 330 def GetFiles(self): |
354 """Returns the list of files included in this change with their status.""" | 331 """Returns the list of files included in this change with their status.""" |
(...skipping 16 matching lines...) Expand all Loading... | |
371 """Returns a list of files added in this change.""" | 348 """Returns a list of files added in this change.""" |
372 return [f[1] for f in self.GetFiles() if f[0].startswith("A")] | 349 return [f[1] for f in self.GetFiles() if f[0].startswith("A")] |
373 | 350 |
374 def Save(self): | 351 def Save(self): |
375 """Writes the changelist information to disk.""" | 352 """Writes the changelist information to disk.""" |
376 data = json.dumps({ | 353 data = json.dumps({ |
377 'issue': self.issue, | 354 'issue': self.issue, |
378 'patchset': self.patchset, | 355 'patchset': self.patchset, |
379 'needs_upload': self.NeedsUpload(), | 356 'needs_upload': self.NeedsUpload(), |
380 'files': self.GetFiles(), | 357 'files': self.GetFiles(), |
381 'description': self.description, | 358 'description': self._change_desc.description, |
382 'rietveld': self.rietveld, | 359 'rietveld': self.rietveld, |
383 }, sort_keys=True, indent=2) | 360 }, sort_keys=True, indent=2) |
384 gclient_utils.FileWrite(GetChangelistInfoFile(self.name), data) | 361 gclient_utils.FileWrite(GetChangelistInfoFile(self.name), data) |
385 | 362 |
386 def Delete(self): | 363 def Delete(self): |
387 """Removes the changelist information from disk.""" | 364 """Removes the changelist information from disk.""" |
388 os.remove(GetChangelistInfoFile(self.name)) | 365 os.remove(GetChangelistInfoFile(self.name)) |
389 | 366 |
390 def CloseIssue(self): | 367 def CloseIssue(self): |
391 """Closes the Rietveld issue for this changelist.""" | 368 """Closes the Rietveld issue for this changelist.""" |
(...skipping 340 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
732 if (files.get('') or (show_unknown_files and len(unknown_files))): | 709 if (files.get('') or (show_unknown_files and len(unknown_files))): |
733 print "\n--- Not in any changelist:" | 710 print "\n--- Not in any changelist:" |
734 for item in files.get('', []): | 711 for item in files.get('', []): |
735 print "".join(item) | 712 print "".join(item) |
736 if show_unknown_files: | 713 if show_unknown_files: |
737 for filename in unknown_files: | 714 for filename in unknown_files: |
738 print "? %s" % filename | 715 print "? %s" % filename |
739 return 0 | 716 return 0 |
740 | 717 |
741 | 718 |
742 def GetEditor(): | |
743 editor = os.environ.get("SVN_EDITOR") | |
744 if not editor: | |
745 editor = os.environ.get("EDITOR") | |
746 | |
747 if not editor: | |
748 if sys.platform.startswith("win"): | |
749 editor = "notepad" | |
750 else: | |
751 editor = "vi" | |
752 | |
753 return editor | |
754 | |
755 | |
756 def GenerateDiff(files, root=None): | 719 def GenerateDiff(files, root=None): |
757 return SVN.GenerateDiff(files, root=root) | 720 return SVN.GenerateDiff(files, root=root) |
758 | 721 |
759 | 722 |
760 def OptionallyDoPresubmitChecks(change_info, committing, args): | 723 def OptionallyDoPresubmitChecks(change_info, committing, args): |
761 if FilterFlag(args, "--no_presubmit") or FilterFlag(args, "--force"): | 724 if FilterFlag(args, "--no_presubmit") or FilterFlag(args, "--force"): |
762 return presubmit_support.PresubmitOutput() | 725 return presubmit_support.PresubmitOutput() |
763 return DoPresubmitChecks(change_info, committing, True) | 726 return DoPresubmitChecks(change_info, committing, True) |
764 | 727 |
765 | 728 |
(...skipping 325 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1091 description = change_info.description | 1054 description = change_info.description |
1092 | 1055 |
1093 other_files = GetFilesNotInCL() | 1056 other_files = GetFilesNotInCL() |
1094 | 1057 |
1095 # Edited files (as opposed to files with only changed properties) will have | 1058 # Edited files (as opposed to files with only changed properties) will have |
1096 # a letter for the first character in the status string. | 1059 # a letter for the first character in the status string. |
1097 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE) | 1060 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE) |
1098 affected_files = [x for x in other_files if file_re.match(x[0])] | 1061 affected_files = [x for x in other_files if file_re.match(x[0])] |
1099 unaffected_files = [x for x in other_files if not file_re.match(x[0])] | 1062 unaffected_files = [x for x in other_files if not file_re.match(x[0])] |
1100 | 1063 |
1101 if not change_info.reviewers: | 1064 reviewers = change_info.reviewers |
1065 if not reviewers: | |
1102 files_for_review = affected_files[:] | 1066 files_for_review = affected_files[:] |
1103 files_for_review.extend(change_info.GetFiles()) | 1067 files_for_review.extend(change_info.GetFiles()) |
1104 suggested_reviewers = suggest_reviewers(change_info, files_for_review) | 1068 reviewers = suggest_reviewers(change_info, files_for_review) |
1105 if suggested_reviewers: | |
1106 reviewers_re = re.compile(REVIEWERS_REGEX) | |
1107 if not any(reviewers_re.match(l) for l in description.splitlines()): | |
1108 description += '\n\nR=' + ','.join(suggested_reviewers) | |
1109 | |
1110 description = description.rstrip() + '\n' | |
1111 | 1069 |
1112 separator1 = ("\n---All lines above this line become the description.\n" | 1070 separator1 = ("\n---All lines above this line become the description.\n" |
1113 "---Repository Root: " + change_info.GetLocalRoot() + "\n" | 1071 "---Repository Root: " + change_info.GetLocalRoot() + "\n" |
1114 "---Paths in this changelist (" + change_info.name + "):\n") | 1072 "---Paths in this changelist (" + change_info.name + "):\n") |
1115 separator2 = "\n\n---Paths modified but not in any changelist:\n\n" | 1073 separator2 = "\n\n---Paths modified but not in any changelist:\n\n" |
1116 text = (description + separator1 + '\n' + | 1074 |
1117 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()])) | 1075 footer = (separator1 + '\n' + |
1076 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()])) | |
1118 | 1077 |
1119 if change_info.Exists(): | 1078 if change_info.Exists(): |
1120 text += (separator2 + | 1079 footer += (separator2 + |
1121 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n') | 1080 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n') |
1122 else: | 1081 else: |
1123 text += ('\n'.join([f[0] + f[1] for f in affected_files]) + '\n' + | 1082 footer += ('\n'.join([f[0] + f[1] for f in affected_files]) + '\n' + |
1124 separator2) | 1083 separator2) |
1125 text += '\n'.join([f[0] + f[1] for f in unaffected_files]) + '\n' | 1084 footer += '\n'.join([f[0] + f[1] for f in unaffected_files]) + '\n' |
1126 | 1085 |
1127 handle, filename = tempfile.mkstemp(text=True) | 1086 change_desc = presubmit_support.ChangeDescription(description=description, |
1128 os.write(handle, text) | 1087 reviewers=reviewers) |
1129 os.close(handle) | |
1130 | 1088 |
1131 # Open up the default editor in the system to get the CL description. | 1089 # These next few lines are equivalent to change_desc.UserUpdate(). We |
1132 try: | 1090 # call them individually to avoid passing a lot of state back and forth. |
1133 if not silent: | 1091 original_description = change_desc.description |
1134 cmd = '%s %s' % (GetEditor(), filename) | 1092 |
1135 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys': | 1093 text = change_desc.EditableDescription() + footer |
M-A Ruel
2011/03/22 17:24:16
Why not?
result = change_desc...
if not silent:
| |
1136 # Msysgit requires the usage of 'env' to be present. | 1094 |
1137 cmd = 'env ' + cmd | 1095 if not silent: |
1138 # shell=True to allow the shell to handle all forms of quotes in $EDITOR. | 1096 result = change_desc.editor(text) |
1139 subprocess.check_call(cmd, shell=True) | 1097 else: |
1140 result = gclient_utils.FileRead(filename, 'r') | 1098 result = text |
1141 finally: | |
1142 os.remove(filename) | |
1143 | 1099 |
1144 if not result: | 1100 if not result: |
1145 return 0 | 1101 return 0 |
1146 | 1102 |
1147 split_result = result.split(separator1, 1) | 1103 split_result = result.split(separator1, 1) |
1148 if len(split_result) != 2: | 1104 if len(split_result) != 2: |
1149 ErrorExit("Don't modify the text starting with ---!\n\n" + result) | 1105 ErrorExit("Don't modify the text starting with ---!\n\n" + result) |
1150 | 1106 |
1151 # Update the CL description if it has changed. | 1107 # Update the CL description if it has changed. |
1152 new_description = split_result[0] | 1108 new_description = split_result[0] |
1153 cl_files_text = split_result[1] | 1109 cl_files_text = split_result[1] |
1154 if new_description != description or override_description: | 1110 change_desc.Parse(new_description) |
1155 change_info.description = new_description | 1111 if change_desc.description != original_description or override_description: |
1156 change_info.needs_upload = True | 1112 change_info.needs_upload = True |
1157 | 1113 |
1158 new_cl_files = [] | 1114 new_cl_files = [] |
1159 for line in cl_files_text.splitlines(): | 1115 for line in cl_files_text.splitlines(): |
1160 if not len(line): | 1116 if not len(line): |
1161 continue | 1117 continue |
1162 if line.startswith("---"): | 1118 if line.startswith("---"): |
1163 break | 1119 break |
1164 status = line[:7] | 1120 status = line[:7] |
1165 filename = line[7:] | 1121 filename = line[7:] |
1166 new_cl_files.append((status, filename)) | 1122 new_cl_files.append((status, filename)) |
1167 | 1123 |
1168 if (not len(change_info.GetFiles()) and not change_info.issue and | 1124 if (not len(change_info.GetFiles()) and not change_info.issue and |
1169 not len(new_description) and not new_cl_files): | 1125 not len(change_desc.description) and not new_cl_files): |
1170 ErrorExit("Empty changelist not saved") | 1126 ErrorExit("Empty changelist not saved") |
1171 | 1127 |
1172 change_info._files = new_cl_files | 1128 change_info._files = new_cl_files |
1173 change_info.Save() | 1129 change_info.Save() |
1174 if svn_info.get('URL', '').startswith('http:'): | 1130 if svn_info.get('URL', '').startswith('http:'): |
1175 Warn("WARNING: Creating CL in a read-only checkout. You will not be " | 1131 Warn("WARNING: Creating CL in a read-only checkout. You will not be " |
1176 "able to commit it!") | 1132 "able to commit it!") |
1177 | 1133 |
1178 print change_info.name + " changelist saved." | 1134 print change_info.name + " changelist saved." |
1179 if change_info.MissingTests(): | 1135 if change_info.MissingTests(): |
(...skipping 287 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1467 if e.code != 500: | 1423 if e.code != 500: |
1468 raise | 1424 raise |
1469 print >> sys.stderr, ( | 1425 print >> sys.stderr, ( |
1470 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 1426 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
1471 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)) | 1427 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)) |
1472 return 1 | 1428 return 1 |
1473 | 1429 |
1474 | 1430 |
1475 if __name__ == "__main__": | 1431 if __name__ == "__main__": |
1476 sys.exit(main(sys.argv[1:])) | 1432 sys.exit(main(sys.argv[1:])) |
OLD | NEW |