| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 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 |
| 4 # found in the LICENSE file. |
| 5 |
| 2 # Wrapper script around Rietveld's upload.py that groups files into | 6 # Wrapper script around Rietveld's upload.py that groups files into |
| 3 # changelists. | 7 # changelists. |
| 4 | 8 |
| 5 import getpass | 9 import getpass |
| 6 import linecache | 10 import linecache |
| 7 import os | 11 import os |
| 8 import random | 12 import random |
| 9 import re | 13 import re |
| 10 import string | 14 import string |
| 11 import subprocess | 15 import subprocess |
| 12 import sys | 16 import sys |
| 13 import tempfile | 17 import tempfile |
| 14 import upload | 18 import upload |
| 15 import urllib2 | 19 import urllib2 |
| 16 | 20 |
| 17 CODEREVIEW_SETTINGS = { | 21 CODEREVIEW_SETTINGS = { |
| 18 # Default values. | 22 # Default values. |
| 19 "CODE_REVIEW_SERVER": "codereview.chromium.org", | 23 "CODE_REVIEW_SERVER": "codereview.chromium.org", |
| 20 "CC_LIST": "chromium-reviews@googlegroups.com", | 24 "CC_LIST": "chromium-reviews@googlegroups.com", |
| 21 "VIEW_VC": "http://src.chromium.org/viewvc/chrome?view=rev&revision=", | 25 "VIEW_VC": "http://src.chromium.org/viewvc/chrome?view=rev&revision=", |
| 22 } | 26 } |
| 23 | 27 |
| 24 # Use a shell for subcommands on Windows to get a PATH search, and because svn | 28 # Use a shell for subcommands on Windows to get a PATH search, and because svn |
| 25 # may be a batch file. | 29 # may be a batch file. |
| 26 use_shell = sys.platform.startswith("win") | 30 use_shell = sys.platform.startswith("win") |
| 27 | 31 |
| 28 # globals that store the root of the current repositary and the directory where | 32 # globals that store the root of the current repository and the directory where |
| 29 # we store information about changelists. | 33 # we store information about changelists. |
| 30 repository_root = "" | 34 repository_root = "" |
| 31 gcl_info_dir = "" | 35 gcl_info_dir = "" |
| 32 | 36 |
| 33 # Filename where we store repository specific information for gcl. | 37 # Filename where we store repository specific information for gcl. |
| 34 CODEREVIEW_SETTINGS_FILE = "codereview.settings" | 38 CODEREVIEW_SETTINGS_FILE = "codereview.settings" |
| 35 | 39 |
| 40 # Warning message when the change appears to be missing tests. |
| 41 MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!" |
| 42 |
| 36 # Caches whether we read the codereview.settings file yet or not. | 43 # Caches whether we read the codereview.settings file yet or not. |
| 37 read_gcl_info = False | 44 read_gcl_info = False |
| 38 | 45 |
| 39 | 46 |
| 40 def GetSVNFileInfo(file, field): | 47 def GetSVNFileInfo(file, field): |
| 41 """Returns a field from the svn info output for the given file.""" | 48 """Returns a field from the svn info output for the given file.""" |
| 42 output = RunShell(["svn", "info", file]) | 49 output = RunShell(["svn", "info", file]) |
| 43 for line in output.splitlines(): | 50 for line in output.splitlines(): |
| 44 search = field + ": " | 51 search = field + ": " |
| 45 if line.startswith(search): | 52 if line.startswith(search): |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 114 | 121 |
| 115 def IsTreeOpen(): | 122 def IsTreeOpen(): |
| 116 """Fetches the tree status and returns either True or False.""" | 123 """Fetches the tree status and returns either True or False.""" |
| 117 url = GetCodeReviewSetting('STATUS') | 124 url = GetCodeReviewSetting('STATUS') |
| 118 status = "" | 125 status = "" |
| 119 if url: | 126 if url: |
| 120 status = urllib2.urlopen(url).read() | 127 status = urllib2.urlopen(url).read() |
| 121 return status.find('0') == -1 | 128 return status.find('0') == -1 |
| 122 | 129 |
| 123 | 130 |
| 124 def ErrorExit(msg): | 131 def Warn(msg): |
| 125 """Print an error message to stderr and exit.""" | 132 ErrorExit(msg, exit=False) |
| 133 |
| 134 |
| 135 def ErrorExit(msg, exit=True): |
| 136 """Print an error message to stderr and optionally exit.""" |
| 126 print >>sys.stderr, msg | 137 print >>sys.stderr, msg |
| 127 sys.exit(1) | 138 sys.exit(1) |
| 128 | 139 |
| 129 | 140 |
| 130 def RunShellWithReturnCode(command, print_output=False): | 141 def RunShellWithReturnCode(command, print_output=False): |
| 131 """Executes a command and returns the output and the return code.""" | 142 """Executes a command and returns the output and the return code.""" |
| 132 p = subprocess.Popen(command, stdout=subprocess.PIPE, | 143 p = subprocess.Popen(command, stdout=subprocess.PIPE, |
| 133 stderr=subprocess.STDOUT, shell=use_shell, | 144 stderr=subprocess.STDOUT, shell=use_shell, |
| 134 universal_newlines=True) | 145 universal_newlines=True) |
| 135 if print_output: | 146 if print_output: |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 175 issue: the Rietveld issue number, of "" if it hasn't been uploaded yet. | 186 issue: the Rietveld issue number, of "" if it hasn't been uploaded yet. |
| 176 description: the description. | 187 description: the description. |
| 177 files: a list of 2 tuple containing (status, filename) of changed files, | 188 files: a list of 2 tuple containing (status, filename) of changed files, |
| 178 with paths being relative to the top repository directory. | 189 with paths being relative to the top repository directory. |
| 179 """ | 190 """ |
| 180 def __init__(self, name="", issue="", description="", files=[]): | 191 def __init__(self, name="", issue="", description="", files=[]): |
| 181 self.name = name | 192 self.name = name |
| 182 self.issue = issue | 193 self.issue = issue |
| 183 self.description = description | 194 self.description = description |
| 184 self.files = files | 195 self.files = files |
| 196 self.patch = None |
| 185 | 197 |
| 186 def FileList(self): | 198 def FileList(self): |
| 187 """Returns a list of files.""" | 199 """Returns a list of files.""" |
| 188 return [file[1] for file in self.files] | 200 return [file[1] for file in self.files] |
| 189 | 201 |
| 202 def _NonDeletedFileList(self): |
| 203 """Returns a list of files in this change, not including deleted files.""" |
| 204 return [file[1] for file in self.files if file[0] != "D"] |
| 205 |
| 190 def Save(self): | 206 def Save(self): |
| 191 """Writes the changelist information to disk.""" | 207 """Writes the changelist information to disk.""" |
| 192 data = SEPARATOR.join([self.issue, | 208 data = SEPARATOR.join([self.issue, |
| 193 "\n".join([f[0] + f[1] for f in self.files]), | 209 "\n".join([f[0] + f[1] for f in self.files]), |
| 194 self.description]) | 210 self.description]) |
| 195 WriteFile(GetChangelistInfoFile(self.name), data) | 211 WriteFile(GetChangelistInfoFile(self.name), data) |
| 196 | 212 |
| 197 def Delete(self): | 213 def Delete(self): |
| 198 """Removes the changelist information from disk.""" | 214 """Removes the changelist information from disk.""" |
| 199 os.remove(GetChangelistInfoFile(self.name)) | 215 os.remove(GetChangelistInfoFile(self.name)) |
| 200 | 216 |
| 201 def CloseIssue(self): | 217 def CloseIssue(self): |
| 202 """Closes the Rietveld issue for this changelist.""" | 218 """Closes the Rietveld issue for this changelist.""" |
| 203 data = [("description", self.description),] | 219 data = [("description", self.description),] |
| 204 ctype, body = upload.EncodeMultipartFormData(data, []) | 220 ctype, body = upload.EncodeMultipartFormData(data, []) |
| 205 SendToRietveld("/" + self.issue + "/close", body, ctype) | 221 SendToRietveld("/" + self.issue + "/close", body, ctype) |
| 206 | 222 |
| 207 def UpdateRietveldDescription(self): | 223 def UpdateRietveldDescription(self): |
| 208 """Sets the description for an issue on Rietveld.""" | 224 """Sets the description for an issue on Rietveld.""" |
| 209 data = [("description", self.description),] | 225 data = [("description", self.description),] |
| 210 ctype, body = upload.EncodeMultipartFormData(data, []) | 226 ctype, body = upload.EncodeMultipartFormData(data, []) |
| 211 SendToRietveld("/" + self.issue + "/description", body, ctype) | 227 SendToRietveld("/" + self.issue + "/description", body, ctype) |
| 212 | 228 |
| 213 | 229 def MissingTests(self): |
| 230 """Returns True if the change looks like it needs unit tests but has none. |
| 231 |
| 232 A change needs unit tests if it contains any new source files or methods. |
| 233 """ |
| 234 SOURCE_SUFFIXES = [".cc", ".cpp", ".c", ".m", ".mm"] |
| 235 # Ignore third_party entirely. |
| 236 files = [file for file in self._NonDeletedFileList() |
| 237 if file.find("third_party") == -1] |
| 238 |
| 239 # Any new or modified test files? |
| 240 # A test file's name ends with "test.*" or "tests.*". |
| 241 test_files = [test for test in files |
| 242 if os.path.splitext(test)[0].rstrip("s").endswith("test")] |
| 243 if len(test_files) > 0: |
| 244 return False |
| 245 |
| 246 # Any new source files? |
| 247 source_files = [file for file in files |
| 248 if os.path.splitext(file)[1] in SOURCE_SUFFIXES] |
| 249 if len(source_files) > 0: |
| 250 return True |
| 251 |
| 252 # Do the long test, checking the files for new methods. |
| 253 return self._HasNewMethod() |
| 254 |
| 255 def _HasNewMethod(self): |
| 256 """Returns True if the changeset contains any new functions, or if a |
| 257 function signature has been changed. |
| 258 |
| 259 A function is identified by starting flush left, containing a "(" before |
| 260 the next flush-left line, and either ending with "{" before the next |
| 261 flush-left line or being followed by an unindented "{". |
| 262 |
| 263 Currently this returns True for new methods, new static functions, and |
| 264 methods or functions whose signatures have been changed. |
| 265 |
| 266 Inline methods added to header files won't be detected by this. That's |
| 267 acceptable for purposes of determining if a unit test is needed, since |
| 268 inline methods should be trivial. |
| 269 """ |
| 270 # To check for methods added to source or header files, we need the diffs. |
| 271 # We'll generate them all, since there aren't likely to be many files |
| 272 # apart from source and headers; besides, we'll want them all if we're |
| 273 # uploading anyway. |
| 274 if self.patch is None: |
| 275 self.patch = GenerateDiff(self.FileList()) |
| 276 |
| 277 definition = "" |
| 278 for line in self.patch.splitlines(): |
| 279 if not line.startswith("+"): |
| 280 continue |
| 281 line = line.strip("+").rstrip(" \t") |
| 282 # Skip empty lines, comments, and preprocessor directives. |
| 283 # TODO(pamg): Handle multiline comments if it turns out to be a problem. |
| 284 if line == "" or line.startswith("/") or line.startswith("#"): |
| 285 continue |
| 286 |
| 287 # A possible definition ending with "{" is complete, so check it. |
| 288 if definition.endswith("{"): |
| 289 if definition.find("(") != -1: |
| 290 return True |
| 291 definition = "" |
| 292 |
| 293 # A { or an indented line, when we're in a definition, continues it. |
| 294 if (definition != "" and |
| 295 (line == "{" or line.startswith(" ") or line.startswith("\t"))): |
| 296 definition += line |
| 297 |
| 298 # A flush-left line starts a new possible function definition. |
| 299 elif not line.startswith(" ") and not line.startswith("\t"): |
| 300 definition = line |
| 301 |
| 302 return False |
| 303 |
| 304 |
| 214 SEPARATOR = "\n-----\n" | 305 SEPARATOR = "\n-----\n" |
| 215 # The info files have the following format: | 306 # The info files have the following format: |
| 216 # issue_id\n | 307 # issue_id\n |
| 217 # SEPARATOR\n | 308 # SEPARATOR\n |
| 218 # filepath1\n | 309 # filepath1\n |
| 219 # filepath2\n | 310 # filepath2\n |
| 220 # . | 311 # . |
| 221 # . | 312 # . |
| 222 # filepathn\n | 313 # filepathn\n |
| 223 # SEPARATOR\n | 314 # SEPARATOR\n |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 310 random.choice(string.ascii_lowercase) + | 401 random.choice(string.ascii_lowercase) + |
| 311 random.choice(string.digits)) | 402 random.choice(string.digits)) |
| 312 if cl_name not in current_cl_names: | 403 if cl_name not in current_cl_names: |
| 313 return cl_name | 404 return cl_name |
| 314 | 405 |
| 315 | 406 |
| 316 def GetModifiedFiles(): | 407 def GetModifiedFiles(): |
| 317 """Returns a set that maps from changelist name to (status,filename) tuples. | 408 """Returns a set that maps from changelist name to (status,filename) tuples. |
| 318 | 409 |
| 319 Files not in a changelist have an empty changelist name. Filenames are in | 410 Files not in a changelist have an empty changelist name. Filenames are in |
| 320 relation to the top level directory of the current repositary. Note that | 411 relation to the top level directory of the current repository. Note that |
| 321 only the current directory and subdirectories are scanned, in order to | 412 only the current directory and subdirectories are scanned, in order to |
| 322 improve performance while still being flexible. | 413 improve performance while still being flexible. |
| 323 """ | 414 """ |
| 324 files = {} | 415 files = {} |
| 325 | 416 |
| 326 # Since the files are normalized to the root folder of the repositary, figure | 417 # Since the files are normalized to the root folder of the repositary, figure |
| 327 # out what we need to add to the paths. | 418 # out what we need to add to the paths. |
| 328 dir_prefix = os.getcwd()[len(GetRepositoryRoot()):].strip(os.sep) | 419 dir_prefix = os.getcwd()[len(GetRepositoryRoot()):].strip(os.sep) |
| 329 | 420 |
| 330 # Get a list of all files in changelists. | 421 # Get a list of all files in changelists. |
| 331 files_in_cl = {} | 422 files_in_cl = {} |
| 332 for cl in GetCLs(): | 423 for cl in GetCLs(): |
| 333 change_info = LoadChangelistInfo(cl) | 424 change_info = LoadChangelistInfo(cl) |
| 334 for status, filename in change_info.files: | 425 for status, filename in change_info.files: |
| 335 files_in_cl[filename] = change_info.name | 426 files_in_cl[filename] = change_info.name |
| (...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 494 if sys.platform.startswith("win"): | 585 if sys.platform.startswith("win"): |
| 495 editor = "notepad" | 586 editor = "notepad" |
| 496 else: | 587 else: |
| 497 editor = "vi" | 588 editor = "vi" |
| 498 | 589 |
| 499 return editor | 590 return editor |
| 500 | 591 |
| 501 | 592 |
| 502 def GenerateDiff(files): | 593 def GenerateDiff(files): |
| 503 """Returns a string containing the diff for the given file list.""" | 594 """Returns a string containing the diff for the given file list.""" |
| 595 previous_cwd = os.getcwd() |
| 596 os.chdir(GetRepositoryRoot()) |
| 597 |
| 504 diff = [] | 598 diff = [] |
| 505 for file in files: | 599 for file in files: |
| 506 # Use svn info output instead of os.path.isdir because the latter fails | 600 # Use svn info output instead of os.path.isdir because the latter fails |
| 507 # when the file is deleted. | 601 # when the file is deleted. |
| 508 if GetSVNFileInfo(file, "Node Kind") == "directory": | 602 if GetSVNFileInfo(file, "Node Kind") == "directory": |
| 509 continue | 603 continue |
| 510 # If the user specified a custom diff command in their svn config file, | 604 # If the user specified a custom diff command in their svn config file, |
| 511 # then it'll be used when we do svn diff, which we don't want to happen | 605 # then it'll be used when we do svn diff, which we don't want to happen |
| 512 # since we want the unified diff. Using --diff-cmd=diff doesn't always | 606 # since we want the unified diff. Using --diff-cmd=diff doesn't always |
| 513 # work, since they can have another diff executable in their path that | 607 # work, since they can have another diff executable in their path that |
| 514 # gives different line endings. So we use a bogus temp directory as the | 608 # gives different line endings. So we use a bogus temp directory as the |
| 515 # config directory, which gets around these problems. | 609 # config directory, which gets around these problems. |
| 516 if sys.platform.startswith("win"): | 610 if sys.platform.startswith("win"): |
| 517 parent_dir = tempfile.gettempdir() | 611 parent_dir = tempfile.gettempdir() |
| 518 else: | 612 else: |
| 519 parent_dir = sys.path[0] # tempdir is not secure. | 613 parent_dir = sys.path[0] # tempdir is not secure. |
| 520 bogus_dir = os.path.join(parent_dir, "temp_svn_config") | 614 bogus_dir = os.path.join(parent_dir, "temp_svn_config") |
| 521 if not os.path.exists(bogus_dir): | 615 if not os.path.exists(bogus_dir): |
| 522 os.mkdir(bogus_dir) | 616 os.mkdir(bogus_dir) |
| 523 diff.append(RunShell(["svn", "diff", "--config-dir", bogus_dir, file])) | 617 diff.append(RunShell(["svn", "diff", "--config-dir", bogus_dir, file])) |
| 618 os.chdir(previous_cwd) |
| 524 return "".join(diff) | 619 return "".join(diff) |
| 525 | 620 |
| 526 | 621 |
| 527 def UploadCL(change_info, args): | 622 def UploadCL(change_info, args): |
| 528 if not change_info.FileList(): | 623 if not change_info.FileList(): |
| 529 print "Nothing to upload, changelist is empty." | 624 print "Nothing to upload, changelist is empty." |
| 530 return | 625 return |
| 531 | 626 |
| 532 no_try = "--no_try" in args | 627 no_try = "--no_try" in args |
| 533 if no_try: | 628 if no_try: |
| 534 args.remove("--no_try") | 629 args.remove("--no_try") |
| 535 | 630 |
| 631 # TODO(pamg): Do something when tests are missing. The plan is to upload a |
| 632 # message to Rietveld and have it shown in the UI attached to this patch. |
| 633 |
| 536 upload_arg = ["upload.py", "-y"] | 634 upload_arg = ["upload.py", "-y"] |
| 537 upload_arg.append("--server=" + GetCodeReviewSetting("CODE_REVIEW_SERVER")) | 635 upload_arg.append("--server=" + GetCodeReviewSetting("CODE_REVIEW_SERVER")) |
| 538 upload_arg.extend(args) | 636 upload_arg.extend(args) |
| 539 | 637 |
| 540 desc_file = "" | 638 desc_file = "" |
| 541 if change_info.issue: # Uploading a new patchset. | 639 if change_info.issue: # Uploading a new patchset. |
| 542 found_message = False | 640 found_message = False |
| 543 for arg in args: | 641 for arg in args: |
| 544 if arg.startswith("--message") or arg.startswith("-m"): | 642 if arg.startswith("--message") or arg.startswith("-m"): |
| 545 found_message = True | 643 found_message = True |
| (...skipping 12 matching lines...) Expand all Loading... |
| 558 upload_arg.append("--description_file=" + desc_file + "") | 656 upload_arg.append("--description_file=" + desc_file + "") |
| 559 if change_info.description: | 657 if change_info.description: |
| 560 subject = change_info.description[:77] | 658 subject = change_info.description[:77] |
| 561 if subject.find("\r\n") != -1: | 659 if subject.find("\r\n") != -1: |
| 562 subject = subject[:subject.find("\r\n")] | 660 subject = subject[:subject.find("\r\n")] |
| 563 if subject.find("\n") != -1: | 661 if subject.find("\n") != -1: |
| 564 subject = subject[:subject.find("\n")] | 662 subject = subject[:subject.find("\n")] |
| 565 if len(change_info.description) > 77: | 663 if len(change_info.description) > 77: |
| 566 subject = subject + "..." | 664 subject = subject + "..." |
| 567 upload_arg.append("--message=" + subject) | 665 upload_arg.append("--message=" + subject) |
| 568 | 666 |
| 569 # Change the current working directory before calling upload.py so that it | 667 # Change the current working directory before calling upload.py so that it |
| 570 # shows the correct base. | 668 # shows the correct base. |
| 669 previous_cwd = os.getcwd() |
| 571 os.chdir(GetRepositoryRoot()) | 670 os.chdir(GetRepositoryRoot()) |
| 572 | 671 |
| 573 # If we have a lot of files with long paths, then we won't be able to fit | 672 # If we have a lot of files with long paths, then we won't be able to fit |
| 574 # the command to "svn diff". Instead, we generate the diff manually for | 673 # the command to "svn diff". Instead, we generate the diff manually for |
| 575 # each file and concatenate them before passing it to upload.py. | 674 # each file and concatenate them before passing it to upload.py. |
| 576 issue, patchset = upload.RealMain(upload_arg, | 675 if change_info.patch is None: |
| 577 GenerateDiff(change_info.FileList())) | 676 change_info.patch = GenerateDiff(change_info.FileList()) |
| 677 issue, patchset = upload.RealMain(upload_arg, change_info.patch) |
| 578 if issue and issue != change_info.issue: | 678 if issue and issue != change_info.issue: |
| 579 change_info.issue = issue | 679 change_info.issue = issue |
| 580 change_info.Save() | 680 change_info.Save() |
| 581 | 681 |
| 582 if desc_file: | 682 if desc_file: |
| 583 os.remove(desc_file) | 683 os.remove(desc_file) |
| 584 | 684 |
| 585 # Do background work on Rietveld to lint the file so that the results are | 685 # Do background work on Rietveld to lint the file so that the results are |
| 586 # ready when the issue is viewed. | 686 # ready when the issue is viewed. |
| 587 SendToRietveld("/lint/issue%s_%s" % (issue, patchset), timeout=0.5) | 687 SendToRietveld("/lint/issue%s_%s" % (issue, patchset), timeout=0.5) |
| 588 | 688 |
| 589 # Once uploaded to Rietveld, send it to the try server. | 689 # Once uploaded to Rietveld, send it to the try server. |
| 590 if not no_try and GetCodeReviewSetting('TRY_ON_UPLOAD').lower() == 'true': | 690 if not no_try and GetCodeReviewSetting('TRY_ON_UPLOAD').lower() == 'true': |
| 591 # Use the local diff. | 691 # Use the local diff. |
| 592 TryChange(change_info, [], True) | 692 TryChange(change_info, [], True) |
| 593 | 693 |
| 694 os.chdir(previous_cwd) |
| 695 |
| 594 | 696 |
| 595 def TryChange(change_info, args, swallow_exception=False, patchset=None): | 697 def TryChange(change_info, args, swallow_exception=False, patchset=None): |
| 596 """Create a diff file of change_info and send it to the try server.""" | 698 """Create a diff file of change_info and send it to the try server.""" |
| 597 try: | 699 try: |
| 598 import trychange | 700 import trychange |
| 599 except ImportError: | 701 except ImportError: |
| 600 if swallow_exception: | 702 if swallow_exception: |
| 601 return | 703 return |
| 602 ErrorExit("You need to install trychange.py to use the try server.") | 704 ErrorExit("You need to install trychange.py to use the try server.") |
| 603 | 705 |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 635 os.write(handle, commit_message) | 737 os.write(handle, commit_message) |
| 636 os.close(handle) | 738 os.close(handle) |
| 637 | 739 |
| 638 handle, targets_filename = tempfile.mkstemp(text=True) | 740 handle, targets_filename = tempfile.mkstemp(text=True) |
| 639 os.write(handle, "\n".join(change_info.FileList())) | 741 os.write(handle, "\n".join(change_info.FileList())) |
| 640 os.close(handle) | 742 os.close(handle) |
| 641 | 743 |
| 642 commit_cmd += ['--file=' + commit_filename] | 744 commit_cmd += ['--file=' + commit_filename] |
| 643 commit_cmd += ['--targets=' + targets_filename] | 745 commit_cmd += ['--targets=' + targets_filename] |
| 644 # Change the current working directory before calling commit. | 746 # Change the current working directory before calling commit. |
| 747 previous_cwd = os.getcwd() |
| 645 os.chdir(GetRepositoryRoot()) | 748 os.chdir(GetRepositoryRoot()) |
| 646 output = RunShell(commit_cmd, True) | 749 output = RunShell(commit_cmd, True) |
| 647 os.remove(commit_filename) | 750 os.remove(commit_filename) |
| 648 os.remove(targets_filename) | 751 os.remove(targets_filename) |
| 649 if output.find("Committed revision") != -1: | 752 if output.find("Committed revision") != -1: |
| 650 change_info.Delete() | 753 change_info.Delete() |
| 651 | 754 |
| 652 if change_info.issue: | 755 if change_info.issue: |
| 653 revision = re.compile(".*?\nCommitted revision (\d+)", | 756 revision = re.compile(".*?\nCommitted revision (\d+)", |
| 654 re.DOTALL).match(output).group(1) | 757 re.DOTALL).match(output).group(1) |
| 655 viewvc_url = GetCodeReviewSetting("VIEW_VC") | 758 viewvc_url = GetCodeReviewSetting("VIEW_VC") |
| 656 change_info.description = (change_info.description + | 759 change_info.description = (change_info.description + |
| 657 "\n\nCommitted: " + viewvc_url + revision) | 760 "\n\nCommitted: " + viewvc_url + revision) |
| 658 change_info.CloseIssue() | 761 change_info.CloseIssue() |
| 762 os.chdir(previous_cwd) |
| 659 | 763 |
| 660 | 764 |
| 661 def Change(change_info): | 765 def Change(change_info): |
| 662 """Creates/edits a changelist.""" | 766 """Creates/edits a changelist.""" |
| 663 if change_info.issue: | 767 if change_info.issue: |
| 664 try: | 768 try: |
| 665 description = GetIssueDescription(change_info.issue) | 769 description = GetIssueDescription(change_info.issue) |
| 666 except urllib2.HTTPError, err: | 770 except urllib2.HTTPError, err: |
| 667 if err.code == 404: | 771 if err.code == 404: |
| 668 # The user deleted the issue in Rietveld, so forget the old issue id. | 772 # The user deleted the issue in Rietveld, so forget the old issue id. |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 714 continue | 818 continue |
| 715 if line.startswith("---"): | 819 if line.startswith("---"): |
| 716 break | 820 break |
| 717 status = line[:7] | 821 status = line[:7] |
| 718 file = line[7:] | 822 file = line[7:] |
| 719 new_cl_files.append((status, file)) | 823 new_cl_files.append((status, file)) |
| 720 change_info.files = new_cl_files | 824 change_info.files = new_cl_files |
| 721 | 825 |
| 722 change_info.Save() | 826 change_info.Save() |
| 723 print change_info.name + " changelist saved." | 827 print change_info.name + " changelist saved." |
| 828 if change_info.MissingTests(): |
| 829 Warn("WARNING: " + MISSING_TEST_MSG) |
| 724 | 830 |
| 725 # We don't lint files in these path prefixes. | 831 # We don't lint files in these path prefixes. |
| 726 IGNORE_PATHS = ("webkit",) | 832 IGNORE_PATHS = ("webkit",) |
| 727 | 833 |
| 728 # Valid extensions for files we want to lint. | 834 # Valid extensions for files we want to lint. |
| 729 CPP_EXTENSIONS = ("cpp", "cc", "h") | 835 CPP_EXTENSIONS = ("cpp", "cc", "h") |
| 730 | 836 |
| 731 def Lint(change_info, args): | 837 def Lint(change_info, args): |
| 732 """Runs cpplint.py on all the files in |change_info|""" | 838 """Runs cpplint.py on all the files in |change_info|""" |
| 733 try: | 839 try: |
| 734 import cpplint | 840 import cpplint |
| 735 except ImportError: | 841 except ImportError: |
| 736 ErrorExit("You need to install cpplint.py to lint C++ files.") | 842 ErrorExit("You need to install cpplint.py to lint C++ files.") |
| 737 | 843 |
| 738 # Change the current working directory before calling lint so that it | 844 # Change the current working directory before calling lint so that it |
| 739 # shows the correct base. | 845 # shows the correct base. |
| 846 previous_cwd = os.getcwd() |
| 740 os.chdir(GetRepositoryRoot()) | 847 os.chdir(GetRepositoryRoot()) |
| 741 | 848 |
| 742 # Process cpplints arguments if any. | 849 # Process cpplints arguments if any. |
| 743 filenames = cpplint.ParseArguments(args + change_info.FileList()) | 850 filenames = cpplint.ParseArguments(args + change_info.FileList()) |
| 744 | 851 |
| 745 for file in filenames: | 852 for file in filenames: |
| 746 if len([file for suffix in CPP_EXTENSIONS if file.endswith(suffix)]): | 853 if len([file for suffix in CPP_EXTENSIONS if file.endswith(suffix)]): |
| 747 if len([file for prefix in IGNORE_PATHS if file.startswith(prefix)]): | 854 if len([file for prefix in IGNORE_PATHS if file.startswith(prefix)]): |
| 748 print "Ignoring non-Google styled file %s" % file | 855 print "Ignoring non-Google styled file %s" % file |
| 749 else: | 856 else: |
| 750 cpplint.ProcessFile(file, cpplint._cpplint_state.verbose_level) | 857 cpplint.ProcessFile(file, cpplint._cpplint_state.verbose_level) |
| 751 | 858 |
| 752 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count | 859 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count |
| 860 os.chdir(previous_cwd) |
| 753 | 861 |
| 754 | 862 |
| 755 def Changes(): | 863 def Changes(): |
| 756 """Print all the changlists and their files.""" | 864 """Print all the changelists and their files.""" |
| 757 for cl in GetCLs(): | 865 for cl in GetCLs(): |
| 758 change_info = LoadChangelistInfo(cl, True, True) | 866 change_info = LoadChangelistInfo(cl, True, True) |
| 759 print "\n--- Changelist " + change_info.name + ":" | 867 print "\n--- Changelist " + change_info.name + ":" |
| 760 for file in change_info.files: | 868 for file in change_info.files: |
| 761 print "".join(file) | 869 print "".join(file) |
| 762 | 870 |
| 763 | 871 |
| 764 def main(argv=None): | 872 def main(argv=None): |
| 765 if argv is None: | 873 if argv is None: |
| 766 argv = sys.argv | 874 argv = sys.argv |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 839 # the files. This allows commands such as 'gcl diff xxx' to work. | 947 # the files. This allows commands such as 'gcl diff xxx' to work. |
| 840 args =["svn", command] | 948 args =["svn", command] |
| 841 root = GetRepositoryRoot() | 949 root = GetRepositoryRoot() |
| 842 args.extend([os.path.join(root, x) for x in change_info.FileList()]) | 950 args.extend([os.path.join(root, x) for x in change_info.FileList()]) |
| 843 RunShell(args, True) | 951 RunShell(args, True) |
| 844 return 0 | 952 return 0 |
| 845 | 953 |
| 846 | 954 |
| 847 if __name__ == "__main__": | 955 if __name__ == "__main__": |
| 848 sys.exit(main()) | 956 sys.exit(main()) |
| OLD | NEW |