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 |