diff options
| author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2015-03-18 13:33:26 +0000 |
|---|---|---|
| committer | <> | 2015-07-08 14:41:01 +0000 |
| commit | bb0ef45f7c46b0ae221b26265ef98a768c33f820 (patch) | |
| tree | 98bae10dde41c746c51ae97ec4f879e330415aa7 /tools/hook-scripts/validate-files.py | |
| parent | 239dfafe71711b2f4c43d7b90a1228d7bdc5195e (diff) | |
| download | subversion-tarball-bb0ef45f7c46b0ae221b26265ef98a768c33f820.tar.gz | |
Imported from /home/lorry/working-area/delta_subversion-tarball/subversion-1.8.13.tar.gz.subversion-1.8.13
Diffstat (limited to 'tools/hook-scripts/validate-files.py')
| -rwxr-xr-x | tools/hook-scripts/validate-files.py | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/tools/hook-scripts/validate-files.py b/tools/hook-scripts/validate-files.py new file mode 100755 index 0000000..7169251 --- /dev/null +++ b/tools/hook-scripts/validate-files.py @@ -0,0 +1,159 @@ +#!/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. +# + +"""Subversion pre-commit hook script that runs user configured commands +to validate files in the commit and reject the commit if the commands +exit with a non-zero exit code. The script expects a validate-files.conf +file placed in the conf dir under the repo the commit is for.""" + +import sys +import os +import subprocess +import fnmatch + +# Deal with the rename of ConfigParser to configparser in Python3 +try: + # Python >= 3.0 + import configparser +except ImportError: + # Python < 3.0 + import ConfigParser as configparser + +class Config(configparser.SafeConfigParser): + """Superclass of SafeConfigParser with some customizations + for this script""" + def optionxform(self, option): + """Redefine optionxform so option names are case sensitive""" + return option + + def getlist(self, section, option): + """Returns value of option as a list using whitespace to + split entries""" + value = self.get(section, option) + if value: + return value.split() + else: + return None + + def get_matching_rules(self, repo): + """Return list of unique rules names that apply to a given repo""" + rules = {} + for option in self.options('repositories'): + if fnmatch.fnmatch(repo, option): + for rule in self.getlist('repositories', option): + rules[rule] = True + return rules.keys() + + def get_rule_section_name(self, rule): + """Given a rule name provide the section name it is defined in.""" + return 'rule:%s' % (rule) + +class Commands: + """Class to handle logic of running commands""" + def __init__(self, config): + self.config = config + + def svnlook_changed(self, repo, txn): + """Provide list of files changed in txn of repo""" + svnlook = self.config.get('DEFAULT', 'svnlook') + cmd = "'%s' changed -t '%s' '%s'" % (svnlook, txn, repo) + p = subprocess.Popen(cmd, shell=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + changed = [] + while True: + line = p.stdout.readline() + if not line: + break + line = line.decode().strip() + text_mod = line[0:1] + # Only if the contents of the file changed (by addition or update) + # directories always end in / in the svnlook changed output + if line[-1] != "/" and (text_mod == "A" or text_mod == "U"): + changed.append(line[4:]) + + # wait on the command to finish so we can get the + # returncode/stderr output + data = p.communicate() + if p.returncode != 0: + sys.stderr.write(data[1].decode()) + sys.exit(2) + + return changed + + def user_command(self, section, repo, txn, fn): + """ Run the command defined for a given section. + Replaces $REPO, $TXN and $FILE with the repo, txn and fn arguments + in the defined command. + + Returns a tuple of the exit code and the stderr output of the command""" + cmd = self.config.get(section, 'command') + cmd_env = os.environ.copy() + cmd_env['REPO'] = repo + cmd_env['TXN'] = txn + cmd_env['FILE'] = fn + p = subprocess.Popen(cmd, shell=True, env=cmd_env, stderr=subprocess.PIPE) + data = p.communicate() + return (p.returncode, data[1].decode()) + +def main(repo, txn): + exitcode = 0 + config = Config() + config.read(os.path.join(repo, 'conf', 'validate-files.conf')) + commands = Commands(config) + + rules = config.get_matching_rules(repo) + + # no matching rules so nothing to do + if len(rules) == 0: + sys.exit(0) + + changed = commands.svnlook_changed(repo, txn) + # this shouldn't ever happen + if len(changed) == 0: + sys.exit(0) + + for rule in rules: + section = config.get_rule_section_name(rule) + pattern = config.get(section, 'pattern') + + # skip leading slashes if present in the pattern + if pattern[0] == '/': pattern = pattern[1:] + + for fn in fnmatch.filter(changed, pattern): + (returncode, err_mesg) = commands.user_command(section, repo, + txn, fn) + if returncode != 0: + sys.stderr.write( + "\nError validating file '%s' with rule '%s' " \ + "(exit code %d):\n" % (fn, rule, returncode)) + sys.stderr.write(err_mesg) + exitcode = 1 + + return exitcode + +if __name__ == "__main__": + if len(sys.argv) != 3: + sys.stderr.write("invalid args\n") + sys.exit(0) + + try: + sys.exit(main(sys.argv[1], sys.argv[2])) + except configparser.Error as e: + sys.stderr.write("Error with the validate-files.conf: %s\n" % e) + sys.exit(2) |
