| 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 |