| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 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 """Enables directory-specific presubmit checks to run at upload and/or commit. | 6 """Enables directory-specific presubmit checks to run at upload and/or commit. |
| 7 """ | 7 """ |
| 8 | 8 |
| 9 __version__ = '1.6.1' | 9 __version__ = '1.6.1' |
| 10 | 10 |
| (...skipping 378 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 389 | 389 |
| 390 This is useful for doing line-by-line regex checks, like checking for | 390 This is useful for doing line-by-line regex checks, like checking for |
| 391 trailing whitespace. | 391 trailing whitespace. |
| 392 | 392 |
| 393 Yields: | 393 Yields: |
| 394 a 3 tuple: | 394 a 3 tuple: |
| 395 the AffectedFile instance of the current file; | 395 the AffectedFile instance of the current file; |
| 396 integer line number (1-based); and | 396 integer line number (1-based); and |
| 397 the contents of the line as a string. | 397 the contents of the line as a string. |
| 398 | 398 |
| 399 Note: The cariage return (LF or CR) is stripped off. | 399 Note: The carriage return (LF or CR) is stripped off. |
| 400 """ | 400 """ |
| 401 files = self.AffectedSourceFiles(source_file_filter) | 401 files = self.AffectedSourceFiles(source_file_filter) |
| 402 return _RightHandSideLinesImpl(files) | 402 return _RightHandSideLinesImpl(files) |
| 403 | 403 |
| 404 def ReadFile(self, file_item, mode='r'): | 404 def ReadFile(self, file_item, mode='r'): |
| 405 """Reads an arbitrary file. | 405 """Reads an arbitrary file. |
| 406 | 406 |
| 407 Deny reading anything outside the repository. | 407 Deny reading anything outside the repository. |
| 408 """ | 408 """ |
| 409 if isinstance(file_item, AffectedFile): | 409 if isinstance(file_item, AffectedFile): |
| 410 file_item = file_item.AbsoluteLocalPath() | 410 file_item = file_item.AbsoluteLocalPath() |
| 411 if not file_item.startswith(self.change.RepositoryRoot()): | 411 if not file_item.startswith(self.change.RepositoryRoot()): |
| 412 raise IOError('Access outside the repository root is denied.') | 412 raise IOError('Access outside the repository root is denied.') |
| 413 return gclient_utils.FileRead(file_item, mode) | 413 return gclient_utils.FileRead(file_item, mode) |
| 414 | 414 |
| 415 | 415 |
| 416 class AffectedFile(object): | 416 class AffectedFile(object): |
| 417 """Representation of a file in a change.""" | 417 """Representation of a file in a change.""" |
| 418 # Method could be a function | 418 # Method could be a function |
| 419 # pylint: disable=R0201 | 419 # pylint: disable=R0201 |
| 420 def __init__(self, path, action, repository_root=''): | 420 def __init__(self, path, action, repository_root=''): |
| 421 self._path = path | 421 self._path = path |
| 422 self._action = action | 422 self._action = action |
| 423 self._local_root = repository_root | 423 self._local_root = repository_root |
| 424 self._is_directory = None | 424 self._is_directory = None |
| 425 self._properties = {} | 425 self._properties = {} |
| 426 self._cached_changed_contents = None |
| 427 self._cached_new_contents = None |
| 426 logging.debug('%s(%s)' % (self.__class__.__name__, self._path)) | 428 logging.debug('%s(%s)' % (self.__class__.__name__, self._path)) |
| 427 | 429 |
| 428 def ServerPath(self): | 430 def ServerPath(self): |
| 429 """Returns a path string that identifies the file in the SCM system. | 431 """Returns a path string that identifies the file in the SCM system. |
| 430 | 432 |
| 431 Returns the empty string if the file does not exist in SCM. | 433 Returns the empty string if the file does not exist in SCM. |
| 432 """ | 434 """ |
| 433 return "" | 435 return "" |
| 434 | 436 |
| 435 def LocalPath(self): | 437 def LocalPath(self): |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 468 Deleted files are not text file.""" | 470 Deleted files are not text file.""" |
| 469 raise NotImplementedError() # Implement when needed | 471 raise NotImplementedError() # Implement when needed |
| 470 | 472 |
| 471 def NewContents(self): | 473 def NewContents(self): |
| 472 """Returns an iterator over the lines in the new version of file. | 474 """Returns an iterator over the lines in the new version of file. |
| 473 | 475 |
| 474 The new version is the file in the user's workspace, i.e. the "right hand | 476 The new version is the file in the user's workspace, i.e. the "right hand |
| 475 side". | 477 side". |
| 476 | 478 |
| 477 Contents will be empty if the file is a directory or does not exist. | 479 Contents will be empty if the file is a directory or does not exist. |
| 478 Note: The cariage returns (LF or CR) are stripped off. | 480 Note: The carriage returns (LF or CR) are stripped off. |
| 479 """ | 481 """ |
| 480 if self.IsDirectory(): | 482 if self._cached_new_contents is None: |
| 481 return [] | 483 self._cached_new_contents = [] |
| 482 else: | 484 if not self.IsDirectory(): |
| 483 return gclient_utils.FileRead(self.AbsoluteLocalPath(), | 485 try: |
| 484 'rU').splitlines() | 486 self._cached_new_contents = gclient_utils.FileRead( |
| 487 self.AbsoluteLocalPath(), 'rU').splitlines() |
| 488 except IOError: |
| 489 pass # File not found? That's fine; maybe it was deleted. |
| 490 return self._cached_new_contents[:] |
| 485 | 491 |
| 486 def OldContents(self): | 492 def OldContents(self): |
| 487 """Returns an iterator over the lines in the old version of file. | 493 """Returns an iterator over the lines in the old version of file. |
| 488 | 494 |
| 489 The old version is the file in depot, i.e. the "left hand side". | 495 The old version is the file in depot, i.e. the "left hand side". |
| 490 """ | 496 """ |
| 491 raise NotImplementedError() # Implement when needed | 497 raise NotImplementedError() # Implement when needed |
| 492 | 498 |
| 493 def OldFileTempPath(self): | 499 def OldFileTempPath(self): |
| 494 """Returns the path on local disk where the old contents resides. | 500 """Returns the path on local disk where the old contents resides. |
| 495 | 501 |
| 496 The old version is the file in depot, i.e. the "left hand side". | 502 The old version is the file in depot, i.e. the "left hand side". |
| 497 This is a read-only cached copy of the old contents. *DO NOT* try to | 503 This is a read-only cached copy of the old contents. *DO NOT* try to |
| 498 modify this file. | 504 modify this file. |
| 499 """ | 505 """ |
| 500 raise NotImplementedError() # Implement if/when needed. | 506 raise NotImplementedError() # Implement if/when needed. |
| 501 | 507 |
| 502 def ChangedContents(self): | 508 def ChangedContents(self): |
| 503 """Returns a list of tuples (line number, line text) of all new lines. | 509 """Returns a list of tuples (line number, line text) of all new lines. |
| 504 | 510 |
| 505 This relies on the scm diff output describing each changed code section | 511 This relies on the scm diff output describing each changed code section |
| 506 with a line of the form | 512 with a line of the form |
| 507 | 513 |
| 508 ^@@ <old line num>,<old size> <new line num>,<new size> @@$ | 514 ^@@ <old line num>,<old size> <new line num>,<new size> @@$ |
| 509 """ | 515 """ |
| 510 new_lines = [] | 516 if self._cached_changed_contents is not None: |
| 517 return self._cached_changed_contents[:] |
| 518 self._cached_changed_contents = [] |
| 511 line_num = 0 | 519 line_num = 0 |
| 512 | 520 |
| 513 if self.IsDirectory(): | 521 if self.IsDirectory(): |
| 514 return [] | 522 return [] |
| 515 | 523 |
| 516 for line in self.GenerateScmDiff().splitlines(): | 524 for line in self.GenerateScmDiff().splitlines(): |
| 517 m = re.match(r'^@@ [0-9\,\+\-]+ \+([0-9]+)\,[0-9]+ @@', line) | 525 m = re.match(r'^@@ [0-9\,\+\-]+ \+([0-9]+)\,[0-9]+ @@', line) |
| 518 if m: | 526 if m: |
| 519 line_num = int(m.groups(1)[0]) | 527 line_num = int(m.groups(1)[0]) |
| 520 continue | 528 continue |
| 521 if line.startswith('+') and not line.startswith('++'): | 529 if line.startswith('+') and not line.startswith('++'): |
| 522 new_lines.append((line_num, line[1:])) | 530 self._cached_changed_contents.append((line_num, line[1:])) |
| 523 if not line.startswith('-'): | 531 if not line.startswith('-'): |
| 524 line_num += 1 | 532 line_num += 1 |
| 525 return new_lines | 533 return self._cached_changed_contents[:] |
| 526 | 534 |
| 527 def __str__(self): | 535 def __str__(self): |
| 528 return self.LocalPath() | 536 return self.LocalPath() |
| 529 | 537 |
| 530 def GenerateScmDiff(self): | 538 def GenerateScmDiff(self): |
| 531 raise NotImplementedError() # Implemented in derived classes. | 539 raise NotImplementedError() # Implemented in derived classes. |
| 532 | 540 |
| 533 | 541 |
| 534 class SvnAffectedFile(AffectedFile): | 542 class SvnAffectedFile(AffectedFile): |
| 535 """Representation of a file in a change out of a Subversion checkout.""" | 543 """Representation of a file in a change out of a Subversion checkout.""" |
| (...skipping 677 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1213 except PresubmitFailure, e: | 1221 except PresubmitFailure, e: |
| 1214 print >> sys.stderr, e | 1222 print >> sys.stderr, e |
| 1215 print >> sys.stderr, 'Maybe your depot_tools is out of date?' | 1223 print >> sys.stderr, 'Maybe your depot_tools is out of date?' |
| 1216 print >> sys.stderr, 'If all fails, contact maruel@' | 1224 print >> sys.stderr, 'If all fails, contact maruel@' |
| 1217 return 2 | 1225 return 2 |
| 1218 | 1226 |
| 1219 | 1227 |
| 1220 if __name__ == '__main__': | 1228 if __name__ == '__main__': |
| 1221 fix_encoding.fix_encoding() | 1229 fix_encoding.fix_encoding() |
| 1222 sys.exit(Main(None)) | 1230 sys.exit(Main(None)) |
| OLD | NEW |