| #!/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. |
| # |
| # |
| |
| """\ |
| Check that any files pending commit into a Subversion repository have |
| suitable file extensions, printing an error and exiting with an |
| errorful value if any files fail validation. This is intended to be |
| used as a Subversion pre-commit hook script. |
| |
| Syntax 1: |
| |
| validate-extensions.py REPOS_PATH TXN_NAME deny EXT [...] |
| |
| Ensure that any newly added files do *not* have one of the provided |
| file extensions. |
| |
| |
| Syntax 2: |
| |
| validate-extensions.py REPOS_PATH TXN_NAME allow EXT [...] |
| |
| Ensure that any newly added files *do* have one of the provided |
| file extensions. (Extension-less files are disallowed.) |
| |
| """ |
| |
| import sys |
| import os |
| from svn import repos, fs, core |
| |
| def validate_added_extensions(repos_path, txn_name, extensions, action): |
| # Open the repository and transaction. |
| fs_ptr = repos.fs(repos.open(repos_path)) |
| txn_t = fs.open_txn(fs_ptr, txn_name) |
| txn_root = fs.txn_root(txn_t) |
| |
| # Fetch the changes made in this transaction. |
| changes = fs.svn_fs_paths_changed(txn_root) |
| paths = changes.keys() |
| |
| # Check the changes. |
| for path in paths: |
| change = changes[path] |
| |
| # Always allow deletions. |
| if change.change_kind == fs.path_change_delete: |
| continue |
| |
| # Always allow non-files. |
| kind = fs.check_path(txn_root, path) |
| if kind != core.svn_node_file: |
| continue |
| |
| # If this was a newly added (without history) file ... |
| if ((change.change_kind == fs.path_change_replace) \ |
| or (change.change_kind == fs.path_change_add)): |
| copyfrom_rev, copyfrom_path = fs.copied_from(txn_root, path) |
| if copyfrom_rev == core.SVN_INVALID_REVNUM: |
| |
| # ... then check it for a valid extension. |
| base, ext = os.path.splitext(path) |
| if ext: |
| ext = ext[1:].lower() |
| if ((ext in extensions) and (action == 'deny')) \ |
| or ((ext not in extensions) and (action == 'allow')): |
| sys.stderr.write("Path '%s' has an extension disallowed by server " |
| "configuration.\n" % (path)) |
| sys.exit(1) |
| |
| def usage_and_exit(errmsg=None): |
| stream = errmsg and sys.stderr or sys.stdout |
| stream.write(__doc__) |
| if errmsg: |
| stream.write("ERROR: " + errmsg + "\n") |
| sys.exit(errmsg and 1 or 0) |
| |
| def main(): |
| argc = len(sys.argv) |
| if argc < 5: |
| usage_and_exit("Not enough arguments.") |
| repos_path = sys.argv[1] |
| txn_name = sys.argv[2] |
| action = sys.argv[3] |
| if action not in ("allow", "deny"): |
| usage_and_exit("Invalid action '%s'. Expected either 'allow' or 'deny'." |
| % (action)) |
| extensions = [x.lower() for x in sys.argv[4:]] |
| validate_added_extensions(repos_path, txn_name, extensions, action) |
| |
| if __name__ == "__main__": |
| main() |