diff options
author | Guido Günther <agx@sigxcpu.org> | 2011-07-29 15:12:20 +0200 |
---|---|---|
committer | Guido Günther <agx@sigxcpu.org> | 2011-07-29 15:12:20 +0200 |
commit | 4431296f58edb533fab0fb067bc33814c9186997 (patch) | |
tree | 1514e31639f15074d4176500b47e4668ca428e7a |
Git commit hooks
-rwxr-xr-x | git-hooks/pre-commit | 61 | ||||
-rw-r--r-- | git-hooks/puppethooks/__init__.py | 1 | ||||
-rw-r--r-- | git-hooks/puppethooks/checkers.py | 71 | ||||
-rw-r--r-- | git-hooks/puppethooks/git.py | 69 | ||||
-rwxr-xr-x | git-hooks/update | 61 |
5 files changed, 263 insertions, 0 deletions
diff --git a/git-hooks/pre-commit b/git-hooks/pre-commit new file mode 100755 index 0000000..cd031c3 --- /dev/null +++ b/git-hooks/pre-commit @@ -0,0 +1,61 @@ +#!/usr/bin/python -u +# vim: set fileencoding=utf-8 : +# +# (C) 2011 Guido Guenther <agx@sigxcpu.org> +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import os +import shutil +import sys +import puppethooks.checkers as checkers +import puppethooks.git as git + +debug = True + +def main(): + ret = 0 + zero = '0000000000000000000000000000000000000000' + + try: + os.environ['GIT_DIR'] + except KeyError: + print >>sys.stderr, "Don't run this script from the command line." + sys.exit(1) + + rev = 'HEAD' + if not git.verify_rev(rev): + rev=zero + + for old_mode, new_mode, old_sha, new_sha, status, name in git.diff_cached(rev): + if debug: + print "Checking %s" % name + try: + checkers.check(name) + except checkers.CheckFailed as details: + print >>sys.stderr, "Syntax check of '%s' failed" % name + if details: + if details.args[2]: + print >>sys.stderr, details.args[2] + print >>sys.stderr, details.args[0] + ret = 1 + + print "Checking files syntax done. %s" % ["", "Will deny commit due to errors."][ret] + return ret + +if __name__ == '__main__': + sys.exit(main()) + +# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: diff --git a/git-hooks/puppethooks/__init__.py b/git-hooks/puppethooks/__init__.py new file mode 100644 index 0000000..d950bdf --- /dev/null +++ b/git-hooks/puppethooks/__init__.py @@ -0,0 +1 @@ +# vim: set fileencoding=utf-8 : diff --git a/git-hooks/puppethooks/checkers.py b/git-hooks/puppethooks/checkers.py new file mode 100644 index 0000000..bc93b46 --- /dev/null +++ b/git-hooks/puppethooks/checkers.py @@ -0,0 +1,71 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2011 Guido Guenther <agx@sigxcpu.org> +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import subprocess + +class CheckFailed(Exception): + def __init__(self, error, path, details=None): + self.args = (error, path, details) + +def puppet_checker(path): + """Check syntax of puppet manifests""" + cmd = ['puppet', '--color=false', '--noop', '--vardir=/tmp', '--confdir=/tmp', '--parseonly', path] + popen = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + output = popen.communicate() + if popen.returncode: + # Using '--ignoreimport is broken as of puppet 2.6.X: + # https://projects.puppetlabs.com/issues/5081 + # so we check the error message to ignore import errors: + if not 'found for import of' in output[0]: + verbose = "\n".join(output).strip() + raise CheckFailed("You can verify the error using '%s'" % " ".join(cmd), path, verbose) + + +def ruby_checker(path): + """Simple ruby syntax checker""" + cmd = ['ruby', '-c', path] + popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + popen.communicate() + if popen.returncode: + raise CheckFailed("You can verify the error using '%s'" % " ".join(cmd), path) + + +def template_checker(path): + """Check syntax of erb templates""" + cmd_str = "erb -P -x -T '-' %s | ruby -c " + cmd = [ cmd_str % path] + popen = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True) + popen.communicate() + if popen.returncode: + raise CheckFailed("You can verify the error using '%s'" % cmd[0], path) + + +_file_checkers = { 'pp': puppet_checker, + 'rb': ruby_checker, + 'erb': template_checker, + } + +def check(path): + try: + extension = path.rsplit('.',1)[-1] + except IndexError: + pass # ignore files without extension + + try: + _file_checkers[extension](path) + except KeyError: + pass # ignore unknown extensions diff --git a/git-hooks/puppethooks/git.py b/git-hooks/puppethooks/git.py new file mode 100644 index 0000000..7bae5b7 --- /dev/null +++ b/git-hooks/puppethooks/git.py @@ -0,0 +1,69 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2011 Guido Guenther <agx@sigxcpu.org> +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Funciton to ease git usage + +import os +import subprocess +import puppethooks.checkers + +def diff_tree(oldrev, newrev): + # We use -z to handle filenames with spaces, tabs, etc. + cmd = ['git', 'diff-tree', '--raw', '-r', '-z', oldrev, newrev ] + popen = subprocess.Popen(cmd, stdout=subprocess.PIPE) + # Parse the '\0' terminated filenames out of the metadata + output = popen.communicate()[0].split('\0') + for i in xrange(0, len(output)-2, 2): + meta, filename = output[i:i+2] + yield meta.split() + [ filename ] + + +def diff_cached(rev): + """Check the index against the given commit""" + # We use -z to handle filenames with spaces, tabs, etc. + cmd = ['git', 'diff', '--cached', '--diff-filter=AM', '--raw', '-z', rev ] + popen = subprocess.Popen(cmd, stdout=subprocess.PIPE) + # Parse the '\0' terminated filenames out of the metadata + output = popen.communicate()[0].split('\0') + for i in xrange(0, len(output)-2, 2): + meta, filename = output[i:i+2] + yield meta.split() + [ filename ] + + +def verify_rev(rev): + """Verify ref, return True if it's o.k.""" + return not subprocess.call(['git', 'rev-parse', '--verify', rev]) + + +def write_tmp_blob(dir, name, sha): + """Write the git blog to a temporary file""" + cmd = ['git', 'cat-file', '-p', sha ] + abs_path = os.path.join(dir, os.path.basename(name)) + popen = subprocess.Popen(cmd, stdout=subprocess.PIPE) + output = popen.communicate()[0] + f = file(abs_path, 'w') + f.write(output) + f.close() + return abs_path + + +def do_check(name, tmpdir, sha): + """Call the checker for a given file path and content sha""" + path = write_tmp_blob(tmpdir, name, sha) + puppethooks.checkers.check(path) + os.unlink(path) + diff --git a/git-hooks/update b/git-hooks/update new file mode 100755 index 0000000..38859c2 --- /dev/null +++ b/git-hooks/update @@ -0,0 +1,61 @@ +#!/usr/bin/python -u +# vim: set fileencoding=utf-8 : + +import os +import shutil +import subprocess +import sys +import tempfile +import puppethooks.git as git +import puppethooks.checkers as checkers + +debug = True + +def main(argv): + tmpdir = None + ret = 0 + zero = '0000000000000000000000000000000000000000' + + try: + os.environ['GIT_DIR'] + except KeyError: + print >>sys.stderr, "Don't run this script from the command line." + sys.exit(1) + + refname, oldrev, newrev = sys.argv[1:4] + if zero in [oldrev, newrev]: + return 0 + + print "Checking files syntax." + try: + tmpdir = tempfile.mkdtemp() + + for old_mode, new_mode, old_sha, new_sha, status, name in git.diff_tree(oldrev, newrev): + if new_sha == zero: + continue + + try: + extension = name.rsplit('.',1)[-1] + except IndexError: + continue + + try: + if debug: + print "Checking %s" % name + git.do_check(name, tmpdir, new_sha) + except KeyError: + continue + except checkers.CheckFailed: + print >>sys.stderr, "Syntax check of '%s' failed" % name + ret = 1 + finally: + if tmpdir: + shutil.rmtree(tmpdir) + + print "Checking files syntax done. %s" % ["", "Will deny commit due to errors."][ret] + return ret + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + +# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: |