aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2011-07-29 15:12:20 +0200
committerGuido Günther <agx@sigxcpu.org>2011-07-29 15:12:20 +0200
commit4431296f58edb533fab0fb067bc33814c9186997 (patch)
tree1514e31639f15074d4176500b47e4668ca428e7a
Git commit hooks
-rwxr-xr-xgit-hooks/pre-commit61
-rw-r--r--git-hooks/puppethooks/__init__.py1
-rw-r--r--git-hooks/puppethooks/checkers.py71
-rw-r--r--git-hooks/puppethooks/git.py69
-rwxr-xr-xgit-hooks/update61
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\:·: