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

Side by Side Diff: presubmit_support.py

Issue 2394043002: Remove SVN support from PRESUBMIT (Closed)
Patch Set: 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,
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):
M-A Ruel 2016/10/05 21:25:33 This increases the odd of this CL blowing up. Are
agable 2016/10/05 22:51:00 Sigh, you're right, it would kill a bunch of PRESU
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))
481 449
482 def FilterSourceFile(self, affected_file, white_list=None, black_list=None): 450 def FilterSourceFile(self, affected_file, white_list=None, black_list=None):
483 """Filters out files that aren't considered "source file". 451 """Filters out files that aren't considered "source file".
484 452
485 If white_list or black_list is None, InputApi.DEFAULT_WHITE_LIST 453 If white_list or black_list is None, InputApi.DEFAULT_WHITE_LIST
486 and InputApi.DEFAULT_BLACK_LIST is used respectively. 454 and InputApi.DEFAULT_BLACK_LIST is used respectively.
487 455
488 The lists will be compiled as regular expression and 456 The lists will be compiled as regular expression and
489 AffectedFile.LocalPath() needs to pass both list. 457 AffectedFile.LocalPath() needs to pass both list.
490 458
491 Note: Copy-paste this function to suit your needs or use a lambda function. 459 Note: Copy-paste this function to suit your needs or use a lambda function.
492 """ 460 """
493 def Find(affected_file, items): 461 def Find(affected_file, items):
494 local_path = affected_file.LocalPath() 462 local_path = affected_file.LocalPath()
495 for item in items: 463 for item in items:
496 if self.re.match(item, local_path): 464 if self.re.match(item, local_path):
497 return True 465 return True
498 return False 466 return False
499 return (Find(affected_file, white_list or self.DEFAULT_WHITE_LIST) and 467 return (Find(affected_file, white_list or self.DEFAULT_WHITE_LIST) and
500 not Find(affected_file, black_list or self.DEFAULT_BLACK_LIST)) 468 not Find(affected_file, black_list or self.DEFAULT_BLACK_LIST))
501 469
502 def AffectedSourceFiles(self, source_file): 470 def AffectedSourceFiles(self, source_file):
503 """Filter the list of AffectedTextFiles by the function source_file. 471 """Filter the list of AffectedTestableFiles by the function source_file.
504 472
505 If source_file is None, InputApi.FilterSourceFile() is used. 473 If source_file is None, InputApi.FilterSourceFile() is used.
506 """ 474 """
507 if not source_file: 475 if not source_file:
508 source_file = self.FilterSourceFile 476 source_file = self.FilterSourceFile
509 return filter(source_file, self.AffectedTextFiles()) 477 return filter(source_file, self.AffectedTestableFiles())
510 478
511 def RightHandSideLines(self, source_file_filter=None): 479 def RightHandSideLines(self, source_file_filter=None):
512 """An iterator over all text lines in "new" version of changed files. 480 """An iterator over all text lines in "new" version of changed files.
513 481
514 Only lists lines from new or modified text files in the change that are 482 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. 483 contained by the directory of the currently executing presubmit script.
516 484
517 This is useful for doing line-by-line regex checks, like checking for 485 This is useful for doing line-by-line regex checks, like checking for
518 trailing whitespace. 486 trailing whitespace.
519 487
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
572 """Caches diffs retrieved from a particular SCM.""" 540 """Caches diffs retrieved from a particular SCM."""
573 def __init__(self, upstream=None): 541 def __init__(self, upstream=None):
574 """Stores the upstream revision against which all diffs will be computed.""" 542 """Stores the upstream revision against which all diffs will be computed."""
575 self._upstream = upstream 543 self._upstream = upstream
576 544
577 def GetDiff(self, path, local_root): 545 def GetDiff(self, path, local_root):
578 """Get the diff for a particular path.""" 546 """Get the diff for a particular path."""
579 raise NotImplementedError() 547 raise NotImplementedError()
580 548
581 549
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): 550 class _GitDiffCache(_DiffCache):
596 """DiffCache implementation for git; gets all file diffs at once.""" 551 """DiffCache implementation for git; gets all file diffs at once."""
597 def __init__(self, upstream): 552 def __init__(self, upstream):
598 super(_GitDiffCache, self).__init__(upstream=upstream) 553 super(_GitDiffCache, self).__init__(upstream=upstream)
599 self._diffs_by_file = None 554 self._diffs_by_file = None
600 555
601 def GetDiff(self, path, local_root): 556 def GetDiff(self, path, local_root):
602 if not self._diffs_by_file: 557 if not self._diffs_by_file:
603 # Compute a single diff for all files and parse the output; should 558 # 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. 559 # 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 594
640 DIFF_CACHE = _DiffCache 595 DIFF_CACHE = _DiffCache
641 596
642 # Method could be a function 597 # Method could be a function
643 # pylint: disable=R0201 598 # pylint: disable=R0201
644 def __init__(self, path, action, repository_root, diff_cache): 599 def __init__(self, path, action, repository_root, diff_cache):
645 self._path = path 600 self._path = path
646 self._action = action 601 self._action = action
647 self._local_root = repository_root 602 self._local_root = repository_root
648 self._is_directory = None 603 self._is_directory = None
649 self._properties = {}
650 self._cached_changed_contents = None 604 self._cached_changed_contents = None
651 self._cached_new_contents = None 605 self._cached_new_contents = None
652 self._diff_cache = diff_cache 606 self._diff_cache = diff_cache
653 logging.debug('%s(%s)', self.__class__.__name__, self._path) 607 logging.debug('%s(%s)', self.__class__.__name__, self._path)
654 608
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): 609 def LocalPath(self):
663 """Returns the path of this file on the local disk relative to client root. 610 """Returns the path of this file on the local disk relative to client root.
664 """ 611 """
665 return normpath(self._path) 612 return normpath(self._path)
666 613
667 def AbsoluteLocalPath(self): 614 def AbsoluteLocalPath(self):
668 """Returns the absolute path of this file on the local disk. 615 """Returns the absolute path of this file on the local disk.
669 """ 616 """
670 return os.path.abspath(os.path.join(self._local_root, self.LocalPath())) 617 return os.path.abspath(os.path.join(self._local_root, self.LocalPath()))
671 618
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): 619 def Action(self):
681 """Returns the action on this opened file, e.g. A, M, D, etc.""" 620 """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 621 # TODO(maruel): Somewhat crappy, Could be "A" or "A +" for svn but
683 # different for other SCM. 622 # different for other SCM.
684 return self._action 623 return self._action
685 624
686 def Property(self, property_name): 625 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. 626 """Returns True if the file is a text file and not a binary file.
694 627
695 Deleted files are not text file.""" 628 Deleted files are not text file."""
696 raise NotImplementedError() # Implement when needed 629 raise NotImplementedError() # Implement when needed
697 630
698 def NewContents(self): 631 def NewContents(self):
699 """Returns an iterator over the lines in the new version of file. 632 """Returns an iterator over the lines in the new version of file.
700 633
701 The new version is the file in the user's workspace, i.e. the "right hand 634 The new version is the file in the user's workspace, i.e. the "right hand
702 side". 635 side".
703 636
704 Contents will be empty if the file is a directory or does not exist. 637 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. 638 Note: The carriage returns (LF or CR) are stripped off.
706 """ 639 """
707 if self._cached_new_contents is None: 640 if self._cached_new_contents is None:
708 self._cached_new_contents = [] 641 self._cached_new_contents = []
709 if not self.IsDirectory(): 642 try:
710 try: 643 self._cached_new_contents = gclient_utils.FileRead(
711 self._cached_new_contents = gclient_utils.FileRead( 644 self.AbsoluteLocalPath(), 'rU').splitlines()
712 self.AbsoluteLocalPath(), 'rU').splitlines() 645 except IOError:
713 except IOError: 646 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[:] 647 return self._cached_new_contents[:]
716 648
717 def ChangedContents(self): 649 def ChangedContents(self):
718 """Returns a list of tuples (line number, line text) of all new lines. 650 """Returns a list of tuples (line number, line text) of all new lines.
719 651
720 This relies on the scm diff output describing each changed code section 652 This relies on the scm diff output describing each changed code section
721 with a line of the form 653 with a line of the form
722 654
723 ^@@ <old line num>,<old size> <new line num>,<new size> @@$ 655 ^@@ <old line num>,<old size> <new line num>,<new size> @@$
724 """ 656 """
725 if self._cached_changed_contents is not None: 657 if self._cached_changed_contents is not None:
726 return self._cached_changed_contents[:] 658 return self._cached_changed_contents[:]
727 self._cached_changed_contents = [] 659 self._cached_changed_contents = []
728 line_num = 0 660 line_num = 0
729 661
730 if self.IsDirectory():
731 return []
732
733 for line in self.GenerateScmDiff().splitlines(): 662 for line in self.GenerateScmDiff().splitlines():
734 m = re.match(r'^@@ [0-9\,\+\-]+ \+([0-9]+)\,[0-9]+ @@', line) 663 m = re.match(r'^@@ [0-9\,\+\-]+ \+([0-9]+)\,[0-9]+ @@', line)
735 if m: 664 if m:
736 line_num = int(m.groups(1)[0]) 665 line_num = int(m.groups(1)[0])
737 continue 666 continue
738 if line.startswith('+') and not line.startswith('++'): 667 if line.startswith('+') and not line.startswith('++'):
739 self._cached_changed_contents.append((line_num, line[1:])) 668 self._cached_changed_contents.append((line_num, line[1:]))
740 if not line.startswith('-'): 669 if not line.startswith('-'):
741 line_num += 1 670 line_num += 1
742 return self._cached_changed_contents[:] 671 return self._cached_changed_contents[:]
743 672
744 def __str__(self): 673 def __str__(self):
745 return self.LocalPath() 674 return self.LocalPath()
746 675
747 def GenerateScmDiff(self): 676 def GenerateScmDiff(self):
748 return self._diff_cache.GetDiff(self.LocalPath(), self._local_root) 677 return self._diff_cache.GetDiff(self.LocalPath(), self._local_root)
749 678
750 679
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): 680 class GitAffectedFile(AffectedFile):
803 """Representation of a file in a change out of a git checkout.""" 681 """Representation of a file in a change out of a git checkout."""
804 # Method 'NNN' is abstract in class 'NNN' but is not overridden 682 # Method 'NNN' is abstract in class 'NNN' but is not overridden
805 # pylint: disable=W0223 683 # pylint: disable=W0223
806 684
807 DIFF_CACHE = _GitDiffCache 685 DIFF_CACHE = _GitDiffCache
808 686
809 def __init__(self, *args, **kwargs): 687 def __init__(self, *args, **kwargs):
810 AffectedFile.__init__(self, *args, **kwargs) 688 AffectedFile.__init__(self, *args, **kwargs)
811 self._server_path = None 689 self._server_path = None
812 self._is_text_file = None 690 self._is_testable_file = None
813 691
814 def ServerPath(self): 692 def IsTestableFile(self):
815 if self._server_path is None: 693 if self._is_testable_file is None:
816 raise NotImplementedError('TODO(maruel) Implement.') 694 if self.Action() == 'D':
817 return self._server_path 695 # A deleted file is not testable.
818 696 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: 697 else:
827 self._is_directory = False 698 self._is_testable_file = os.path.isfile(self.AbsoluteLocalPath())
828 return self._is_directory 699 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 700
846 701
847 class Change(object): 702 class Change(object):
848 """Describe a change. 703 """Describe a change.
849 704
850 Used directly by the presubmit scripts to query the current change being 705 Used directly by the presubmit scripts to query the current change being
851 tested. 706 tested.
852 707
853 Instance members: 708 Instance members:
854 tags: Dictionary of KEY=VALUE pairs found in the change description. 709 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): 791 def __getattr__(self, attr):
937 """Return tags directly as attributes on the object.""" 792 """Return tags directly as attributes on the object."""
938 if not re.match(r"^[A-Z_]*$", attr): 793 if not re.match(r"^[A-Z_]*$", attr):
939 raise AttributeError(self, attr) 794 raise AttributeError(self, attr)
940 return self.tags.get(attr) 795 return self.tags.get(attr)
941 796
942 def AllFiles(self, root=None): 797 def AllFiles(self, root=None):
943 """List all files under source control in the repo.""" 798 """List all files under source control in the repo."""
944 raise NotImplementedError() 799 raise NotImplementedError()
945 800
946 def AffectedFiles(self, include_dirs=False, include_deletes=True, 801 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. 802 """Returns a list of AffectedFile instances for all files in the change.
949 803
950 Args: 804 Args:
951 include_deletes: If false, deleted files will be filtered out. 805 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. 806 file_filter: An additional filter to apply.
954 807
955 Returns: 808 Returns:
956 [AffectedFile(path, action), AffectedFile(path, action)] 809 [AffectedFile(path, action), AffectedFile(path, action)]
957 """ 810 """
958 if include_dirs: 811 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 812
965 if include_deletes: 813 if include_deletes:
966 return affected 814 return affected
967 else: 815 else:
968 return filter(lambda x: x.Action() != 'D', affected) 816 return filter(lambda x: x.Action() != 'D', affected)
969 817
970 def AffectedTextFiles(self, include_deletes=None): 818 def AffectedTestableFiles(self, include_deletes=None):
971 """Return a list of the existing text files in a change.""" 819 """Return a list of the existing text files in a change."""
972 if include_deletes is not None: 820 if include_deletes is not None:
973 warn("AffectedTextFiles(include_deletes=%s)" 821 warn("AffectedTeestableFiles(include_deletes=%s)"
974 " is deprecated and ignored" % str(include_deletes), 822 " is deprecated and ignored" % str(include_deletes),
975 category=DeprecationWarning, 823 category=DeprecationWarning,
976 stacklevel=2) 824 stacklevel=2)
977 return filter(lambda x: x.IsTextFile(), 825 return filter(lambda x: x.IsTestableFile(),
978 self.AffectedFiles(include_dirs=False, include_deletes=False)) 826 self.AffectedFiles(include_deletes=False))
979 827
980 def LocalPaths(self, include_dirs=False): 828 def LocalPaths(self):
981 """Convenience function.""" 829 """Convenience function."""
982 return [af.LocalPath() for af in self.AffectedFiles(include_dirs)] 830 return [af.LocalPath() for af in self.AffectedFiles()]
983 831
984 def AbsoluteLocalPaths(self, include_dirs=False): 832 def AbsoluteLocalPaths(self):
985 """Convenience function.""" 833 """Convenience function."""
986 return [af.AbsoluteLocalPath() for af in self.AffectedFiles(include_dirs)] 834 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 835
992 def RightHandSideLines(self): 836 def RightHandSideLines(self):
993 """An iterator over all text lines in "new" version of changed files. 837 """An iterator over all text lines in "new" version of changed files.
994 838
995 Lists lines from new or modified text files in the change. 839 Lists lines from new or modified text files in the change.
996 840
997 This is useful for doing line-by-line regex checks, like checking for 841 This is useful for doing line-by-line regex checks, like checking for
998 trailing whitespace. 842 trailing whitespace.
999 843
1000 Yields: 844 Yields:
1001 a 3 tuple: 845 a 3 tuple:
1002 the AffectedFile instance of the current file; 846 the AffectedFile instance of the current file;
1003 integer line number (1-based); and 847 integer line number (1-based); and
1004 the contents of the line as a string. 848 the contents of the line as a string.
1005 """ 849 """
1006 return _RightHandSideLinesImpl( 850 return _RightHandSideLinesImpl(
1007 x for x in self.AffectedFiles(include_deletes=False) 851 x for x in self.AffectedFiles(include_deletes=False)
1008 if x.IsTextFile()) 852 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 853
1022 854
1023 class GitChange(Change): 855 class GitChange(Change):
1024 _AFFECTED_FILES = GitAffectedFile 856 _AFFECTED_FILES = GitAffectedFile
1025 scm = 'git' 857 scm = 'git'
1026 858
1027 def AllFiles(self, root=None): 859 def AllFiles(self, root=None):
1028 """List all files under source control in the repo.""" 860 """List all files under source control in the repo."""
1029 root = root or self.RepositoryRoot() 861 root = root or self.RepositoryRoot()
1030 return subprocess.check_output( 862 return subprocess.check_output(
(...skipping 473 matching lines...) Expand 10 before | Expand all | Expand 10 after
1504 os.environ = os.environ.copy() 1336 os.environ = os.environ.copy()
1505 os.environ['PYTHONDONTWRITEBYTECODE'] = '1' 1337 os.environ['PYTHONDONTWRITEBYTECODE'] = '1'
1506 1338
1507 output = PresubmitOutput(input_stream, output_stream) 1339 output = PresubmitOutput(input_stream, output_stream)
1508 if committing: 1340 if committing:
1509 output.write("Running presubmit commit checks ...\n") 1341 output.write("Running presubmit commit checks ...\n")
1510 else: 1342 else:
1511 output.write("Running presubmit upload checks ...\n") 1343 output.write("Running presubmit upload checks ...\n")
1512 start_time = time.time() 1344 start_time = time.time()
1513 presubmit_files = ListRelevantPresubmitFiles( 1345 presubmit_files = ListRelevantPresubmitFiles(
1514 change.AbsoluteLocalPaths(True), change.RepositoryRoot()) 1346 change.AbsoluteLocalPaths(), change.RepositoryRoot())
1515 if not presubmit_files and verbose: 1347 if not presubmit_files and verbose:
1516 output.write("Warning, no PRESUBMIT.py found.\n") 1348 output.write("Warning, no PRESUBMIT.py found.\n")
1517 results = [] 1349 results = []
1518 executer = PresubmitExecuter(change, committing, rietveld_obj, verbose, 1350 executer = PresubmitExecuter(change, committing, rietveld_obj, verbose,
1519 gerrit_obj, dry_run) 1351 gerrit_obj, dry_run)
1520 if default_presubmit: 1352 if default_presubmit:
1521 if verbose: 1353 if verbose:
1522 output.write("Running default presubmit script.\n") 1354 output.write("Running default presubmit script.\n")
1523 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py') 1355 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py')
1524 results += executer.ExecPresubmitScript(default_presubmit, fake_path) 1356 results += executer.ExecPresubmitScript(default_presubmit, fake_path)
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
1596 def ParseFiles(args, recursive): 1428 def ParseFiles(args, recursive):
1597 logging.debug('Searching for %s', args) 1429 logging.debug('Searching for %s', args)
1598 files = [] 1430 files = []
1599 for arg in args: 1431 for arg in args:
1600 files.extend([('M', f) for f in ScanSubDirs(arg, recursive)]) 1432 files.extend([('M', f) for f in ScanSubDirs(arg, recursive)])
1601 return files 1433 return files
1602 1434
1603 1435
1604 def load_files(options, args): 1436 def load_files(options, args):
1605 """Tries to determine the SCM.""" 1437 """Tries to determine the SCM."""
1606 change_scm = scm.determine_scm(options.root)
1607 files = [] 1438 files = []
1608 if args: 1439 if args:
1609 files = ParseFiles(args, options.recursive) 1440 files = ParseFiles(args, options.recursive)
1610 if change_scm == 'svn': 1441 change_scm = scm.determine_scm(options.root)
1611 change_class = SvnChange 1442 if change_scm == 'git':
1612 if not files:
1613 files = scm.SVN.CaptureStatus([], options.root)
1614 elif change_scm == 'git':
1615 change_class = GitChange 1443 change_class = GitChange
1616 upstream = options.upstream or None 1444 upstream = options.upstream or None
1617 if not files: 1445 if not files:
1618 files = scm.GIT.CaptureStatus([], options.root, upstream) 1446 files = scm.GIT.CaptureStatus([], options.root, upstream)
1619 else: 1447 else:
1620 logging.info('Doesn\'t seem under source control. Got %d files', len(args)) 1448 logging.info('Doesn\'t seem under source control. Got %d files', len(args))
1621 if not files: 1449 if not files:
1622 return None, None 1450 return None, None
1623 change_class = Change 1451 change_class = Change
1624 return change_class, files 1452 return change_class, files
(...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after
1805 return 2 1633 return 2
1806 1634
1807 1635
1808 if __name__ == '__main__': 1636 if __name__ == '__main__':
1809 fix_encoding.fix_encoding() 1637 fix_encoding.fix_encoding()
1810 try: 1638 try:
1811 sys.exit(main()) 1639 sys.exit(main())
1812 except KeyboardInterrupt: 1640 except KeyboardInterrupt:
1813 sys.stderr.write('interrupted\n') 1641 sys.stderr.write('interrupted\n')
1814 sys.exit(2) 1642 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