| #!/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 <REPOS-PATH> <TXN-NAME> | |
| 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)) |