| 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.5' | 9 __version__ = '1.6' |
| 10 | 10 |
| 11 # TODO(joi) Add caching where appropriate/needed. The API is designed to allow | 11 # TODO(joi) Add caching where appropriate/needed. The API is designed to allow |
| 12 # caching (between all different invocations of presubmit scripts for a given | 12 # caching (between all different invocations of presubmit scripts for a given |
| 13 # change). We should add it as our presubmit scripts start feeling slow. | 13 # change). We should add it as our presubmit scripts start feeling slow. |
| 14 | 14 |
| 15 import cPickle # Exposed through the API. | 15 import cPickle # Exposed through the API. |
| 16 import cStringIO # Exposed through the API. | 16 import cStringIO # Exposed through the API. |
| 17 import exceptions | |
| 18 import fnmatch | 17 import fnmatch |
| 19 import glob | 18 import glob |
| 20 import logging | 19 import logging |
| 21 import marshal # Exposed through the API. | 20 import marshal # Exposed through the API. |
| 22 import optparse | 21 import optparse |
| 23 import os # Somewhat exposed through the API. | 22 import os # Somewhat exposed through the API. |
| 24 import pickle # Exposed through the API. | 23 import pickle # Exposed through the API. |
| 25 import random | 24 import random |
| 26 import re # Exposed through the API. | 25 import re # Exposed through the API. |
| 27 import sys # Parts exposed through API. | 26 import sys # Parts exposed through API. |
| (...skipping 21 matching lines...) Expand all Loading... |
| 49 import owners | 48 import owners |
| 50 import presubmit_canned_checks | 49 import presubmit_canned_checks |
| 51 import scm | 50 import scm |
| 52 import subprocess2 as subprocess # Exposed through the API. | 51 import subprocess2 as subprocess # Exposed through the API. |
| 53 | 52 |
| 54 | 53 |
| 55 # Ask for feedback only once in program lifetime. | 54 # Ask for feedback only once in program lifetime. |
| 56 _ASKED_FOR_FEEDBACK = False | 55 _ASKED_FOR_FEEDBACK = False |
| 57 | 56 |
| 58 | 57 |
| 59 class NotImplementedException(Exception): | 58 class PresubmitFailure(Exception): |
| 60 """We're leaving placeholders in a bunch of places to remind us of the | |
| 61 design of the API, but we have not implemented all of it yet. Implement as | |
| 62 the need arises. | |
| 63 """ | |
| 64 pass | 59 pass |
| 65 | 60 |
| 66 | 61 |
| 67 def normpath(path): | 62 def normpath(path): |
| 68 '''Version of os.path.normpath that also changes backward slashes to | 63 '''Version of os.path.normpath that also changes backward slashes to |
| 69 forward slashes when not running on Windows. | 64 forward slashes when not running on Windows. |
| 70 ''' | 65 ''' |
| 71 # This is safe to always do because the Windows version of os.path.normpath | 66 # This is safe to always do because the Windows version of os.path.normpath |
| 72 # will replace forward slashes with backward slashes. | 67 # will replace forward slashes with backward slashes. |
| 73 path = path.replace(os.sep, '/') | 68 path = path.replace(os.sep, '/') |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 171 should_prompt = True | 166 should_prompt = True |
| 172 | 167 |
| 173 class PresubmitNotifyResult(PresubmitResult): | 168 class PresubmitNotifyResult(PresubmitResult): |
| 174 """Just print something to the screen -- but it's not even a warning.""" | 169 """Just print something to the screen -- but it's not even a warning.""" |
| 175 pass | 170 pass |
| 176 | 171 |
| 177 class MailTextResult(PresubmitResult): | 172 class MailTextResult(PresubmitResult): |
| 178 """A warning that should be included in the review request email.""" | 173 """A warning that should be included in the review request email.""" |
| 179 def __init__(self, *args, **kwargs): | 174 def __init__(self, *args, **kwargs): |
| 180 super(OutputApi.MailTextResult, self).__init__() | 175 super(OutputApi.MailTextResult, self).__init__() |
| 181 raise NotImplementedException() | 176 raise NotImplementedError() |
| 182 | 177 |
| 183 | 178 |
| 184 class InputApi(object): | 179 class InputApi(object): |
| 185 """An instance of this object is passed to presubmit scripts so they can | 180 """An instance of this object is passed to presubmit scripts so they can |
| 186 know stuff about the change they're looking at. | 181 know stuff about the change they're looking at. |
| 187 """ | 182 """ |
| 188 # Method could be a function | 183 # Method could be a function |
| 189 # pylint: disable=R0201 | 184 # pylint: disable=R0201 |
| 190 | 185 |
| 191 # File extensions that are considered source files from a style guide | 186 # File extensions that are considered source files from a style guide |
| (...skipping 23 matching lines...) Expand all Loading... |
| 215 r".*\bsconsbuild[\\\/].*", | 210 r".*\bsconsbuild[\\\/].*", |
| 216 # All caps files like README and LICENCE. | 211 # All caps files like README and LICENCE. |
| 217 r".*\b[A-Z0-9_]{2,}$", | 212 r".*\b[A-Z0-9_]{2,}$", |
| 218 # SCM (can happen in dual SCM configuration). (Slightly over aggressive) | 213 # SCM (can happen in dual SCM configuration). (Slightly over aggressive) |
| 219 r"(|.*[\\\/])\.git[\\\/].*", | 214 r"(|.*[\\\/])\.git[\\\/].*", |
| 220 r"(|.*[\\\/])\.svn[\\\/].*", | 215 r"(|.*[\\\/])\.svn[\\\/].*", |
| 221 ) | 216 ) |
| 222 | 217 |
| 223 # TODO(dpranke): Update callers to pass in tbr, host_url, remove | 218 # TODO(dpranke): Update callers to pass in tbr, host_url, remove |
| 224 # default arguments. | 219 # default arguments. |
| 225 def __init__(self, change, presubmit_path, is_committing, tbr, host_url=None): | 220 def __init__(self, change, presubmit_path, is_committing, tbr, host_url, |
| 221 verbose): |
| 226 """Builds an InputApi object. | 222 """Builds an InputApi object. |
| 227 | 223 |
| 228 Args: | 224 Args: |
| 229 change: A presubmit.Change object. | 225 change: A presubmit.Change object. |
| 230 presubmit_path: The path to the presubmit script being processed. | 226 presubmit_path: The path to the presubmit script being processed. |
| 231 is_committing: True if the change is about to be committed. | 227 is_committing: True if the change is about to be committed. |
| 232 tbr: True if '--tbr' was passed to skip any reviewer/owner checks | 228 tbr: True if '--tbr' was passed to skip any reviewer/owner checks |
| 233 host_url: scheme, host, and path of rietveld instance | 229 host_url: scheme, host, and path of rietveld instance |
| 234 """ | 230 """ |
| 235 # Version number of the presubmit_support script. | 231 # Version number of the presubmit_support script. |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 269 # The local path of the currently-being-processed presubmit script. | 265 # The local path of the currently-being-processed presubmit script. |
| 270 self._current_presubmit_path = os.path.dirname(presubmit_path) | 266 self._current_presubmit_path = os.path.dirname(presubmit_path) |
| 271 | 267 |
| 272 # We carry the canned checks so presubmit scripts can easily use them. | 268 # We carry the canned checks so presubmit scripts can easily use them. |
| 273 self.canned_checks = presubmit_canned_checks | 269 self.canned_checks = presubmit_canned_checks |
| 274 | 270 |
| 275 # TODO(dpranke): figure out a list of all approved owners for a repo | 271 # TODO(dpranke): figure out a list of all approved owners for a repo |
| 276 # in order to be able to handle wildcard OWNERS files? | 272 # in order to be able to handle wildcard OWNERS files? |
| 277 self.owners_db = owners.Database(change.RepositoryRoot(), | 273 self.owners_db = owners.Database(change.RepositoryRoot(), |
| 278 fopen=file, os_path=self.os_path) | 274 fopen=file, os_path=self.os_path) |
| 275 self.verbose = verbose |
| 279 | 276 |
| 280 def PresubmitLocalPath(self): | 277 def PresubmitLocalPath(self): |
| 281 """Returns the local path of the presubmit script currently being run. | 278 """Returns the local path of the presubmit script currently being run. |
| 282 | 279 |
| 283 This is useful if you don't want to hard-code absolute paths in the | 280 This is useful if you don't want to hard-code absolute paths in the |
| 284 presubmit script. For example, It can be used to find another file | 281 presubmit script. For example, It can be used to find another file |
| 285 relative to the PRESUBMIT.py script, so the whole tree can be branched and | 282 relative to the PRESUBMIT.py script, so the whole tree can be branched and |
| 286 the presubmit script still works, without editing its content. | 283 the presubmit script still works, without editing its content. |
| 287 """ | 284 """ |
| 288 return self._current_presubmit_path | 285 return self._current_presubmit_path |
| (...skipping 299 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 588 # Method 'NNN' is abstract in class 'NNN' but is not overridden | 585 # Method 'NNN' is abstract in class 'NNN' but is not overridden |
| 589 # pylint: disable=W0223 | 586 # pylint: disable=W0223 |
| 590 | 587 |
| 591 def __init__(self, *args, **kwargs): | 588 def __init__(self, *args, **kwargs): |
| 592 AffectedFile.__init__(self, *args, **kwargs) | 589 AffectedFile.__init__(self, *args, **kwargs) |
| 593 self._server_path = None | 590 self._server_path = None |
| 594 self._is_text_file = None | 591 self._is_text_file = None |
| 595 | 592 |
| 596 def ServerPath(self): | 593 def ServerPath(self): |
| 597 if self._server_path is None: | 594 if self._server_path is None: |
| 598 raise NotImplementedException() # TODO(maruel) Implement. | 595 raise NotImplementedError('TODO(maruel) Implement.') |
| 599 return self._server_path | 596 return self._server_path |
| 600 | 597 |
| 601 def IsDirectory(self): | 598 def IsDirectory(self): |
| 602 if self._is_directory is None: | 599 if self._is_directory is None: |
| 603 path = self.AbsoluteLocalPath() | 600 path = self.AbsoluteLocalPath() |
| 604 if os.path.exists(path): | 601 if os.path.exists(path): |
| 605 # Retrieve directly from the file system; it is much faster than | 602 # Retrieve directly from the file system; it is much faster than |
| 606 # querying subversion, especially on Windows. | 603 # querying subversion, especially on Windows. |
| 607 self._is_directory = os.path.isdir(path) | 604 self._is_directory = os.path.isdir(path) |
| 608 else: | 605 else: |
| 609 # raise NotImplementedException() # TODO(maruel) Implement. | |
| 610 self._is_directory = False | 606 self._is_directory = False |
| 611 return self._is_directory | 607 return self._is_directory |
| 612 | 608 |
| 613 def Property(self, property_name): | 609 def Property(self, property_name): |
| 614 if not property_name in self._properties: | 610 if not property_name in self._properties: |
| 615 raise NotImplementedException() # TODO(maruel) Implement. | 611 raise NotImplementedError('TODO(maruel) Implement.') |
| 616 return self._properties[property_name] | 612 return self._properties[property_name] |
| 617 | 613 |
| 618 def IsTextFile(self): | 614 def IsTextFile(self): |
| 619 if self._is_text_file is None: | 615 if self._is_text_file is None: |
| 620 if self.Action() == 'D': | 616 if self.Action() == 'D': |
| 621 # A deleted file is not a text file. | 617 # A deleted file is not a text file. |
| 622 self._is_text_file = False | 618 self._is_text_file = False |
| 623 elif self.IsDirectory(): | 619 elif self.IsDirectory(): |
| 624 self._is_text_file = False | 620 self._is_text_file = False |
| 625 else: | 621 else: |
| 626 # raise NotImplementedException() # TODO(maruel) Implement. | |
| 627 self._is_text_file = os.path.isfile(self.AbsoluteLocalPath()) | 622 self._is_text_file = os.path.isfile(self.AbsoluteLocalPath()) |
| 628 return self._is_text_file | 623 return self._is_text_file |
| 629 | 624 |
| 630 def GenerateScmDiff(self): | 625 def GenerateScmDiff(self): |
| 631 return scm.GIT.GenerateDiff(self._local_root, files=[self.LocalPath(),]) | 626 return scm.GIT.GenerateDiff(self._local_root, files=[self.LocalPath(),]) |
| 632 | 627 |
| 633 class Change(object): | 628 class Change(object): |
| 634 """Describe a change. | 629 """Describe a change. |
| 635 | 630 |
| 636 Used directly by the presubmit scripts to query the current change being | 631 Used directly by the presubmit scripts to query the current change being |
| (...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 854 p = os.path.join(directory, 'PRESUBMIT.py') | 849 p = os.path.join(directory, 'PRESUBMIT.py') |
| 855 if os.path.isfile(p): | 850 if os.path.isfile(p): |
| 856 results.append(p) | 851 results.append(p) |
| 857 | 852 |
| 858 logging.debug('Presubmit files: %s' % ','.join(results)) | 853 logging.debug('Presubmit files: %s' % ','.join(results)) |
| 859 return results | 854 return results |
| 860 | 855 |
| 861 | 856 |
| 862 class GetTrySlavesExecuter(object): | 857 class GetTrySlavesExecuter(object): |
| 863 @staticmethod | 858 @staticmethod |
| 864 def ExecPresubmitScript(script_text): | 859 def ExecPresubmitScript(script_text, presubmit_path): |
| 865 """Executes GetPreferredTrySlaves() from a single presubmit script. | 860 """Executes GetPreferredTrySlaves() from a single presubmit script. |
| 866 | 861 |
| 867 Args: | 862 Args: |
| 868 script_text: The text of the presubmit script. | 863 script_text: The text of the presubmit script. |
| 869 | 864 |
| 870 Return: | 865 Return: |
| 871 A list of try slaves. | 866 A list of try slaves. |
| 872 """ | 867 """ |
| 873 context = {} | 868 context = {} |
| 874 exec script_text in context | 869 try: |
| 870 exec script_text in context |
| 871 except Exception, e: |
| 872 raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e)) |
| 875 | 873 |
| 876 function_name = 'GetPreferredTrySlaves' | 874 function_name = 'GetPreferredTrySlaves' |
| 877 if function_name in context: | 875 if function_name in context: |
| 878 result = eval(function_name + '()', context) | 876 result = eval(function_name + '()', context) |
| 879 if not isinstance(result, types.ListType): | 877 if not isinstance(result, types.ListType): |
| 880 raise exceptions.RuntimeError( | 878 raise PresubmitFailure( |
| 881 'Presubmit functions must return a list, got a %s instead: %s' % | 879 'Presubmit functions must return a list, got a %s instead: %s' % |
| 882 (type(result), str(result))) | 880 (type(result), str(result))) |
| 883 for item in result: | 881 for item in result: |
| 884 if not isinstance(item, basestring): | 882 if not isinstance(item, basestring): |
| 885 raise exceptions.RuntimeError('All try slaves names must be strings.') | 883 raise PresubmitFailure('All try slaves names must be strings.') |
| 886 if item != item.strip(): | 884 if item != item.strip(): |
| 887 raise exceptions.RuntimeError('Try slave names cannot start/end' | 885 raise PresubmitFailure( |
| 888 'with whitespace') | 886 'Try slave names cannot start/end with whitespace') |
| 889 else: | 887 else: |
| 890 result = [] | 888 result = [] |
| 891 return result | 889 return result |
| 892 | 890 |
| 893 | 891 |
| 894 def DoGetTrySlaves(changed_files, | 892 def DoGetTrySlaves(changed_files, |
| 895 repository_root, | 893 repository_root, |
| 896 default_presubmit, | 894 default_presubmit, |
| 897 verbose, | 895 verbose, |
| 898 output_stream): | 896 output_stream): |
| (...skipping 10 matching lines...) Expand all Loading... |
| 909 List of try slaves | 907 List of try slaves |
| 910 """ | 908 """ |
| 911 presubmit_files = ListRelevantPresubmitFiles(changed_files, repository_root) | 909 presubmit_files = ListRelevantPresubmitFiles(changed_files, repository_root) |
| 912 if not presubmit_files and verbose: | 910 if not presubmit_files and verbose: |
| 913 output_stream.write("Warning, no presubmit.py found.\n") | 911 output_stream.write("Warning, no presubmit.py found.\n") |
| 914 results = [] | 912 results = [] |
| 915 executer = GetTrySlavesExecuter() | 913 executer = GetTrySlavesExecuter() |
| 916 if default_presubmit: | 914 if default_presubmit: |
| 917 if verbose: | 915 if verbose: |
| 918 output_stream.write("Running default presubmit script.\n") | 916 output_stream.write("Running default presubmit script.\n") |
| 919 results += executer.ExecPresubmitScript(default_presubmit) | 917 fake_path = os.path.join(repository_root, 'PRESUBMIT.py') |
| 918 results += executer.ExecPresubmitScript(default_presubmit, fake_path) |
| 920 for filename in presubmit_files: | 919 for filename in presubmit_files: |
| 921 filename = os.path.abspath(filename) | 920 filename = os.path.abspath(filename) |
| 922 if verbose: | 921 if verbose: |
| 923 output_stream.write("Running %s\n" % filename) | 922 output_stream.write("Running %s\n" % filename) |
| 924 # Accept CRLF presubmit script. | 923 # Accept CRLF presubmit script. |
| 925 presubmit_script = gclient_utils.FileRead(filename, 'rU') | 924 presubmit_script = gclient_utils.FileRead(filename, 'rU') |
| 926 results += executer.ExecPresubmitScript(presubmit_script) | 925 results += executer.ExecPresubmitScript(presubmit_script, filename) |
| 927 | 926 |
| 928 slaves = list(set(results)) | 927 slaves = list(set(results)) |
| 929 if slaves and verbose: | 928 if slaves and verbose: |
| 930 output_stream.write(', '.join(slaves)) | 929 output_stream.write(', '.join(slaves)) |
| 931 output_stream.write('\n') | 930 output_stream.write('\n') |
| 932 return slaves | 931 return slaves |
| 933 | 932 |
| 934 | 933 |
| 935 class PresubmitExecuter(object): | 934 class PresubmitExecuter(object): |
| 936 def __init__(self, change, committing, tbr, host_url): | 935 def __init__(self, change, committing, tbr, host_url, verbose): |
| 937 """ | 936 """ |
| 938 Args: | 937 Args: |
| 939 change: The Change object. | 938 change: The Change object. |
| 940 committing: True if 'gcl commit' is running, False if 'gcl upload' is. | 939 committing: True if 'gcl commit' is running, False if 'gcl upload' is. |
| 941 tbr: True if '--tbr' was passed to skip any reviewer/owner checks | 940 tbr: True if '--tbr' was passed to skip any reviewer/owner checks |
| 942 host_url: scheme, host, and path of rietveld instance | 941 host_url: scheme, host, and path of rietveld instance |
| 943 (or None for default) | 942 (or None for default) |
| 944 """ | 943 """ |
| 945 self.change = change | 944 self.change = change |
| 946 self.committing = committing | 945 self.committing = committing |
| 947 self.tbr = tbr | 946 self.tbr = tbr |
| 948 self.host_url = host_url | 947 self.host_url = host_url |
| 948 self.verbose = verbose |
| 949 | 949 |
| 950 def ExecPresubmitScript(self, script_text, presubmit_path): | 950 def ExecPresubmitScript(self, script_text, presubmit_path): |
| 951 """Executes a single presubmit script. | 951 """Executes a single presubmit script. |
| 952 | 952 |
| 953 Args: | 953 Args: |
| 954 script_text: The text of the presubmit script. | 954 script_text: The text of the presubmit script. |
| 955 presubmit_path: The path to the presubmit file (this will be reported via | 955 presubmit_path: The path to the presubmit file (this will be reported via |
| 956 input_api.PresubmitLocalPath()). | 956 input_api.PresubmitLocalPath()). |
| 957 | 957 |
| 958 Return: | 958 Return: |
| 959 A list of result objects, empty if no problems. | 959 A list of result objects, empty if no problems. |
| 960 """ | 960 """ |
| 961 | 961 |
| 962 # Change to the presubmit file's directory to support local imports. | 962 # Change to the presubmit file's directory to support local imports. |
| 963 main_path = os.getcwd() | 963 main_path = os.getcwd() |
| 964 os.chdir(os.path.dirname(presubmit_path)) | 964 os.chdir(os.path.dirname(presubmit_path)) |
| 965 | 965 |
| 966 # Load the presubmit script into context. | 966 # Load the presubmit script into context. |
| 967 input_api = InputApi(self.change, presubmit_path, self.committing, | 967 input_api = InputApi(self.change, presubmit_path, self.committing, |
| 968 self.tbr, self.host_url) | 968 self.tbr, self.host_url, self.verbose) |
| 969 context = {} | 969 context = {} |
| 970 exec script_text in context | 970 try: |
| 971 exec script_text in context |
| 972 except Exception, e: |
| 973 raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e)) |
| 971 | 974 |
| 972 # These function names must change if we make substantial changes to | 975 # These function names must change if we make substantial changes to |
| 973 # the presubmit API that are not backwards compatible. | 976 # the presubmit API that are not backwards compatible. |
| 974 if self.committing: | 977 if self.committing: |
| 975 function_name = 'CheckChangeOnCommit' | 978 function_name = 'CheckChangeOnCommit' |
| 976 else: | 979 else: |
| 977 function_name = 'CheckChangeOnUpload' | 980 function_name = 'CheckChangeOnUpload' |
| 978 if function_name in context: | 981 if function_name in context: |
| 979 context['__args'] = (input_api, OutputApi()) | 982 context['__args'] = (input_api, OutputApi()) |
| 980 logging.debug('Running %s in %s' % (function_name, presubmit_path)) | 983 logging.debug('Running %s in %s' % (function_name, presubmit_path)) |
| 981 result = eval(function_name + '(*__args)', context) | 984 result = eval(function_name + '(*__args)', context) |
| 982 logging.debug('Running %s done.' % function_name) | 985 logging.debug('Running %s done.' % function_name) |
| 983 if not (isinstance(result, types.TupleType) or | 986 if not (isinstance(result, types.TupleType) or |
| 984 isinstance(result, types.ListType)): | 987 isinstance(result, types.ListType)): |
| 985 raise exceptions.RuntimeError( | 988 raise PresubmitFailure( |
| 986 'Presubmit functions must return a tuple or list') | 989 'Presubmit functions must return a tuple or list') |
| 987 for item in result: | 990 for item in result: |
| 988 if not isinstance(item, OutputApi.PresubmitResult): | 991 if not isinstance(item, OutputApi.PresubmitResult): |
| 989 raise exceptions.RuntimeError( | 992 raise PresubmitFailure( |
| 990 'All presubmit results must be of types derived from ' | 993 'All presubmit results must be of types derived from ' |
| 991 'output_api.PresubmitResult') | 994 'output_api.PresubmitResult') |
| 992 else: | 995 else: |
| 993 result = () # no error since the script doesn't care about current event. | 996 result = () # no error since the script doesn't care about current event. |
| 994 | 997 |
| 995 # Return the process to the original working directory. | 998 # Return the process to the original working directory. |
| 996 os.chdir(main_path) | 999 os.chdir(main_path) |
| 997 return result | 1000 return result |
| 998 | 1001 |
| 999 | 1002 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1040 if committing: | 1043 if committing: |
| 1041 output.write("Running presubmit commit checks ...\n") | 1044 output.write("Running presubmit commit checks ...\n") |
| 1042 else: | 1045 else: |
| 1043 output.write("Running presubmit upload checks ...\n") | 1046 output.write("Running presubmit upload checks ...\n") |
| 1044 start_time = time.time() | 1047 start_time = time.time() |
| 1045 presubmit_files = ListRelevantPresubmitFiles(change.AbsoluteLocalPaths(True), | 1048 presubmit_files = ListRelevantPresubmitFiles(change.AbsoluteLocalPaths(True), |
| 1046 change.RepositoryRoot()) | 1049 change.RepositoryRoot()) |
| 1047 if not presubmit_files and verbose: | 1050 if not presubmit_files and verbose: |
| 1048 output.write("Warning, no presubmit.py found.\n") | 1051 output.write("Warning, no presubmit.py found.\n") |
| 1049 results = [] | 1052 results = [] |
| 1050 executer = PresubmitExecuter(change, committing, tbr, host_url) | 1053 executer = PresubmitExecuter(change, committing, tbr, host_url, verbose) |
| 1051 if default_presubmit: | 1054 if default_presubmit: |
| 1052 if verbose: | 1055 if verbose: |
| 1053 output.write("Running default presubmit script.\n") | 1056 output.write("Running default presubmit script.\n") |
| 1054 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py') | 1057 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py') |
| 1055 results += executer.ExecPresubmitScript(default_presubmit, fake_path) | 1058 results += executer.ExecPresubmitScript(default_presubmit, fake_path) |
| 1056 for filename in presubmit_files: | 1059 for filename in presubmit_files: |
| 1057 filename = os.path.abspath(filename) | 1060 filename = os.path.abspath(filename) |
| 1058 if verbose: | 1061 if verbose: |
| 1059 output.write("Running %s\n" % filename) | 1062 output.write("Running %s\n" % filename) |
| 1060 # Accept CRLF presubmit script. | 1063 # Accept CRLF presubmit script. |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1153 | 1156 |
| 1154 def Main(argv): | 1157 def Main(argv): |
| 1155 parser = optparse.OptionParser(usage="%prog [options] <files...>", | 1158 parser = optparse.OptionParser(usage="%prog [options] <files...>", |
| 1156 version="%prog " + str(__version__)) | 1159 version="%prog " + str(__version__)) |
| 1157 parser.add_option("-c", "--commit", action="store_true", default=False, | 1160 parser.add_option("-c", "--commit", action="store_true", default=False, |
| 1158 help="Use commit instead of upload checks") | 1161 help="Use commit instead of upload checks") |
| 1159 parser.add_option("-u", "--upload", action="store_false", dest='commit', | 1162 parser.add_option("-u", "--upload", action="store_false", dest='commit', |
| 1160 help="Use upload instead of commit checks") | 1163 help="Use upload instead of commit checks") |
| 1161 parser.add_option("-r", "--recursive", action="store_true", | 1164 parser.add_option("-r", "--recursive", action="store_true", |
| 1162 help="Act recursively") | 1165 help="Act recursively") |
| 1163 parser.add_option("-v", "--verbose", action="store_true", default=False, | 1166 parser.add_option("-v", "--verbose", action="count", default=0, |
| 1164 help="Verbose output") | 1167 help="Use 2 times for more debug info") |
| 1165 parser.add_option("--name", default='no name') | 1168 parser.add_option("--name", default='no name') |
| 1166 parser.add_option("--description", default='') | 1169 parser.add_option("--description", default='') |
| 1167 parser.add_option("--issue", type='int', default=0) | 1170 parser.add_option("--issue", type='int', default=0) |
| 1168 parser.add_option("--patchset", type='int', default=0) | 1171 parser.add_option("--patchset", type='int', default=0) |
| 1169 parser.add_option("--root", default=os.getcwd(), | 1172 parser.add_option("--root", default=os.getcwd(), |
| 1170 help="Search for PRESUBMIT.py up to this directory. " | 1173 help="Search for PRESUBMIT.py up to this directory. " |
| 1171 "If inherit-review-settings-ok is present in this " | 1174 "If inherit-review-settings-ok is present in this " |
| 1172 "directory, parent directories up to the root file " | 1175 "directory, parent directories up to the root file " |
| 1173 "system directories will also be searched.") | 1176 "system directories will also be searched.") |
| 1174 parser.add_option("--default_presubmit") | 1177 parser.add_option("--default_presubmit") |
| 1175 parser.add_option("--may_prompt", action='store_true', default=False) | 1178 parser.add_option("--may_prompt", action='store_true', default=False) |
| 1176 options, args = parser.parse_args(argv) | 1179 options, args = parser.parse_args(argv) |
| 1177 if options.verbose: | 1180 if options.verbose >= 2: |
| 1178 logging.basicConfig(level=logging.DEBUG) | 1181 logging.basicConfig(level=logging.DEBUG) |
| 1182 elif options.verbose: |
| 1183 logging.basicConfig(level=logging.INFO) |
| 1184 else: |
| 1185 logging.basicConfig(level=logging.ERROR) |
| 1179 change_class, files = load_files(options, args) | 1186 change_class, files = load_files(options, args) |
| 1180 if not change_class: | 1187 if not change_class: |
| 1181 parser.error('For unversioned directory, <files> is not optional.') | 1188 parser.error('For unversioned directory, <files> is not optional.') |
| 1182 if options.verbose: | 1189 logging.info('Found %d file(s).' % len(files)) |
| 1183 print "Found %d file(s)." % len(files) | 1190 try: |
| 1184 results = DoPresubmitChecks(change_class(options.name, | 1191 results = DoPresubmitChecks( |
| 1185 options.description, | 1192 change_class(options.name, |
| 1186 options.root, | 1193 options.description, |
| 1187 files, | 1194 options.root, |
| 1188 options.issue, | 1195 files, |
| 1189 options.patchset), | 1196 options.issue, |
| 1190 options.commit, | 1197 options.patchset), |
| 1191 options.verbose, | 1198 options.commit, |
| 1192 sys.stdout, | 1199 options.verbose, |
| 1193 sys.stdin, | 1200 sys.stdout, |
| 1194 options.default_presubmit, | 1201 sys.stdin, |
| 1195 options.may_prompt) | 1202 options.default_presubmit, |
| 1196 return not results.should_continue() | 1203 options.may_prompt) |
| 1204 return not results.should_continue() |
| 1205 except PresubmitFailure, e: |
| 1206 print >> sys.stderr, e |
| 1207 print >> sys.stderr, 'Maybe your depot_tools is out of date?' |
| 1208 print >> sys.stderr, 'If all fails, contact maruel@' |
| 1209 return 2 |
| 1197 | 1210 |
| 1198 | 1211 |
| 1199 if __name__ == '__main__': | 1212 if __name__ == '__main__': |
| 1200 fix_encoding.fix_encoding() | 1213 fix_encoding.fix_encoding() |
| 1201 sys.exit(Main(None)) | 1214 sys.exit(Main(None)) |
| OLD | NEW |