summaryrefslogtreecommitdiff
path: root/tools/hook-scripts/validate-files.py
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2015-03-18 13:33:26 +0000
committer <>2015-07-08 14:41:01 +0000
commitbb0ef45f7c46b0ae221b26265ef98a768c33f820 (patch)
tree98bae10dde41c746c51ae97ec4f879e330415aa7 /tools/hook-scripts/validate-files.py
parent239dfafe71711b2f4c43d7b90a1228d7bdc5195e (diff)
downloadsubversion-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-xtools/hook-scripts/validate-files.py159
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)