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 320 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1086 | 1049 |
1087 other_files = GetFilesNotInCL() | 1050 other_files = GetFilesNotInCL() |
1088 | 1051 |
1089 # Edited files (as opposed to files with only changed properties) will have | 1052 # Edited files (as opposed to files with only changed properties) will have |
1090 # a letter for the first character in the status string. | 1053 # a letter for the first character in the status string. |
1091 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE) | 1054 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE) |
1092 affected_files = [x for x in other_files if file_re.match(x[0])] | 1055 affected_files = [x for x in other_files if file_re.match(x[0])] |
1093 unaffected_files = [x for x in other_files if not file_re.match(x[0])] | 1056 unaffected_files = [x for x in other_files if not file_re.match(x[0])] |
1094 | 1057 |
1095 description = description.rstrip() + '\n' | 1058 description = description.rstrip() + '\n' |
| 1059 reviewers = change_info.reviewers |
1096 | 1060 |
1097 separator1 = ("\n---All lines above this line become the description.\n" | 1061 separator1 = ("\n---All lines above this line become the description.\n" |
1098 "---Repository Root: " + change_info.GetLocalRoot() + "\n" | 1062 "---Repository Root: " + change_info.GetLocalRoot() + "\n" |
1099 "---Paths in this changelist (" + change_info.name + "):\n") | 1063 "---Paths in this changelist (" + change_info.name + "):\n") |
1100 separator2 = "\n\n---Paths modified but not in any changelist:\n\n" | 1064 separator2 = "\n\n---Paths modified but not in any changelist:\n\n" |
1101 text = (description + separator1 + '\n' + | 1065 |
1102 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()])) | 1066 footer = (separator1 + '\n' + |
| 1067 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()])) |
1103 | 1068 |
1104 if change_info.Exists(): | 1069 if change_info.Exists(): |
1105 text += (separator2 + | 1070 footer += (separator2 + |
1106 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n') | 1071 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n') |
1107 else: | 1072 else: |
1108 text += ('\n'.join([f[0] + f[1] for f in affected_files]) + '\n' + | 1073 footer += ('\n'.join([f[0] + f[1] for f in affected_files]) + '\n' + |
1109 separator2) | 1074 separator2) |
1110 text += '\n'.join([f[0] + f[1] for f in unaffected_files]) + '\n' | 1075 footer += '\n'.join([f[0] + f[1] for f in unaffected_files]) + '\n' |
1111 | 1076 |
1112 handle, filename = tempfile.mkstemp(text=True) | 1077 change_desc = presubmit_support.ChangeDescription(description=description, |
1113 os.write(handle, text) | 1078 reviewers=reviewers) |
1114 os.close(handle) | |
1115 | 1079 |
1116 # Open up the default editor in the system to get the CL description. | 1080 # These next few lines are equivalent to change_desc.UserUpdate(). We |
1117 try: | 1081 # call them individually to avoid passing a lot of state back and forth. |
1118 if not silent: | 1082 original_description = change_desc.description |
1119 cmd = '%s %s' % (GetEditor(), filename) | 1083 |
1120 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys': | 1084 result = change_desc.EditableDescription() + footer |
1121 # Msysgit requires the usage of 'env' to be present. | 1085 if not silent: |
1122 cmd = 'env ' + cmd | 1086 result = change_desc.editor(result) |
1123 # shell=True to allow the shell to handle all forms of quotes in $EDITOR. | |
1124 subprocess.check_call(cmd, shell=True) | |
1125 result = gclient_utils.FileRead(filename, 'r') | |
1126 finally: | |
1127 os.remove(filename) | |
1128 | 1087 |
1129 if not result: | 1088 if not result: |
1130 return 0 | 1089 return 0 |
1131 | 1090 |
1132 split_result = result.split(separator1, 1) | 1091 split_result = result.split(separator1, 1) |
1133 if len(split_result) != 2: | 1092 if len(split_result) != 2: |
1134 ErrorExit("Don't modify the text starting with ---!\n\n" + result) | 1093 ErrorExit("Don't modify the text starting with ---!\n\n" + result) |
1135 | 1094 |
1136 # Update the CL description if it has changed. | 1095 # Update the CL description if it has changed. |
1137 new_description = split_result[0] | 1096 new_description = split_result[0] |
1138 cl_files_text = split_result[1] | 1097 cl_files_text = split_result[1] |
1139 if new_description != description or override_description: | 1098 change_desc.Parse(new_description) |
1140 change_info.description = new_description | 1099 if change_desc.description != original_description or override_description: |
1141 change_info.needs_upload = True | 1100 change_info.needs_upload = True |
1142 | 1101 |
1143 new_cl_files = [] | 1102 new_cl_files = [] |
1144 for line in cl_files_text.splitlines(): | 1103 for line in cl_files_text.splitlines(): |
1145 if not len(line): | 1104 if not len(line): |
1146 continue | 1105 continue |
1147 if line.startswith("---"): | 1106 if line.startswith("---"): |
1148 break | 1107 break |
1149 status = line[:7] | 1108 status = line[:7] |
1150 filename = line[7:] | 1109 filename = line[7:] |
1151 new_cl_files.append((status, filename)) | 1110 new_cl_files.append((status, filename)) |
1152 | 1111 |
1153 if (not len(change_info.GetFiles()) and not change_info.issue and | 1112 if (not len(change_info.GetFiles()) and not change_info.issue and |
1154 not len(new_description) and not new_cl_files): | 1113 not len(change_desc.description) and not new_cl_files): |
1155 ErrorExit("Empty changelist not saved") | 1114 ErrorExit("Empty changelist not saved") |
1156 | 1115 |
1157 change_info._files = new_cl_files | 1116 change_info._files = new_cl_files |
1158 change_info.Save() | 1117 change_info.Save() |
1159 if svn_info.get('URL', '').startswith('http:'): | 1118 if svn_info.get('URL', '').startswith('http:'): |
1160 Warn("WARNING: Creating CL in a read-only checkout. You will not be " | 1119 Warn("WARNING: Creating CL in a read-only checkout. You will not be " |
1161 "able to commit it!") | 1120 "able to commit it!") |
1162 | 1121 |
1163 print change_info.name + " changelist saved." | 1122 print change_info.name + " changelist saved." |
1164 if change_info.MissingTests(): | 1123 if change_info.MissingTests(): |
(...skipping 288 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1453 raise | 1412 raise |
1454 print >> sys.stderr, ( | 1413 print >> sys.stderr, ( |
1455 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' | 1414 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' |
1456 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)) | 1415 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)) |
1457 return 1 | 1416 return 1 |
1458 | 1417 |
1459 | 1418 |
1460 if __name__ == "__main__": | 1419 if __name__ == "__main__": |
1461 fix_encoding.fix_encoding() | 1420 fix_encoding.fix_encoding() |
1462 sys.exit(main(sys.argv[1:])) | 1421 sys.exit(main(sys.argv[1:])) |
OLD | NEW |