#!/usr/bin/env python # # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # '''control-chars.py: Subversion repository hook script that rejects filenames which contain control characters. Expects to be called like a pre-commit hook: control-chars.py Latest version should be available at http://svn.apache.org/repos/asf/subversion/trunk/tools/hook-scripts/ See validate-files.py for more generic validations.''' import sys import re import posixpath import svn import svn.fs import svn.repos import svn.core # Can't hurt to disallow chr(0), though the C API will never pass one anyway. control_chars = set( [chr(i) for i in range(32)] ) control_chars.add(chr(127)) def check_node(node, path): "check NODE for control characters. PATH is used for error messages" if node.action == 'A': if any((c in control_chars) for c in node.name): sys.stderr.write("'%s' contains a control character" % path) return 3 def walk_tree(node, path, callback): "Walk NODE" if not node: return 0 ret_val = callback(node, path) if ret_val > 0: return ret_val node = node.child if not node: return 0 while node: full_path = posixpath.join(path, node.name) ret_val = walk_tree(node, full_path, callback) # If we ran into an error just return up the stack all the way if ret_val > 0: return ret_val node = node.sibling return 0 def usage(): sys.stderr.write("Invalid arguments, expects to be called like a pre-commit hook.") def main(ignored_pool, argv): if len(argv) < 3: usage() return 2 repos_path = svn.core.svn_path_canonicalize(argv[1]) txn_name = argv[2] if not repos_path or not txn_name: usage() return 2 repos = svn.repos.svn_repos_open(repos_path) fs = svn.repos.svn_repos_fs(repos) txn = svn.fs.svn_fs_open_txn(fs, txn_name) txn_root = svn.fs.svn_fs_txn_root(txn) base_rev = svn.fs.svn_fs_txn_base_revision(txn) if base_rev is None or base_rev <= svn.core.SVN_INVALID_REVNUM: sys.stderr.write("Transaction '%s' is not based on a revision" % txn_name) return 2 base_root = svn.fs.svn_fs_revision_root(fs, base_rev) editor, editor_baton = svn.repos.svn_repos_node_editor(repos, base_root, txn_root) try: svn.repos.svn_repos_replay2(txn_root, "", svn.core.SVN_INVALID_REVNUM, False, editor, editor_baton, None, None) except svn.core.SubversionException as e: # If we get a file not found error then some file has a newline in it and # fsfs's own transaction is now corrupted. if e.apr_err == svn.core.SVN_ERR_FS_NOT_FOUND: match = re.search("path '(.*?)'", e.message) if not match: sys.stderr.write(repr(e)) return 2 path = match.group(1) sys.stderr.write("Path name that contains '%s' has a newline." % path) return 3 # fs corrupt error probably means that there is probably both # file and file\n in the transaction. However, we can't really determine # which files since the transaction is broken. Even if we didn't reject # this it would not be able to be committed. This just gives a better # error message. elif e.apr_err == svn.core.SVN_ERR_FS_CORRUPT: sys.stderr.write("Some path contains a newline causing: %s" % repr(e)) return 3 else: sys.stderr.write(repr(e)) return 2 tree = svn.repos.svn_repos_node_from_baton(editor_baton) return walk_tree(tree, "/", check_node) if __name__ == '__main__': sys.exit(svn.core.run_app(main, sys.argv))