| OLD | NEW |
| 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 """Makes sure files have the right permissions. | 6 """Makes sure files have the right permissions. |
| 7 | 7 |
| 8 Some developers have broken SCM configurations that flip the svn:executable | 8 Some developers have broken SCM configurations that flip the svn:executable |
| 9 permission on for no good reason. Unix developers who run ls --color will then | 9 permission on for no good reason. Unix developers who run ls --color will then |
| 10 see .cc files in green and get confused. | 10 see .cc files in green and get confused. |
| 11 | 11 |
| 12 To ignore a particular file, add it to WHITELIST_FILES. | 12 - For file extensions that must be executable, add it to EXECUTABLE_EXTENSIONS. |
| 13 To ignore a particular extension, add it to WHITELIST_EXTENSIONS. | 13 - For file extensions that must not be executable, add it to |
| 14 To ignore whatever regexps your heart desires, add it WHITELIST_REGEX. | 14 NOT_EXECUTABLE_EXTENSIONS. |
| 15 - To ignore all the files inside a directory, add it to IGNORED_PATHS. |
| 16 - For file base name with ambiguous state and that should not be checked for |
| 17 shebang, add it to IGNORED_FILENAMES. |
| 18 |
| 19 Any file not matching the above will be opened and looked if it has a shebang. |
| 20 It this doesn't match the executable bit on the file, the file will be flagged. |
| 15 | 21 |
| 16 Note that all directory separators must be slashes (Unix-style) and not | 22 Note that all directory separators must be slashes (Unix-style) and not |
| 17 backslashes. All directories should be relative to the source root and all | 23 backslashes. All directories should be relative to the source root and all |
| 18 file paths should be only lowercase. | 24 file paths should be only lowercase. |
| 19 """ | 25 """ |
| 20 | 26 |
| 27 import logging |
| 21 import optparse | 28 import optparse |
| 22 import os | 29 import os |
| 23 import pipes | |
| 24 import re | |
| 25 import stat | 30 import stat |
| 31 import subprocess |
| 26 import sys | 32 import sys |
| 27 | 33 |
| 28 #### USER EDITABLE SECTION STARTS HERE #### | 34 #### USER EDITABLE SECTION STARTS HERE #### |
| 29 | 35 |
| 30 # Files with these extensions are allowed to have executable permissions. | 36 # Files with these extensions must have executable bit set. |
| 31 WHITELIST_EXTENSIONS = [ | 37 EXECUTABLE_EXTENSIONS = ( |
| 32 'bash', | 38 'bat', |
| 33 'bat', | 39 'dll', |
| 34 'dll', | 40 'dylib', |
| 35 'dylib', | 41 'exe', |
| 36 'exe', | 42 ) |
| 37 'pl', | |
| 38 'py', | |
| 39 'rb', | |
| 40 'sed', | |
| 41 'sh', | |
| 42 ] | |
| 43 | 43 |
| 44 # Files that end the following paths are whitelisted too. | 44 # These files must have executable bit set. |
| 45 WHITELIST_FILES = [ | 45 EXECUTABLE_PATHS = ( |
| 46 '/build/gyp_chromium', | 46 # TODO(maruel): Detect ELF files. |
| 47 '/build/linux/dump_app_syms', | 47 'chrome/test/data/webrtc/linux/peerconnection_server', |
| 48 '/build/linux/pkg-config-wrapper', | 48 'chrome/installer/mac/sign_app.sh.in', |
| 49 '/build/mac/strip_from_xcode', | 49 'chrome/installer/mac/sign_versioned_dir.sh.in', |
| 50 '/build/mac/strip_save_dsym', | 50 ) |
| 51 '/chrome/installer/mac/pkg-dmg', | 51 |
| 52 '/chrome/test/data/webrtc/linux/peerconnection_server', | 52 # These files must not have the executable bit set. This is mainly a performance |
| 53 '/chrome/tools/build/linux/chrome-wrapper', | 53 # optimization as these files are not checked for shebang. The list was |
| 54 '/chrome/tools/build/mac/build_app_dmg', | 54 # partially generated from: |
| 55 '/chrome/tools/build/mac/clean_up_old_versions', | 55 # git ls-files | grep "\\." | sed 's/.*\.//' | sort | uniq -c | sort -b -g |
| 56 '/chrome/tools/build/mac/dump_product_syms', | 56 NON_EXECUTABLE_EXTENSIONS = ( |
| 57 '/chrome/tools/build/mac/generate_localizer', | 57 '1', |
| 58 '/chrome/tools/build/mac/make_sign_sh', | 58 '3ds', |
| 59 '/chrome/tools/build/mac/verify_order', | 59 'S', |
| 60 '/o3d/build/gyp_o3d', | 60 'am', |
| 61 '/o3d/gypbuild', | 61 'applescript', |
| 62 '/o3d/installer/linux/debian.in/rules', | 62 'asm', |
| 63 '/third_party/icu/source/runconfigureicu', | 63 'c', |
| 64 '/third_party/gold/gold32', | 64 'cc', |
| 65 '/third_party/gold/gold64', | 65 'cfg', |
| 66 '/third_party/gold/ld', | 66 'chromium', |
| 67 '/third_party/gold/ld.bfd', | 67 'cpp', |
| 68 '/third_party/lcov/bin/gendesc', | 68 'crx', |
| 69 '/third_party/lcov/bin/genhtml', | 69 'cs', |
| 70 '/third_party/lcov/bin/geninfo', | 70 'css', |
| 71 '/third_party/lcov/bin/genpng', | 71 'cur', |
| 72 '/third_party/lcov/bin/lcov', | 72 'def', |
| 73 '/third_party/lcov/bin/mcov', | 73 'der', |
| 74 '/third_party/lcov-1.9/bin/gendesc', | 74 'expected', |
| 75 '/third_party/lcov-1.9/bin/genhtml', | 75 'gif', |
| 76 '/third_party/lcov-1.9/bin/geninfo', | 76 'grd', |
| 77 '/third_party/lcov-1.9/bin/genpng', | 77 'gyp', |
| 78 '/third_party/lcov-1.9/bin/lcov', | 78 'gypi', |
| 79 '/third_party/libxml/linux/xml2-config', | 79 'h', |
| 80 '/third_party/lzma_sdk/executable/7za.exe', | 80 'hh', |
| 81 '/third_party/swig/linux/swig', | 81 'htm', |
| 82 '/third_party/tcmalloc/chromium/src/pprof', | 82 'html', |
| 83 '/tools/deep_memory_profiler/dmprof', | 83 'hyph', |
| 84 '/tools/git/post-checkout', | 84 'ico', |
| 85 '/tools/git/post-merge', | 85 'idl', |
| 86 '/tools/ld_bfd/ld', | 86 'java', |
| 87 ] | 87 'jpg', |
| 88 'js', |
| 89 'json', |
| 90 'm', |
| 91 'm4', |
| 92 'mm', |
| 93 'mms', |
| 94 'mock-http-headers', |
| 95 'nmf', |
| 96 'onc', |
| 97 'pat', |
| 98 'patch', |
| 99 'pdf', |
| 100 'pem', |
| 101 'plist', |
| 102 'png', |
| 103 'proto', |
| 104 'rc', |
| 105 'rfx', |
| 106 'rgs', |
| 107 'rules', |
| 108 'spec', |
| 109 'sql', |
| 110 'srpc', |
| 111 'svg', |
| 112 'tcl', |
| 113 'test', |
| 114 'tga', |
| 115 'txt', |
| 116 'vcproj', |
| 117 'vsprops', |
| 118 'webm', |
| 119 'word', |
| 120 'xib', |
| 121 'xml', |
| 122 'xtb', |
| 123 'zip', |
| 124 ) |
| 88 | 125 |
| 89 # File names that are always whitelisted. (These are all autoconf spew.) | 126 # File names that are always whitelisted. (These are all autoconf spew.) |
| 90 WHITELIST_FILENAMES = set(( | 127 IGNORED_FILENAMES = ( |
| 91 'config.guess', | 128 'config.guess', |
| 92 'config.sub', | 129 'config.sub', |
| 93 'configure', | 130 'configure', |
| 94 'depcomp', | 131 'depcomp', |
| 95 'install-sh', | 132 'install-sh', |
| 96 'missing', | 133 'missing', |
| 97 'mkinstalldirs', | 134 'mkinstalldirs', |
| 135 'naclsdk', |
| 98 'scons', | 136 'scons', |
| 99 'naclsdk', | 137 ) |
| 100 )) | 138 |
| 101 | 139 # File paths starting with one of these will be ignored as well. |
| 102 # File paths that contain these regexps will be whitelisted as well. | 140 IGNORED_PATHS = ( |
| 103 WHITELIST_REGEX = [ | 141 # TODO(maruel): Detect ELF files. |
| 104 re.compile('/third_party/openssl/'), | 142 'chrome/test/data/webrtc/linux/peerconnection_server', |
| 105 re.compile('/third_party/sqlite/'), | 143 'chrome/installer/mac/sign_app.sh.in', |
| 106 re.compile('/third_party/xdg-utils/'), | 144 'chrome/installer/mac/sign_versioned_dir.sh.in', |
| 107 re.compile('/third_party/yasm/source/patched-yasm/config'), | 145 'native_client_sdk/src/build_tools/sdk_tools/third_party/', |
| 108 re.compile('/third_party/ffmpeg/tools'), | 146 'out/', |
| 109 ] | 147 # TODO(maruel): Fix these. |
| 148 'third_party/android_testrunner/', |
| 149 'third_party/closure_linter/', |
| 150 'third_party/devscripts/licensecheck.pl.vanilla', |
| 151 'third_party/hyphen/', |
| 152 'third_party/jemalloc/', |
| 153 'third_party/lcov-1.9/contrib/galaxy/conglomerate_functions.pl', |
| 154 'third_party/lcov-1.9/contrib/galaxy/gen_makefile.sh', |
| 155 'third_party/lcov/contrib/galaxy/conglomerate_functions.pl', |
| 156 'third_party/lcov/contrib/galaxy/gen_makefile.sh', |
| 157 'third_party/libevent/autogen.sh', |
| 158 'third_party/libevent/test/test.sh', |
| 159 'third_party/libxml/linux/xml2-config', |
| 160 'third_party/libxml/src/ltmain.sh', |
| 161 'third_party/mesa/', |
| 162 'third_party/protobuf/', |
| 163 'third_party/python_gflags/gflags.py', |
| 164 'third_party/sqlite/', |
| 165 'third_party/talloc/script/mksyms.sh', |
| 166 'third_party/tcmalloc/', |
| 167 'third_party/tlslite/setup.py', |
| 168 ) |
| 110 | 169 |
| 111 #### USER EDITABLE SECTION ENDS HERE #### | 170 #### USER EDITABLE SECTION ENDS HERE #### |
| 112 | 171 |
| 113 WHITELIST_EXTENSIONS_REGEX = re.compile(r'\.(%s)' % | 172 assert set(EXECUTABLE_EXTENSIONS) & set(NON_EXECUTABLE_EXTENSIONS) == set() |
| 114 '|'.join(WHITELIST_EXTENSIONS)) | 173 |
| 115 | 174 |
| 116 WHITELIST_FILES_REGEX = re.compile(r'(%s)$' % '|'.join(WHITELIST_FILES)) | 175 def capture(cmd, cwd): |
| 117 | 176 """Returns the output of a command. |
| 118 # Set to true for more output. This is set by the command line options. | 177 |
| 119 VERBOSE = False | 178 Ignores the error code or stderr. |
| 120 | 179 """ |
| 121 # Using forward slashes as directory separators, ending in a forward slash. | 180 logging.debug('%s; cwd=%s' % (' '.join(cmd), cwd)) |
| 122 # Set by the command line options. | 181 env = os.environ.copy() |
| 123 BASE_DIRECTORY = '' | 182 env['LANGUAGE'] = 'en_US.UTF-8' |
| 124 | 183 p = subprocess.Popen( |
| 125 # The default if BASE_DIRECTORY is not set on the command line. | 184 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env) |
| 126 DEFAULT_BASE_DIRECTORY = '../../..' | 185 return p.communicate()[0] |
| 127 | 186 |
| 128 # The directories which contain the sources managed by git. | 187 |
| 129 GIT_SOURCE_DIRECTORY = set() | 188 def get_svn_info(dir_path): |
| 130 | 189 """Returns svn meta-data for a svn checkout.""" |
| 131 # The SVN repository url. | 190 if not os.path.isdir(dir_path): |
| 132 SVN_REPO_URL = '' | 191 return {} |
| 133 | 192 out = capture(['svn', 'info', '.', '--non-interactive'], dir_path) |
| 134 # Whether we are using SVN or GIT. | 193 return dict(l.split(': ', 1) for l in out.splitlines() if l) |
| 135 IS_SVN = True | 194 |
| 136 | 195 |
| 137 # Executable permission mask | 196 def get_svn_url(dir_path): |
| 138 EXECUTABLE_PERMISSION = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH | 197 return get_svn_info(dir_path).get('URL') |
| 139 | 198 |
| 140 | 199 |
| 141 def IsWhiteListed(file_path): | 200 def get_svn_root(dir_path): |
| 142 """Returns True if file_path is in our whitelist of files to ignore.""" | 201 """Returns the svn checkout root or None.""" |
| 143 if WHITELIST_EXTENSIONS_REGEX.match(os.path.splitext(file_path)[1]): | 202 svn_url = get_svn_url(dir_path) |
| 144 return True | 203 if not svn_url: |
| 145 if WHITELIST_FILES_REGEX.search(file_path): | |
| 146 return True | |
| 147 if os.path.basename(file_path) in WHITELIST_FILENAMES: | |
| 148 return True | |
| 149 for regex in WHITELIST_REGEX: | |
| 150 if regex.search(file_path): | |
| 151 return True | |
| 152 return False | |
| 153 | |
| 154 | |
| 155 def CheckFile(file_path): | |
| 156 """Checks file_path's permissions. | |
| 157 | |
| 158 Args: | |
| 159 file_path: The file path to check. | |
| 160 | |
| 161 Returns: | |
| 162 Either a string describing the error if there was one, or None if the file | |
| 163 checked out OK. | |
| 164 """ | |
| 165 if VERBOSE: | |
| 166 print 'Checking file: ' + file_path | |
| 167 | |
| 168 file_path_lower = file_path.lower() | |
| 169 if IsWhiteListed(file_path_lower): | |
| 170 return None | 204 return None |
| 171 | 205 logging.info('svn url: %s' % svn_url) |
| 172 # Not whitelisted, stat the file and check permissions. | 206 while True: |
| 173 try: | 207 parent = os.path.dirname(dir_path) |
| 174 st_mode = os.stat(file_path).st_mode | 208 if parent == dir_path: |
| 175 except IOError, e: | 209 return None |
| 176 return 'Failed to stat file: %s' % e | 210 svn_url = svn_url.rsplit('/', 1)[0] |
| 177 except OSError, e: | 211 if svn_url != get_svn_url(parent): |
| 178 return 'Failed to stat file: %s' % e | 212 return dir_path |
| 179 | 213 dir_path = parent |
| 180 if EXECUTABLE_PERMISSION & st_mode: | 214 |
| 181 # Look if the file starts with #!/ | 215 |
| 182 with open(file_path, 'rb') as f: | 216 def get_git_root(dir_path): |
| 183 if f.read(3) == '#!/': | 217 """Returns the git checkout root or None.""" |
| 184 # That's fine. | 218 root = capture(['git', 'rev-parse', '--show-toplevel'], dir_path).strip() |
| 185 return None | 219 if root: |
| 186 # TODO(maruel): Check that non-executable file do not start with a shebang. | 220 return root |
| 187 error = 'Contains executable permission' | 221 |
| 188 if VERBOSE: | 222 |
| 189 return '%s: %06o' % (error, st_mode) | 223 def is_ignored(rel_path): |
| 190 return error | 224 """Returns True if rel_path is in our whitelist of files to ignore.""" |
| 191 return None | 225 rel_path = rel_path.lower() |
| 192 | 226 return ( |
| 193 | 227 os.path.basename(rel_path) in IGNORED_FILENAMES or |
| 194 def ShouldCheckDirectory(dir_path): | 228 rel_path.startswith(IGNORED_PATHS)) |
| 195 """Determine if we should check the content of dir_path.""" | 229 |
| 196 if not IS_SVN: | 230 |
| 197 return dir_path in GIT_SOURCE_DIRECTORY | 231 def must_be_executable(rel_path): |
| 198 repo_url = GetSvnRepositoryRoot(dir_path) | 232 """The file name represents a file type that must have the executable bit |
| 199 if not repo_url: | 233 set. |
| 200 return False | 234 """ |
| 201 return repo_url == SVN_REPO_URL | 235 return ( |
| 202 | 236 os.path.splitext(rel_path)[1][1:].lower() in EXECUTABLE_EXTENSIONS or |
| 203 | 237 rel_path in EXECUTABLE_PATHS) |
| 204 def CheckDirectory(dir_path): | 238 |
| 205 """Check the files in dir_path; recursively check its subdirectories.""" | 239 |
| 206 # Collect a list of all files and directories to check. | 240 def must_not_be_executable(rel_path): |
| 207 files_to_check = [] | 241 """The file name represents a file type that must not have the executable |
| 208 dirs_to_check = [] | 242 bit set. |
| 209 success = True | 243 """ |
| 210 contents = os.listdir(dir_path) | 244 return os.path.splitext(rel_path)[1][1:].lower() in NON_EXECUTABLE_EXTENSIONS |
| 211 for cur in contents: | 245 |
| 212 full_path = os.path.join(dir_path, cur) | 246 |
| 213 if os.path.isdir(full_path) and ShouldCheckDirectory(full_path): | 247 def has_executable_bit(full_path): |
| 214 dirs_to_check.append(full_path) | 248 """Returns if any executable bit is set.""" |
| 215 elif os.path.isfile(full_path): | 249 permission = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
| 216 files_to_check.append(full_path) | 250 return bool(permission & os.stat(full_path).st_mode) |
| 217 | 251 |
| 218 # First check all files in this directory. | 252 |
| 219 for cur_file in files_to_check: | 253 def has_shebang(full_path): |
| 220 file_status = CheckFile(cur_file) | 254 """Returns if the file starts with #!/. |
| 221 if file_status is not None: | 255 |
| 222 print 'ERROR in %s\n%s' % (cur_file, file_status) | 256 file_path is the absolute path to the file. |
| 223 success = False | 257 """ |
| 224 | 258 with open(full_path, 'rb') as f: |
| 225 # Next recurse into the subdirectories. | 259 return f.read(3) == '#!/' |
| 226 for cur_dir in dirs_to_check: | 260 |
| 227 if not CheckDirectory(cur_dir): | 261 |
| 228 success = False | 262 class ApiBase(object): |
| 229 return success | 263 def __init__(self, root_dir, bare_output): |
| 230 | 264 self.root_dir = root_dir |
| 231 | 265 self.bare_output = bare_output |
| 232 def GetGitSourceDirectory(root): | 266 self.count = 0 |
| 233 """Returns a set of the directories to be checked. | 267 self.count_shebang = 0 |
| 234 | 268 |
| 235 Args: | 269 def check_file(self, rel_path): |
| 236 root: The repository root where a .git directory must exist. | 270 """Checks file_path's permissions and returns an error if it is |
| 237 | 271 inconsistent. |
| 238 Returns: | 272 |
| 239 A set of directories which contain sources managed by git. | 273 It is assumed that the file is not ignored by is_ignored(). |
| 240 """ | 274 |
| 241 git_source_directory = set() | 275 If the file name is matched with must_be_executable() or |
| 242 popen_out = os.popen('cd %s && git ls-files --full-name .' % | 276 must_not_be_executable(), only its executable bit is checked. |
| 243 pipes.quote(root)) | 277 Otherwise, the 3 first bytes of the file are read to verify if it has a |
| 244 for line in popen_out: | 278 shebang and compares this with the executable bit on the file. |
| 245 dir_path = os.path.join(root, os.path.dirname(line)) | 279 """ |
| 246 git_source_directory.add(dir_path) | 280 logging.debug('check_file(%s)' % rel_path) |
| 247 git_source_directory.add(root) | 281 self.count += 1 |
| 248 return git_source_directory | 282 |
| 249 | 283 full_path = os.path.join(self.root_dir, rel_path) |
| 250 | 284 try: |
| 251 def GetSvnRepositoryRoot(dir_path): | 285 bit = has_executable_bit(full_path) |
| 252 """Returns the repository root for a directory. | 286 except OSError: |
| 253 | 287 # It's faster to catch exception than call os.path.islink(). Chromium |
| 254 Args: | 288 # tree happens to have invalid symlinks under |
| 255 dir_path: A directory where a .svn subdirectory should exist. | 289 # third_party/openssl/openssl/test/. |
| 256 | 290 return None |
| 257 Returns: | 291 |
| 258 The svn repository that contains dir_path or None. | 292 if must_be_executable(rel_path): |
| 259 """ | 293 if not bit: |
| 260 svn_dir = os.path.join(dir_path, '.svn') | 294 if self.bare_output: |
| 261 if not os.path.isdir(svn_dir): | 295 return rel_path |
| 262 return None | 296 return '%s: Must have executable bit set' % rel_path |
| 263 popen_out = os.popen('cd %s && svn info' % pipes.quote(dir_path)) | 297 return |
| 264 for line in popen_out: | 298 if must_not_be_executable(rel_path): |
| 265 if line.startswith('Repository Root: '): | 299 if bit: |
| 266 return line[len('Repository Root: '):].rstrip() | 300 if self.bare_output: |
| 267 return None | 301 return rel_path |
| 268 | 302 return '%s: Must not have executable bit set' % rel_path |
| 269 | 303 return |
| 270 def main(argv): | 304 |
| 305 # For the others, it depends on the shebang. |
| 306 shebang = has_shebang(full_path) |
| 307 self.count_shebang += 1 |
| 308 if bit != shebang: |
| 309 if self.bare_output: |
| 310 return rel_path |
| 311 if bit: |
| 312 return '%s: Has executable bit but not shebang' % rel_path |
| 313 else: |
| 314 return '%s: Has shebang but not executable bit' % rel_path |
| 315 |
| 316 def check_dir(self, rel_path): |
| 317 return self.check(rel_path) |
| 318 |
| 319 def check(self, start_dir): |
| 320 """Check the files in start_dir, recursively check its subdirectories.""" |
| 321 errors = [] |
| 322 items = self.list_dir(start_dir) |
| 323 logging.info('check(%s) -> %d' % (start_dir, len(items))) |
| 324 for item in items: |
| 325 full_path = os.path.join(self.root_dir, start_dir, item) |
| 326 rel_path = full_path[len(self.root_dir) + 1:] |
| 327 if is_ignored(rel_path): |
| 328 continue |
| 329 if os.path.isdir(full_path): |
| 330 # Depth first. |
| 331 errors.extend(self.check_dir(rel_path)) |
| 332 else: |
| 333 error = self.check_file(rel_path) |
| 334 if error: |
| 335 errors.append(error) |
| 336 return errors |
| 337 |
| 338 def list_dir(self, start_dir): |
| 339 """Lists all the files and directory inside start_dir.""" |
| 340 return sorted( |
| 341 x for x in os.listdir(os.path.join(self.root_dir, start_dir)) |
| 342 if not x.startswith('.') |
| 343 ) |
| 344 |
| 345 |
| 346 class ApiSvnQuick(ApiBase): |
| 347 """Returns all files in svn-versioned directories, independent of the fact if |
| 348 they are versionned. |
| 349 |
| 350 Uses svn info in each directory to determine which directories should be |
| 351 crawled. |
| 352 """ |
| 353 def __init__(self, *args): |
| 354 super(ApiSvnQuick, self).__init__(*args) |
| 355 self.url = get_svn_url(self.root_dir) |
| 356 |
| 357 def check_dir(self, rel_path): |
| 358 url = self.url + '/' + rel_path |
| 359 if get_svn_url(os.path.join(self.root_dir, rel_path)) != url: |
| 360 return [] |
| 361 return super(ApiSvnQuick, self).check_dir(rel_path) |
| 362 |
| 363 |
| 364 class ApiAllFilesAtOnceBase(ApiBase): |
| 365 _files = None |
| 366 |
| 367 def list_dir(self, start_dir): |
| 368 """Lists all the files and directory inside start_dir.""" |
| 369 if self._files is None: |
| 370 self._files = sorted(self._get_all_files()) |
| 371 if not self.bare_output: |
| 372 print 'Found %s files' % len(self._files) |
| 373 start_dir = start_dir[len(self.root_dir) + 1:] |
| 374 return [ |
| 375 x[len(start_dir):] for x in self._files if x.startswith(start_dir) |
| 376 ] |
| 377 |
| 378 def _get_all_files(self): |
| 379 """Lists all the files and directory inside self._root_dir.""" |
| 380 raise NotImplementedError() |
| 381 |
| 382 |
| 383 class ApiSvn(ApiAllFilesAtOnceBase): |
| 384 """Returns all the subversion controlled files. |
| 385 |
| 386 Warning: svn ls is abnormally slow. |
| 387 """ |
| 388 def _get_all_files(self): |
| 389 cmd = ['svn', 'ls', '--non-interactive', '--recursive'] |
| 390 return ( |
| 391 x for x in capture(cmd, self.root_dir).splitlines() |
| 392 if not x.endswith(os.path.sep)) |
| 393 |
| 394 |
| 395 class ApiGit(ApiAllFilesAtOnceBase): |
| 396 def _get_all_files(self): |
| 397 return capture(['git', 'ls-files'], cwd=self.root_dir).splitlines() |
| 398 |
| 399 |
| 400 def get_scm(dir_path, bare): |
| 401 """Returns a properly configured ApiBase instance.""" |
| 402 cwd = os.getcwd() |
| 403 root = get_svn_root(dir_path or cwd) |
| 404 if root: |
| 405 if not bare: |
| 406 print('Found subversion checkout at %s' % root) |
| 407 return ApiSvnQuick(dir_path or root, bare) |
| 408 root = get_git_root(dir_path or cwd) |
| 409 if root: |
| 410 if not bare: |
| 411 print('Found git repository at %s' % root) |
| 412 return ApiGit(dir_path or root, bare) |
| 413 |
| 414 # Returns a non-scm aware checker. |
| 415 if not bare: |
| 416 print('Failed to determine the SCM for %s' % dir_path) |
| 417 return ApiBase(dir_path or cwd, bare) |
| 418 |
| 419 |
| 420 def main(): |
| 271 usage = """Usage: python %prog [--root <root>] [tocheck] | 421 usage = """Usage: python %prog [--root <root>] [tocheck] |
| 272 tocheck Specifies the directory, relative to root, to check. This defaults | 422 tocheck Specifies the directory, relative to root, to check. This defaults |
| 273 to "." so it checks everything. | 423 to "." so it checks everything. |
| 274 | 424 |
| 275 Examples: | 425 Examples: |
| 276 python checkperms.py | 426 python %prog |
| 277 python checkperms.py --root /path/to/source chrome""" | 427 python %prog --root /path/to/source chrome""" |
| 278 | 428 |
| 279 option_parser = optparse.OptionParser(usage=usage) | 429 parser = optparse.OptionParser(usage=usage) |
| 280 option_parser.add_option('--root', dest='base_directory', | 430 parser.add_option( |
| 281 default=DEFAULT_BASE_DIRECTORY, | 431 '--root', |
| 282 help='Specifies the repository root. This defaults ' | 432 help='Specifies the repository root. This defaults ' |
| 283 'to %default relative to the script file, which ' | 433 'to the checkout repository root') |
| 284 'will normally be the repository root.') | 434 parser.add_option( |
| 285 option_parser.add_option('-v', '--verbose', action='store_true', | 435 '-v', '--verbose', action='count', default=0, help='Print debug logging') |
| 286 help='Print debug logging') | 436 parser.add_option( |
| 287 options, args = option_parser.parse_args() | 437 '--bare', |
| 288 | 438 action='store_true', |
| 289 global VERBOSE | 439 default=False, |
| 290 if options.verbose: | 440 help='Prints the bare filename triggering the checks') |
| 291 VERBOSE = True | 441 options, args = parser.parse_args() |
| 292 | 442 |
| 293 # Optional base directory of the repository. | 443 levels = [logging.ERROR, logging.INFO, logging.DEBUG] |
| 294 global BASE_DIRECTORY | 444 logging.basicConfig(level=levels[min(len(levels) - 1, options.verbose)]) |
| 295 if (not options.base_directory or | 445 |
| 296 options.base_directory == DEFAULT_BASE_DIRECTORY): | 446 if len(args) > 1: |
| 297 BASE_DIRECTORY = os.path.abspath( | 447 parser.error('Too many arguments used') |
| 298 os.path.join(os.path.abspath(argv[0]), DEFAULT_BASE_DIRECTORY)) | 448 |
| 449 if options.root: |
| 450 options.root = os.path.abspath(options.root) |
| 451 |
| 452 api = get_scm(options.root, options.bare) |
| 453 if args: |
| 454 start_dir = args[0] |
| 299 else: | 455 else: |
| 300 BASE_DIRECTORY = os.path.abspath(argv[2]) | 456 start_dir = api.root_dir |
| 301 | 457 |
| 302 # Figure out which directory we have to check. | 458 errors = api.check(start_dir) |
| 303 if not args: | 459 |
| 304 # No directory to check specified, use the repository root. | 460 if not options.bare: |
| 305 start_dir = BASE_DIRECTORY | 461 print 'Processed %s files, %d files where tested for shebang' % ( |
| 306 elif len(args) == 1: | 462 api.count, api.count_shebang) |
| 307 # Directory specified. Start here. It's supposed to be relative to the | 463 |
| 308 # base directory. | 464 if errors: |
| 309 start_dir = os.path.abspath(os.path.join(BASE_DIRECTORY, args[0])) | 465 if not options.bare: |
| 310 else: | 466 print '\nFAILED\n' |
| 311 # More than one argument, we don't handle this. | 467 print '\n'.join(errors) |
| 312 option_parser.print_help() | |
| 313 return 1 | 468 return 1 |
| 314 | 469 if not options.bare: |
| 315 print 'Using base directory:', BASE_DIRECTORY | 470 print '\nSUCCESS\n' |
| 316 print 'Checking directory:', start_dir | |
| 317 | |
| 318 BASE_DIRECTORY = BASE_DIRECTORY.replace('\\', '/') | |
| 319 start_dir = start_dir.replace('\\', '/') | |
| 320 | |
| 321 success = True | |
| 322 if os.path.exists(os.path.join(BASE_DIRECTORY, '.svn')): | |
| 323 global SVN_REPO_URL | |
| 324 SVN_REPO_URL = GetSvnRepositoryRoot(BASE_DIRECTORY) | |
| 325 if not SVN_REPO_URL: | |
| 326 print 'Cannot determine the SVN repo URL' | |
| 327 success = False | |
| 328 elif os.path.exists(os.path.join(BASE_DIRECTORY, '.git')): | |
| 329 global IS_SVN | |
| 330 IS_SVN = False | |
| 331 global GIT_SOURCE_DIRECTORY | |
| 332 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY) | |
| 333 if not GIT_SOURCE_DIRECTORY: | |
| 334 print 'Cannot determine the list of GIT directories' | |
| 335 success = False | |
| 336 else: | |
| 337 print 'Cannot determine the SCM used in %s' % BASE_DIRECTORY | |
| 338 success = False | |
| 339 | |
| 340 if success: | |
| 341 success = CheckDirectory(start_dir) | |
| 342 if not success: | |
| 343 print '\nFAILED\n' | |
| 344 return 1 | |
| 345 print '\nSUCCESS\n' | |
| 346 return 0 | 471 return 0 |
| 347 | 472 |
| 348 | 473 |
| 349 if '__main__' == __name__: | 474 if '__main__' == __name__: |
| 350 sys.exit(main(sys.argv)) | 475 sys.exit(main()) |
| OLD | NEW |