Chromium Code Reviews| 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 |