Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(530)

Side by Side Diff: presubmit_support.py

Issue 2394043002: Remove SVN support from PRESUBMIT (Closed)
Patch Set: Updated patchset dependency Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « presubmit_canned_checks.py ('k') | tests/presubmit_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 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.8.0' 9 __version__ = '1.8.0'
10 10
(...skipping 393 matching lines...) Expand 10 before | Expand all | Expand 10 after
404 def PresubmitLocalPath(self): 404 def PresubmitLocalPath(self):
405 """Returns the local path of the presubmit script currently being run. 405 """Returns the local path of the presubmit script currently being run.
406 406
407 This is useful if you don't want to hard-code absolute paths in the 407 This is useful if you don't want to hard-code absolute paths in the
408 presubmit script. For example, It can be used to find another file 408 presubmit script. For example, It can be used to find another file
409 relative to the PRESUBMIT.py script, so the whole tree can be branched and 409 relative to the PRESUBMIT.py script, so the whole tree can be branched and
410 the presubmit script still works, without editing its content. 410 the presubmit script still works, without editing its content.
411 """ 411 """
412 return self._current_presubmit_path 412 return self._current_presubmit_path
413 413
414 def DepotToLocalPath(self, depot_path): 414 def AffectedFiles(self, include_deletes=True, file_filter=None):
415 """Translate a depot path to a local path (relative to client root).
416
417 Args:
418 Depot path as a string.
419
420 Returns:
421 The local path of the depot path under the user's current client, or None
422 if the file is not mapped.
423
424 Remember to check for the None case and show an appropriate error!
425 """
426 return scm.SVN.CaptureLocalInfo([depot_path], self.change.RepositoryRoot()
427 ).get('Path')
428
429 def LocalToDepotPath(self, local_path):
430 """Translate a local path to a depot path.
431
432 Args:
433 Local path (relative to current directory, or absolute) as a string.
434
435 Returns:
436 The depot path (SVN URL) of the file if mapped, otherwise None.
437 """
438 return scm.SVN.CaptureLocalInfo([local_path], self.change.RepositoryRoot()
439 ).get('URL')
440
441 def AffectedFiles(self, include_dirs=False, include_deletes=True,
Dan Beam 2016/11/29 19:13:45 can you grep for keywords when you remove them nex
Dan Beam 2016/11/29 19:16:29 "this broke" -> removing include_dirs=
442 file_filter=None):
443 """Same as input_api.change.AffectedFiles() except only lists files 415 """Same as input_api.change.AffectedFiles() except only lists files
444 (and optionally directories) in the same directory as the current presubmit 416 (and optionally directories) in the same directory as the current presubmit
445 script, or subdirectories thereof. 417 script, or subdirectories thereof.
446 """ 418 """
447 dir_with_slash = normpath("%s/" % self.PresubmitLocalPath()) 419 dir_with_slash = normpath("%s/" % self.PresubmitLocalPath())
448 if len(dir_with_slash) == 1: 420 if len(dir_with_slash) == 1:
449 dir_with_slash = '' 421 dir_with_slash = ''
450 422
451 return filter( 423 return filter(
452 lambda x: normpath(x.AbsoluteLocalPath()).startswith(dir_with_slash), 424 lambda x: normpath(x.AbsoluteLocalPath()).startswith(dir_with_slash),
453 self.change.AffectedFiles(include_dirs, include_deletes, file_filter)) 425 self.change.AffectedFiles(include_deletes, file_filter))
454 426
455 def LocalPaths(self, include_dirs=False): 427 def LocalPaths(self):
456 """Returns local paths of input_api.AffectedFiles().""" 428 """Returns local paths of input_api.AffectedFiles()."""
457 paths = [af.LocalPath() for af in self.AffectedFiles(include_dirs)] 429 paths = [af.LocalPath() for af in self.AffectedFiles()]
458 logging.debug("LocalPaths: %s", paths) 430 logging.debug("LocalPaths: %s", paths)
459 return paths 431 return paths
460 432
461 def AbsoluteLocalPaths(self, include_dirs=False): 433 def AbsoluteLocalPaths(self):
462 """Returns absolute local paths of input_api.AffectedFiles().""" 434 """Returns absolute local paths of input_api.AffectedFiles()."""
463 return [af.AbsoluteLocalPath() for af in self.AffectedFiles(include_dirs)] 435 return [af.AbsoluteLocalPath() for af in self.AffectedFiles()]
464 436
465 def ServerPaths(self, include_dirs=False): 437 def AffectedTestableFiles(self, include_deletes=None):
466 """Returns server paths of input_api.AffectedFiles().""" 438 """Same as input_api.change.AffectedTestableFiles() except only lists files
467 return [af.ServerPath() for af in self.AffectedFiles(include_dirs)]
468
469 def AffectedTextFiles(self, include_deletes=None):
470 """Same as input_api.change.AffectedTextFiles() except only lists files
471 in the same directory as the current presubmit script, or subdirectories 439 in the same directory as the current presubmit script, or subdirectories
472 thereof. 440 thereof.
473 """ 441 """
474 if include_deletes is not None: 442 if include_deletes is not None:
475 warn("AffectedTextFiles(include_deletes=%s)" 443 warn("AffectedTestableFiles(include_deletes=%s)"
476 " is deprecated and ignored" % str(include_deletes), 444 " is deprecated and ignored" % str(include_deletes),
477 category=DeprecationWarning, 445 category=DeprecationWarning,
478 stacklevel=2) 446 stacklevel=2)
479 return filter(lambda x: x.IsTextFile(), 447 return filter(lambda x: x.IsTestableFile(),
480 self.AffectedFiles(include_dirs=False, include_deletes=False)) 448 self.AffectedFiles(include_deletes=False))
449
450 def AffectedTextFiles(self, include_deletes=None):
451 """An alias to AffectedTestableFiles for backwards compatibility."""
452 return self.AffectedTestableFiles(include_deletes=include_deletes)
481 453
482 def FilterSourceFile(self, affected_file, white_list=None, black_list=None): 454 def FilterSourceFile(self, affected_file, white_list=None, black_list=None):
483 """Filters out files that aren't considered "source file". 455 """Filters out files that aren't considered "source file".
484 456
485 If white_list or black_list is None, InputApi.DEFAULT_WHITE_LIST 457 If white_list or black_list is None, InputApi.DEFAULT_WHITE_LIST
486 and InputApi.DEFAULT_BLACK_LIST is used respectively. 458 and InputApi.DEFAULT_BLACK_LIST is used respectively.
487 459
488 The lists will be compiled as regular expression and 460 The lists will be compiled as regular expression and
489 AffectedFile.LocalPath() needs to pass both list. 461 AffectedFile.LocalPath() needs to pass both list.
490 462
491 Note: Copy-paste this function to suit your needs or use a lambda function. 463 Note: Copy-paste this function to suit your needs or use a lambda function.
492 """ 464 """
493 def Find(affected_file, items): 465 def Find(affected_file, items):
494 local_path = affected_file.LocalPath() 466 local_path = affected_file.LocalPath()
495 for item in items: 467 for item in items:
496 if self.re.match(item, local_path): 468 if self.re.match(item, local_path):
497 return True 469 return True
498 return False 470 return False
499 return (Find(affected_file, white_list or self.DEFAULT_WHITE_LIST) and 471 return (Find(affected_file, white_list or self.DEFAULT_WHITE_LIST) and
500 not Find(affected_file, black_list or self.DEFAULT_BLACK_LIST)) 472 not Find(affected_file, black_list or self.DEFAULT_BLACK_LIST))
501 473
502 def AffectedSourceFiles(self, source_file): 474 def AffectedSourceFiles(self, source_file):
503 """Filter the list of AffectedTextFiles by the function source_file. 475 """Filter the list of AffectedTestableFiles by the function source_file.
504 476
505 If source_file is None, InputApi.FilterSourceFile() is used. 477 If source_file is None, InputApi.FilterSourceFile() is used.
506 """ 478 """
507 if not source_file: 479 if not source_file:
508 source_file = self.FilterSourceFile 480 source_file = self.FilterSourceFile
509 return filter(source_file, self.AffectedTextFiles()) 481 return filter(source_file, self.AffectedTestableFiles())
510 482
511 def RightHandSideLines(self, source_file_filter=None): 483 def RightHandSideLines(self, source_file_filter=None):
512 """An iterator over all text lines in "new" version of changed files. 484 """An iterator over all text lines in "new" version of changed files.
513 485
514 Only lists lines from new or modified text files in the change that are 486 Only lists lines from new or modified text files in the change that are
515 contained by the directory of the currently executing presubmit script. 487 contained by the directory of the currently executing presubmit script.
516 488
517 This is useful for doing line-by-line regex checks, like checking for 489 This is useful for doing line-by-line regex checks, like checking for
518 trailing whitespace. 490 trailing whitespace.
519 491
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
572 """Caches diffs retrieved from a particular SCM.""" 544 """Caches diffs retrieved from a particular SCM."""
573 def __init__(self, upstream=None): 545 def __init__(self, upstream=None):
574 """Stores the upstream revision against which all diffs will be computed.""" 546 """Stores the upstream revision against which all diffs will be computed."""
575 self._upstream = upstream 547 self._upstream = upstream
576 548
577 def GetDiff(self, path, local_root): 549 def GetDiff(self, path, local_root):
578 """Get the diff for a particular path.""" 550 """Get the diff for a particular path."""
579 raise NotImplementedError() 551 raise NotImplementedError()
580 552
581 553
582 class _SvnDiffCache(_DiffCache):
583 """DiffCache implementation for subversion."""
584 def __init__(self, *args, **kwargs):
585 super(_SvnDiffCache, self).__init__(*args, **kwargs)
586 self._diffs_by_file = {}
587
588 def GetDiff(self, path, local_root):
589 if path not in self._diffs_by_file:
590 self._diffs_by_file[path] = scm.SVN.GenerateDiff([path], local_root,
591 False, None)
592 return self._diffs_by_file[path]
593
594
595 class _GitDiffCache(_DiffCache): 554 class _GitDiffCache(_DiffCache):
596 """DiffCache implementation for git; gets all file diffs at once.""" 555 """DiffCache implementation for git; gets all file diffs at once."""
597 def __init__(self, upstream): 556 def __init__(self, upstream):
598 super(_GitDiffCache, self).__init__(upstream=upstream) 557 super(_GitDiffCache, self).__init__(upstream=upstream)
599 self._diffs_by_file = None 558 self._diffs_by_file = None
600 559
601 def GetDiff(self, path, local_root): 560 def GetDiff(self, path, local_root):
602 if not self._diffs_by_file: 561 if not self._diffs_by_file:
603 # Compute a single diff for all files and parse the output; should 562 # Compute a single diff for all files and parse the output; should
604 # with git this is much faster than computing one diff for each file. 563 # with git this is much faster than computing one diff for each file.
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
639 598
640 DIFF_CACHE = _DiffCache 599 DIFF_CACHE = _DiffCache
641 600
642 # Method could be a function 601 # Method could be a function
643 # pylint: disable=R0201 602 # pylint: disable=R0201
644 def __init__(self, path, action, repository_root, diff_cache): 603 def __init__(self, path, action, repository_root, diff_cache):
645 self._path = path 604 self._path = path
646 self._action = action 605 self._action = action
647 self._local_root = repository_root 606 self._local_root = repository_root
648 self._is_directory = None 607 self._is_directory = None
649 self._properties = {}
650 self._cached_changed_contents = None 608 self._cached_changed_contents = None
651 self._cached_new_contents = None 609 self._cached_new_contents = None
652 self._diff_cache = diff_cache 610 self._diff_cache = diff_cache
653 logging.debug('%s(%s)', self.__class__.__name__, self._path) 611 logging.debug('%s(%s)', self.__class__.__name__, self._path)
654 612
655 def ServerPath(self):
656 """Returns a path string that identifies the file in the SCM system.
657
658 Returns the empty string if the file does not exist in SCM.
659 """
660 return ''
661
662 def LocalPath(self): 613 def LocalPath(self):
663 """Returns the path of this file on the local disk relative to client root. 614 """Returns the path of this file on the local disk relative to client root.
664 """ 615 """
665 return normpath(self._path) 616 return normpath(self._path)
666 617
667 def AbsoluteLocalPath(self): 618 def AbsoluteLocalPath(self):
668 """Returns the absolute path of this file on the local disk. 619 """Returns the absolute path of this file on the local disk.
669 """ 620 """
670 return os.path.abspath(os.path.join(self._local_root, self.LocalPath())) 621 return os.path.abspath(os.path.join(self._local_root, self.LocalPath()))
671 622
672 def IsDirectory(self):
673 """Returns true if this object is a directory."""
674 if self._is_directory is None:
675 path = self.AbsoluteLocalPath()
676 self._is_directory = (os.path.exists(path) and
677 os.path.isdir(path))
678 return self._is_directory
679
680 def Action(self): 623 def Action(self):
681 """Returns the action on this opened file, e.g. A, M, D, etc.""" 624 """Returns the action on this opened file, e.g. A, M, D, etc."""
682 # TODO(maruel): Somewhat crappy, Could be "A" or "A +" for svn but 625 # TODO(maruel): Somewhat crappy, Could be "A" or "A +" for svn but
683 # different for other SCM. 626 # different for other SCM.
684 return self._action 627 return self._action
685 628
686 def Property(self, property_name): 629 def IsTestableFile(self):
687 """Returns the specified SCM property of this file, or None if no such
688 property.
689 """
690 return self._properties.get(property_name, None)
691
692 def IsTextFile(self):
693 """Returns True if the file is a text file and not a binary file. 630 """Returns True if the file is a text file and not a binary file.
694 631
695 Deleted files are not text file.""" 632 Deleted files are not text file."""
696 raise NotImplementedError() # Implement when needed 633 raise NotImplementedError() # Implement when needed
697 634
635 def IsTextFile(self):
636 """An alias to IsTestableFile for backwards compatibility."""
637 return self.IsTestableFile()
638
698 def NewContents(self): 639 def NewContents(self):
699 """Returns an iterator over the lines in the new version of file. 640 """Returns an iterator over the lines in the new version of file.
700 641
701 The new version is the file in the user's workspace, i.e. the "right hand 642 The new version is the file in the user's workspace, i.e. the "right hand
702 side". 643 side".
703 644
704 Contents will be empty if the file is a directory or does not exist. 645 Contents will be empty if the file is a directory or does not exist.
705 Note: The carriage returns (LF or CR) are stripped off. 646 Note: The carriage returns (LF or CR) are stripped off.
706 """ 647 """
707 if self._cached_new_contents is None: 648 if self._cached_new_contents is None:
708 self._cached_new_contents = [] 649 self._cached_new_contents = []
709 if not self.IsDirectory(): 650 try:
710 try: 651 self._cached_new_contents = gclient_utils.FileRead(
711 self._cached_new_contents = gclient_utils.FileRead( 652 self.AbsoluteLocalPath(), 'rU').splitlines()
712 self.AbsoluteLocalPath(), 'rU').splitlines() 653 except IOError:
713 except IOError: 654 pass # File not found? That's fine; maybe it was deleted.
714 pass # File not found? That's fine; maybe it was deleted.
715 return self._cached_new_contents[:] 655 return self._cached_new_contents[:]
716 656
717 def ChangedContents(self): 657 def ChangedContents(self):
718 """Returns a list of tuples (line number, line text) of all new lines. 658 """Returns a list of tuples (line number, line text) of all new lines.
719 659
720 This relies on the scm diff output describing each changed code section 660 This relies on the scm diff output describing each changed code section
721 with a line of the form 661 with a line of the form
722 662
723 ^@@ <old line num>,<old size> <new line num>,<new size> @@$ 663 ^@@ <old line num>,<old size> <new line num>,<new size> @@$
724 """ 664 """
725 if self._cached_changed_contents is not None: 665 if self._cached_changed_contents is not None:
726 return self._cached_changed_contents[:] 666 return self._cached_changed_contents[:]
727 self._cached_changed_contents = [] 667 self._cached_changed_contents = []
728 line_num = 0 668 line_num = 0
729 669
730 if self.IsDirectory():
731 return []
732
733 for line in self.GenerateScmDiff().splitlines(): 670 for line in self.GenerateScmDiff().splitlines():
734 m = re.match(r'^@@ [0-9\,\+\-]+ \+([0-9]+)\,[0-9]+ @@', line) 671 m = re.match(r'^@@ [0-9\,\+\-]+ \+([0-9]+)\,[0-9]+ @@', line)
735 if m: 672 if m:
736 line_num = int(m.groups(1)[0]) 673 line_num = int(m.groups(1)[0])
737 continue 674 continue
738 if line.startswith('+') and not line.startswith('++'): 675 if line.startswith('+') and not line.startswith('++'):
739 self._cached_changed_contents.append((line_num, line[1:])) 676 self._cached_changed_contents.append((line_num, line[1:]))
740 if not line.startswith('-'): 677 if not line.startswith('-'):
741 line_num += 1 678 line_num += 1
742 return self._cached_changed_contents[:] 679 return self._cached_changed_contents[:]
743 680
744 def __str__(self): 681 def __str__(self):
745 return self.LocalPath() 682 return self.LocalPath()
746 683
747 def GenerateScmDiff(self): 684 def GenerateScmDiff(self):
748 return self._diff_cache.GetDiff(self.LocalPath(), self._local_root) 685 return self._diff_cache.GetDiff(self.LocalPath(), self._local_root)
749 686
750 687
751 class SvnAffectedFile(AffectedFile):
752 """Representation of a file in a change out of a Subversion checkout."""
753 # Method 'NNN' is abstract in class 'NNN' but is not overridden
754 # pylint: disable=W0223
755
756 DIFF_CACHE = _SvnDiffCache
757
758 def __init__(self, *args, **kwargs):
759 AffectedFile.__init__(self, *args, **kwargs)
760 self._server_path = None
761 self._is_text_file = None
762
763 def ServerPath(self):
764 if self._server_path is None:
765 self._server_path = scm.SVN.CaptureLocalInfo(
766 [self.LocalPath()], self._local_root).get('URL', '')
767 return self._server_path
768
769 def IsDirectory(self):
770 if self._is_directory is None:
771 path = self.AbsoluteLocalPath()
772 if os.path.exists(path):
773 # Retrieve directly from the file system; it is much faster than
774 # querying subversion, especially on Windows.
775 self._is_directory = os.path.isdir(path)
776 else:
777 self._is_directory = scm.SVN.CaptureLocalInfo(
778 [self.LocalPath()], self._local_root
779 ).get('Node Kind') in ('dir', 'directory')
780 return self._is_directory
781
782 def Property(self, property_name):
783 if not property_name in self._properties:
784 self._properties[property_name] = scm.SVN.GetFileProperty(
785 self.LocalPath(), property_name, self._local_root).rstrip()
786 return self._properties[property_name]
787
788 def IsTextFile(self):
789 if self._is_text_file is None:
790 if self.Action() == 'D':
791 # A deleted file is not a text file.
792 self._is_text_file = False
793 elif self.IsDirectory():
794 self._is_text_file = False
795 else:
796 mime_type = scm.SVN.GetFileProperty(
797 self.LocalPath(), 'svn:mime-type', self._local_root)
798 self._is_text_file = (not mime_type or mime_type.startswith('text/'))
799 return self._is_text_file
800
801
802 class GitAffectedFile(AffectedFile): 688 class GitAffectedFile(AffectedFile):
803 """Representation of a file in a change out of a git checkout.""" 689 """Representation of a file in a change out of a git checkout."""
804 # Method 'NNN' is abstract in class 'NNN' but is not overridden 690 # Method 'NNN' is abstract in class 'NNN' but is not overridden
805 # pylint: disable=W0223 691 # pylint: disable=W0223
806 692
807 DIFF_CACHE = _GitDiffCache 693 DIFF_CACHE = _GitDiffCache
808 694
809 def __init__(self, *args, **kwargs): 695 def __init__(self, *args, **kwargs):
810 AffectedFile.__init__(self, *args, **kwargs) 696 AffectedFile.__init__(self, *args, **kwargs)
811 self._server_path = None 697 self._server_path = None
812 self._is_text_file = None 698 self._is_testable_file = None
813 699
814 def ServerPath(self): 700 def IsTestableFile(self):
815 if self._server_path is None: 701 if self._is_testable_file is None:
816 raise NotImplementedError('TODO(maruel) Implement.') 702 if self.Action() == 'D':
817 return self._server_path 703 # A deleted file is not testable.
818 704 self._is_testable_file = False
819 def IsDirectory(self):
820 if self._is_directory is None:
821 path = self.AbsoluteLocalPath()
822 if os.path.exists(path):
823 # Retrieve directly from the file system; it is much faster than
824 # querying subversion, especially on Windows.
825 self._is_directory = os.path.isdir(path)
826 else: 705 else:
827 self._is_directory = False 706 self._is_testable_file = os.path.isfile(self.AbsoluteLocalPath())
828 return self._is_directory 707 return self._is_testable_file
829
830 def Property(self, property_name):
831 if not property_name in self._properties:
832 raise NotImplementedError('TODO(maruel) Implement.')
833 return self._properties[property_name]
834
835 def IsTextFile(self):
836 if self._is_text_file is None:
837 if self.Action() == 'D':
838 # A deleted file is not a text file.
839 self._is_text_file = False
840 elif self.IsDirectory():
841 self._is_text_file = False
842 else:
843 self._is_text_file = os.path.isfile(self.AbsoluteLocalPath())
844 return self._is_text_file
845 708
846 709
847 class Change(object): 710 class Change(object):
848 """Describe a change. 711 """Describe a change.
849 712
850 Used directly by the presubmit scripts to query the current change being 713 Used directly by the presubmit scripts to query the current change being
851 tested. 714 tested.
852 715
853 Instance members: 716 Instance members:
854 tags: Dictionary of KEY=VALUE pairs found in the change description. 717 tags: Dictionary of KEY=VALUE pairs found in the change description.
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
936 def __getattr__(self, attr): 799 def __getattr__(self, attr):
937 """Return tags directly as attributes on the object.""" 800 """Return tags directly as attributes on the object."""
938 if not re.match(r"^[A-Z_]*$", attr): 801 if not re.match(r"^[A-Z_]*$", attr):
939 raise AttributeError(self, attr) 802 raise AttributeError(self, attr)
940 return self.tags.get(attr) 803 return self.tags.get(attr)
941 804
942 def AllFiles(self, root=None): 805 def AllFiles(self, root=None):
943 """List all files under source control in the repo.""" 806 """List all files under source control in the repo."""
944 raise NotImplementedError() 807 raise NotImplementedError()
945 808
946 def AffectedFiles(self, include_dirs=False, include_deletes=True, 809 def AffectedFiles(self, include_deletes=True, file_filter=None):
947 file_filter=None):
948 """Returns a list of AffectedFile instances for all files in the change. 810 """Returns a list of AffectedFile instances for all files in the change.
949 811
950 Args: 812 Args:
951 include_deletes: If false, deleted files will be filtered out. 813 include_deletes: If false, deleted files will be filtered out.
952 include_dirs: True to include directories in the list
953 file_filter: An additional filter to apply. 814 file_filter: An additional filter to apply.
954 815
955 Returns: 816 Returns:
956 [AffectedFile(path, action), AffectedFile(path, action)] 817 [AffectedFile(path, action), AffectedFile(path, action)]
957 """ 818 """
958 if include_dirs: 819 affected = filter(file_filter, self._affected_files)
959 affected = self._affected_files
960 else:
961 affected = filter(lambda x: not x.IsDirectory(), self._affected_files)
962
963 affected = filter(file_filter, affected)
964 820
965 if include_deletes: 821 if include_deletes:
966 return affected 822 return affected
967 else: 823 else:
968 return filter(lambda x: x.Action() != 'D', affected) 824 return filter(lambda x: x.Action() != 'D', affected)
969 825
970 def AffectedTextFiles(self, include_deletes=None): 826 def AffectedTestableFiles(self, include_deletes=None):
971 """Return a list of the existing text files in a change.""" 827 """Return a list of the existing text files in a change."""
972 if include_deletes is not None: 828 if include_deletes is not None:
973 warn("AffectedTextFiles(include_deletes=%s)" 829 warn("AffectedTeestableFiles(include_deletes=%s)"
974 " is deprecated and ignored" % str(include_deletes), 830 " is deprecated and ignored" % str(include_deletes),
975 category=DeprecationWarning, 831 category=DeprecationWarning,
976 stacklevel=2) 832 stacklevel=2)
977 return filter(lambda x: x.IsTextFile(), 833 return filter(lambda x: x.IsTestableFile(),
978 self.AffectedFiles(include_dirs=False, include_deletes=False)) 834 self.AffectedFiles(include_deletes=False))
979 835
980 def LocalPaths(self, include_dirs=False): 836 def AffectedTextFiles(self, include_deletes=None):
837 """An alias to AffectedTestableFiles for backwards compatibility."""
838 return self.AffectedTestableFiles(include_deletes=include_deletes)
839
840 def LocalPaths(self):
981 """Convenience function.""" 841 """Convenience function."""
982 return [af.LocalPath() for af in self.AffectedFiles(include_dirs)] 842 return [af.LocalPath() for af in self.AffectedFiles()]
983 843
984 def AbsoluteLocalPaths(self, include_dirs=False): 844 def AbsoluteLocalPaths(self):
985 """Convenience function.""" 845 """Convenience function."""
986 return [af.AbsoluteLocalPath() for af in self.AffectedFiles(include_dirs)] 846 return [af.AbsoluteLocalPath() for af in self.AffectedFiles()]
987
988 def ServerPaths(self, include_dirs=False):
989 """Convenience function."""
990 return [af.ServerPath() for af in self.AffectedFiles(include_dirs)]
991 847
992 def RightHandSideLines(self): 848 def RightHandSideLines(self):
993 """An iterator over all text lines in "new" version of changed files. 849 """An iterator over all text lines in "new" version of changed files.
994 850
995 Lists lines from new or modified text files in the change. 851 Lists lines from new or modified text files in the change.
996 852
997 This is useful for doing line-by-line regex checks, like checking for 853 This is useful for doing line-by-line regex checks, like checking for
998 trailing whitespace. 854 trailing whitespace.
999 855
1000 Yields: 856 Yields:
1001 a 3 tuple: 857 a 3 tuple:
1002 the AffectedFile instance of the current file; 858 the AffectedFile instance of the current file;
1003 integer line number (1-based); and 859 integer line number (1-based); and
1004 the contents of the line as a string. 860 the contents of the line as a string.
1005 """ 861 """
1006 return _RightHandSideLinesImpl( 862 return _RightHandSideLinesImpl(
1007 x for x in self.AffectedFiles(include_deletes=False) 863 x for x in self.AffectedFiles(include_deletes=False)
1008 if x.IsTextFile()) 864 if x.IsTestableFile())
1009
1010
1011 class SvnChange(Change):
1012 _AFFECTED_FILES = SvnAffectedFile
1013 scm = 'svn'
1014 _changelists = None
1015
1016 def AllFiles(self, root=None):
1017 """List all files under source control in the repo."""
1018 root = root or self.RepositoryRoot()
1019 return subprocess.check_output(
1020 ['svn', 'ls', '-R', '.'], cwd=root).splitlines()
1021 865
1022 866
1023 class GitChange(Change): 867 class GitChange(Change):
1024 _AFFECTED_FILES = GitAffectedFile 868 _AFFECTED_FILES = GitAffectedFile
1025 scm = 'git' 869 scm = 'git'
1026 870
1027 def AllFiles(self, root=None): 871 def AllFiles(self, root=None):
1028 """List all files under source control in the repo.""" 872 """List all files under source control in the repo."""
1029 root = root or self.RepositoryRoot() 873 root = root or self.RepositoryRoot()
1030 return subprocess.check_output( 874 return subprocess.check_output(
(...skipping 473 matching lines...) Expand 10 before | Expand all | Expand 10 after
1504 os.environ = os.environ.copy() 1348 os.environ = os.environ.copy()
1505 os.environ['PYTHONDONTWRITEBYTECODE'] = '1' 1349 os.environ['PYTHONDONTWRITEBYTECODE'] = '1'
1506 1350
1507 output = PresubmitOutput(input_stream, output_stream) 1351 output = PresubmitOutput(input_stream, output_stream)
1508 if committing: 1352 if committing:
1509 output.write("Running presubmit commit checks ...\n") 1353 output.write("Running presubmit commit checks ...\n")
1510 else: 1354 else:
1511 output.write("Running presubmit upload checks ...\n") 1355 output.write("Running presubmit upload checks ...\n")
1512 start_time = time.time() 1356 start_time = time.time()
1513 presubmit_files = ListRelevantPresubmitFiles( 1357 presubmit_files = ListRelevantPresubmitFiles(
1514 change.AbsoluteLocalPaths(True), change.RepositoryRoot()) 1358 change.AbsoluteLocalPaths(), change.RepositoryRoot())
1515 if not presubmit_files and verbose: 1359 if not presubmit_files and verbose:
1516 output.write("Warning, no PRESUBMIT.py found.\n") 1360 output.write("Warning, no PRESUBMIT.py found.\n")
1517 results = [] 1361 results = []
1518 executer = PresubmitExecuter(change, committing, rietveld_obj, verbose, 1362 executer = PresubmitExecuter(change, committing, rietveld_obj, verbose,
1519 gerrit_obj, dry_run) 1363 gerrit_obj, dry_run)
1520 if default_presubmit: 1364 if default_presubmit:
1521 if verbose: 1365 if verbose:
1522 output.write("Running default presubmit script.\n") 1366 output.write("Running default presubmit script.\n")
1523 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py') 1367 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py')
1524 results += executer.ExecPresubmitScript(default_presubmit, fake_path) 1368 results += executer.ExecPresubmitScript(default_presubmit, fake_path)
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
1596 def ParseFiles(args, recursive): 1440 def ParseFiles(args, recursive):
1597 logging.debug('Searching for %s', args) 1441 logging.debug('Searching for %s', args)
1598 files = [] 1442 files = []
1599 for arg in args: 1443 for arg in args:
1600 files.extend([('M', f) for f in ScanSubDirs(arg, recursive)]) 1444 files.extend([('M', f) for f in ScanSubDirs(arg, recursive)])
1601 return files 1445 return files
1602 1446
1603 1447
1604 def load_files(options, args): 1448 def load_files(options, args):
1605 """Tries to determine the SCM.""" 1449 """Tries to determine the SCM."""
1606 change_scm = scm.determine_scm(options.root)
1607 files = [] 1450 files = []
1608 if args: 1451 if args:
1609 files = ParseFiles(args, options.recursive) 1452 files = ParseFiles(args, options.recursive)
1610 if change_scm == 'svn': 1453 change_scm = scm.determine_scm(options.root)
1611 change_class = SvnChange 1454 if change_scm == 'git':
1612 if not files:
1613 files = scm.SVN.CaptureStatus([], options.root)
1614 elif change_scm == 'git':
1615 change_class = GitChange 1455 change_class = GitChange
1616 upstream = options.upstream or None 1456 upstream = options.upstream or None
1617 if not files: 1457 if not files:
1618 files = scm.GIT.CaptureStatus([], options.root, upstream) 1458 files = scm.GIT.CaptureStatus([], options.root, upstream)
1619 else: 1459 else:
1620 logging.info('Doesn\'t seem under source control. Got %d files', len(args)) 1460 logging.info('Doesn\'t seem under source control. Got %d files', len(args))
1621 if not files: 1461 if not files:
1622 return None, None 1462 return None, None
1623 change_class = Change 1463 change_class = Change
1624 return change_class, files 1464 return change_class, files
(...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after
1805 return 2 1645 return 2
1806 1646
1807 1647
1808 if __name__ == '__main__': 1648 if __name__ == '__main__':
1809 fix_encoding.fix_encoding() 1649 fix_encoding.fix_encoding()
1810 try: 1650 try:
1811 sys.exit(main()) 1651 sys.exit(main())
1812 except KeyboardInterrupt: 1652 except KeyboardInterrupt:
1813 sys.stderr.write('interrupted\n') 1653 sys.stderr.write('interrupted\n')
1814 sys.exit(2) 1654 sys.exit(2)
OLDNEW
« no previous file with comments | « presubmit_canned_checks.py ('k') | tests/presubmit_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698