blob: 17223fe24551597773b46baed8ce3c324af439b8 [file] [log] [blame]
#!/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))