OLD | NEW |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2006-2009 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 """Wrapper script around Rietveld's upload.py that groups files into | 6 """Wrapper script around Rietveld's upload.py that groups files into |
7 changelists.""" | 7 changelists.""" |
8 | 8 |
9 import getpass | 9 import getpass |
10 import os | 10 import os |
(...skipping 252 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
263 # issue_id, patchset\n (, patchset is optional) | 263 # issue_id, patchset\n (, patchset is optional) |
264 # _SEPARATOR\n | 264 # _SEPARATOR\n |
265 # filepath1\n | 265 # filepath1\n |
266 # filepath2\n | 266 # filepath2\n |
267 # . | 267 # . |
268 # . | 268 # . |
269 # filepathn\n | 269 # filepathn\n |
270 # _SEPARATOR\n | 270 # _SEPARATOR\n |
271 # description | 271 # description |
272 | 272 |
273 def __init__(self, name, issue, patchset, description, files, local_root): | 273 def __init__(self, name, issue, patchset, description, files, local_root, |
274 needs_upload=False): | |
274 self.name = name | 275 self.name = name |
275 self.issue = int(issue) | 276 self.issue = int(issue) |
276 self.patchset = int(patchset) | 277 self.patchset = int(patchset) |
277 self.description = description | 278 self.description = description |
278 if files is None: | 279 if files is None: |
279 files = [] | 280 files = [] |
280 self._files = files | 281 self._files = files |
281 self.patch = None | 282 self.patch = None |
282 self._local_root = local_root | 283 self._local_root = local_root |
284 self.needs_upload = needs_upload | |
285 | |
286 def NeedsUpload(self): | |
287 return self.needs_upload | |
283 | 288 |
284 def GetFileNames(self): | 289 def GetFileNames(self): |
285 """Returns the list of file names included in this change.""" | 290 """Returns the list of file names included in this change.""" |
286 return [f[1] for f in self._files] | 291 return [f[1] for f in self._files] |
287 | 292 |
288 def GetFiles(self): | 293 def GetFiles(self): |
289 """Returns the list of files included in this change with their status.""" | 294 """Returns the list of files included in this change with their status.""" |
290 return self._files | 295 return self._files |
291 | 296 |
292 def GetLocalRoot(self): | 297 def GetLocalRoot(self): |
293 """Returns the local repository checkout root directory.""" | 298 """Returns the local repository checkout root directory.""" |
294 return self._local_root | 299 return self._local_root |
295 | 300 |
296 def Exists(self): | 301 def Exists(self): |
297 """Returns True if this change already exists (i.e., is not new).""" | 302 """Returns True if this change already exists (i.e., is not new).""" |
298 return (self.issue or self.description or self._files) | 303 return (self.issue or self.description or self._files) |
299 | 304 |
300 def _NonDeletedFileList(self): | 305 def _NonDeletedFileList(self): |
301 """Returns a list of files in this change, not including deleted files.""" | 306 """Returns a list of files in this change, not including deleted files.""" |
302 return [f[1] for f in self.GetFiles() | 307 return [f[1] for f in self.GetFiles() |
303 if not f[0].startswith("D")] | 308 if not f[0].startswith("D")] |
304 | 309 |
305 def _AddedFileList(self): | 310 def _AddedFileList(self): |
306 """Returns a list of files added in this change.""" | 311 """Returns a list of files added in this change.""" |
307 return [f[1] for f in self.GetFiles() if f[0].startswith("A")] | 312 return [f[1] for f in self.GetFiles() if f[0].startswith("A")] |
308 | 313 |
309 def Save(self): | 314 def Save(self): |
310 """Writes the changelist information to disk.""" | 315 """Writes the changelist information to disk.""" |
316 if self.NeedsUpload(): | |
317 needs_upload = "dirty" | |
318 else: | |
319 needs_upload = "clean" | |
311 data = ChangeInfo._SEPARATOR.join([ | 320 data = ChangeInfo._SEPARATOR.join([ |
M-A Ruel
2009/11/22 18:06:10
We should have used json or pickle right at the st
| |
312 "%d, %d" % (self.issue, self.patchset), | 321 "%d, %d, %s" % (self.issue, self.patchset, needs_upload), |
313 "\n".join([f[0] + f[1] for f in self.GetFiles()]), | 322 "\n".join([f[0] + f[1] for f in self.GetFiles()]), |
314 self.description]) | 323 self.description]) |
315 WriteFile(GetChangelistInfoFile(self.name), data) | 324 WriteFile(GetChangelistInfoFile(self.name), data) |
316 | 325 |
317 def Delete(self): | 326 def Delete(self): |
318 """Removes the changelist information from disk.""" | 327 """Removes the changelist information from disk.""" |
319 os.remove(GetChangelistInfoFile(self.name)) | 328 os.remove(GetChangelistInfoFile(self.name)) |
320 | 329 |
321 def CloseIssue(self): | 330 def CloseIssue(self): |
322 """Closes the Rietveld issue for this changelist.""" | 331 """Closes the Rietveld issue for this changelist.""" |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
420 changelist doesn't exist. | 429 changelist doesn't exist. |
421 update_status: if True, the svn status will be updated for all the files | 430 update_status: if True, the svn status will be updated for all the files |
422 and unchanged files will be removed. | 431 and unchanged files will be removed. |
423 | 432 |
424 Returns: a ChangeInfo object. | 433 Returns: a ChangeInfo object. |
425 """ | 434 """ |
426 info_file = GetChangelistInfoFile(changename) | 435 info_file = GetChangelistInfoFile(changename) |
427 if not os.path.exists(info_file): | 436 if not os.path.exists(info_file): |
428 if fail_on_not_found: | 437 if fail_on_not_found: |
429 ErrorExit("Changelist " + changename + " not found.") | 438 ErrorExit("Changelist " + changename + " not found.") |
430 return ChangeInfo(changename, 0, 0, '', None, local_root) | 439 return ChangeInfo(changename, 0, 0, '', None, local_root, |
440 needs_upload=False) | |
431 split_data = ReadFile(info_file).split(ChangeInfo._SEPARATOR, 2) | 441 split_data = ReadFile(info_file).split(ChangeInfo._SEPARATOR, 2) |
432 if len(split_data) != 3: | 442 if len(split_data) != 3: |
433 ErrorExit("Changelist file %s is corrupt" % info_file) | 443 ErrorExit("Changelist file %s is corrupt" % info_file) |
434 items = split_data[0].split(',') | 444 items = split_data[0].split(', ') |
435 issue = 0 | 445 issue = 0 |
436 patchset = 0 | 446 patchset = 0 |
447 needs_upload = False | |
437 if items[0]: | 448 if items[0]: |
438 issue = int(items[0]) | 449 issue = int(items[0]) |
439 if len(items) > 1: | 450 if len(items) > 1: |
440 patchset = int(items[1]) | 451 patchset = int(items[1]) |
452 if len(items) > 2: | |
453 needs_upload = (items[2] == "dirty") | |
441 files = [] | 454 files = [] |
442 for line in split_data[1].splitlines(): | 455 for line in split_data[1].splitlines(): |
443 status = line[:7] | 456 status = line[:7] |
444 filename = line[7:] | 457 filename = line[7:] |
445 files.append((status, filename)) | 458 files.append((status, filename)) |
446 description = split_data[2] | 459 description = split_data[2] |
447 save = False | 460 save = False |
448 if update_status: | 461 if update_status: |
449 for item in files: | 462 for item in files: |
450 filename = os.path.join(local_root, item[1]) | 463 filename = os.path.join(local_root, item[1]) |
451 status_result = SVN.CaptureStatus(filename) | 464 status_result = SVN.CaptureStatus(filename) |
452 if not status_result or not status_result[0][0]: | 465 if not status_result or not status_result[0][0]: |
453 # File has been reverted. | 466 # File has been reverted. |
454 save = True | 467 save = True |
455 files.remove(item) | 468 files.remove(item) |
456 continue | 469 continue |
457 status = status_result[0][0] | 470 status = status_result[0][0] |
458 if status != item[0]: | 471 if status != item[0]: |
459 save = True | 472 save = True |
460 files[files.index(item)] = (status, item[1]) | 473 files[files.index(item)] = (status, item[1]) |
461 change_info = ChangeInfo(changename, issue, patchset, description, files, | 474 change_info = ChangeInfo(changename, issue, patchset, description, files, |
462 local_root) | 475 local_root, needs_upload) |
463 if save: | 476 if save: |
464 change_info.Save() | 477 change_info.Save() |
465 return change_info | 478 return change_info |
466 | 479 |
467 | 480 |
468 def GetChangelistInfoFile(changename): | 481 def GetChangelistInfoFile(changename): |
469 """Returns the file that stores information about a changelist.""" | 482 """Returns the file that stores information about a changelist.""" |
470 if not changename or re.search(r'[^\w-]', changename): | 483 if not changename or re.search(r'[^\w-]', changename): |
471 ErrorExit("Invalid changelist name: " + changename) | 484 ErrorExit("Invalid changelist name: " + changename) |
472 return os.path.join(GetChangesDir(), changename) | 485 return os.path.join(GetChangesDir(), changename) |
473 | 486 |
474 | 487 |
475 def LoadChangelistInfoForMultiple(changenames, local_root, fail_on_not_found, | 488 def LoadChangelistInfoForMultiple(changenames, local_root, fail_on_not_found, |
476 update_status): | 489 update_status): |
477 """Loads many changes and merge their files list into one pseudo change. | 490 """Loads many changes and merge their files list into one pseudo change. |
478 | 491 |
479 This is mainly usefull to concatenate many changes into one for a 'gcl try'. | 492 This is mainly usefull to concatenate many changes into one for a 'gcl try'. |
480 """ | 493 """ |
481 changes = changenames.split(',') | 494 changes = changenames.split(',') |
482 aggregate_change_info = ChangeInfo(changenames, 0, 0, '', None, local_root) | 495 aggregate_change_info = ChangeInfo(changenames, 0, 0, '', None, local_root, |
496 needs_upload=False) | |
483 for change in changes: | 497 for change in changes: |
484 aggregate_change_info._files += ChangeInfo.Load(change, | 498 aggregate_change_info._files += ChangeInfo.Load(change, |
485 local_root, | 499 local_root, |
486 fail_on_not_found, | 500 fail_on_not_found, |
487 update_status).GetFiles() | 501 update_status).GetFiles() |
488 return aggregate_change_info | 502 return aggregate_change_info |
489 | 503 |
490 | 504 |
491 def GetCLs(): | 505 def GetCLs(): |
492 """Returns a list of all the changelists in this repository.""" | 506 """Returns a list of all the changelists in this repository.""" |
(...skipping 479 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
972 "directory.") | 986 "directory.") |
973 | 987 |
974 if (len(args) == 1): | 988 if (len(args) == 1): |
975 filename = args[0] | 989 filename = args[0] |
976 f = open(filename, 'rU') | 990 f = open(filename, 'rU') |
977 override_description = f.read() | 991 override_description = f.read() |
978 f.close() | 992 f.close() |
979 else: | 993 else: |
980 override_description = None | 994 override_description = None |
981 | 995 |
982 if change_info.issue: | 996 if change_info.issue and not change_info.NeedsUpload(): |
983 try: | 997 try: |
984 description = GetIssueDescription(change_info.issue) | 998 description = GetIssueDescription(change_info.issue) |
985 except urllib2.HTTPError, err: | 999 except urllib2.HTTPError, err: |
986 if err.code == 404: | 1000 if err.code == 404: |
987 # The user deleted the issue in Rietveld, so forget the old issue id. | 1001 # The user deleted the issue in Rietveld, so forget the old issue id. |
988 description = change_info.description | 1002 description = change_info.description |
989 change_info.issue = 0 | 1003 change_info.issue = 0 |
990 change_info.Save() | 1004 change_info.Save() |
991 else: | 1005 else: |
992 ErrorExit("Error getting the description from Rietveld: " + err) | 1006 ErrorExit("Error getting the description from Rietveld: " + err) |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1029 result = ReadFile(filename) | 1043 result = ReadFile(filename) |
1030 os.remove(filename) | 1044 os.remove(filename) |
1031 | 1045 |
1032 if not result: | 1046 if not result: |
1033 return | 1047 return |
1034 | 1048 |
1035 split_result = result.split(separator1, 1) | 1049 split_result = result.split(separator1, 1) |
1036 if len(split_result) != 2: | 1050 if len(split_result) != 2: |
1037 ErrorExit("Don't modify the text starting with ---!\n\n" + result) | 1051 ErrorExit("Don't modify the text starting with ---!\n\n" + result) |
1038 | 1052 |
1053 # Update the CL description if it has changed. | |
1039 new_description = split_result[0] | 1054 new_description = split_result[0] |
1040 cl_files_text = split_result[1] | 1055 cl_files_text = split_result[1] |
1041 if new_description != description or override_description: | 1056 if new_description != description or override_description: |
1042 change_info.description = new_description | 1057 change_info.description = new_description |
1043 if change_info.issue: | 1058 change_info.needs_upload = True |
1044 # Update the Rietveld issue with the new description. | |
1045 change_info.UpdateRietveldDescription() | |
1046 | 1059 |
1047 new_cl_files = [] | 1060 new_cl_files = [] |
1048 for line in cl_files_text.splitlines(): | 1061 for line in cl_files_text.splitlines(): |
1049 if not len(line): | 1062 if not len(line): |
1050 continue | 1063 continue |
1051 if line.startswith("---"): | 1064 if line.startswith("---"): |
1052 break | 1065 break |
1053 status = line[:7] | 1066 status = line[:7] |
1054 filename = line[7:] | 1067 filename = line[7:] |
1055 new_cl_files.append((status, filename)) | 1068 new_cl_files.append((status, filename)) |
1056 | 1069 |
1057 if (not len(change_info._files)) and (not change_info.issue) and \ | 1070 if (not len(change_info._files)) and (not change_info.issue) and \ |
1058 (not len(new_description) and (not new_cl_files)): | 1071 (not len(new_description) and (not new_cl_files)): |
1059 ErrorExit("Empty changelist not saved") | 1072 ErrorExit("Empty changelist not saved") |
1060 | 1073 |
1061 change_info._files = new_cl_files | 1074 change_info._files = new_cl_files |
1062 change_info.Save() | 1075 change_info.Save() |
1063 if svn_info.get('URL', '').startswith('http:'): | 1076 if svn_info.get('URL', '').startswith('http:'): |
1064 Warn("WARNING: Creating CL in a read-only checkout. You will not be " | 1077 Warn("WARNING: Creating CL in a read-only checkout. You will not be " |
1065 "able to commit it!") | 1078 "able to commit it!") |
1066 | 1079 |
1067 print change_info.name + " changelist saved." | 1080 print change_info.name + " changelist saved." |
1068 if change_info.MissingTests(): | 1081 if change_info.MissingTests(): |
1069 Warn("WARNING: " + MISSING_TEST_MSG) | 1082 Warn("WARNING: " + MISSING_TEST_MSG) |
1070 | 1083 |
1084 # Update the Rietveld issue. | |
1085 if change_info.issue and change_info.NeedsUpload(): | |
1086 change_info.UpdateRietveldDescription() | |
1087 change_info.needs_upload = False | |
1088 change_info.Save() | |
1089 | |
1090 | |
1071 # Valid extensions for files we want to lint. | 1091 # Valid extensions for files we want to lint. |
1072 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" | 1092 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" |
1073 DEFAULT_LINT_IGNORE_REGEX = r"" | 1093 DEFAULT_LINT_IGNORE_REGEX = r"" |
1074 | 1094 |
1075 def Lint(change_info, args): | 1095 def Lint(change_info, args): |
1076 """Runs cpplint.py on all the files in |change_info|""" | 1096 """Runs cpplint.py on all the files in |change_info|""" |
1077 try: | 1097 try: |
1078 import cpplint | 1098 import cpplint |
1079 except ImportError: | 1099 except ImportError: |
1080 ErrorExit("You need to install cpplint.py to lint C++ files.") | 1100 ErrorExit("You need to install cpplint.py to lint C++ files.") |
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1264 return 0 | 1284 return 0 |
1265 args =["svn", command] | 1285 args =["svn", command] |
1266 root = GetRepositoryRoot() | 1286 root = GetRepositoryRoot() |
1267 args.extend([os.path.join(root, x) for x in change_info.GetFileNames()]) | 1287 args.extend([os.path.join(root, x) for x in change_info.GetFileNames()]) |
1268 RunShell(args, True) | 1288 RunShell(args, True) |
1269 return 0 | 1289 return 0 |
1270 | 1290 |
1271 | 1291 |
1272 if __name__ == "__main__": | 1292 if __name__ == "__main__": |
1273 sys.exit(main()) | 1293 sys.exit(main()) |
OLD | NEW |