#!/usr/bin/env python """ Suggested installation: $ cp update-hash .git/hooks/ $ $EDITOR .git/hooks/pre-commit #!/bin/bash set -e touch "$(git rev-parse --git-dir)/hooks/.need-post-commit" $ $EDITOR .git/hooks/post-commit #!/bin/bash set -e MARKER="$(git rev-parse --git-dir)/hooks/.need-post-commit" UPDATE_HASH="$(git rev-parse --git-dir)/hooks/update-hash" if [ -e "$MARKER" -a -x "$UPDATE_HASH" ]; then rm "$MARKER" if git diff-index --quiet HEAD -- ; then # nothing true else git stash save --keep-index --quiet "update-hash pre-commit hook" trap "git stash pop" EXIT fi find "$(git rev-parse --show-toplevel)" -name "buildout.hash.cfg" | while IFS= read -r HASHFILE; do "$UPDATE_HASH" "$HASHFILE" git add "$HASHFILE" done git commit --amend --no-verify -C HEAD fi BEWARE: rebasing does not trigger this hook, so you have to commit each change explicitely. Improvements welcome. """ import hashlib import os import shutil import sys import tempfile # Note: this is an intentionally very restrictive and primitive # ConfigParser-ish parser. # buildout.hash.cfg files are ConfigParser-compatible, but they are *not* # ConfigParser syntax in order to be strictly validated, to prevent misuse # and allow easy extension (ex: to other hashes). FILENAME_KEY = 'filename' HASH_MAP = { 'md5sum': hashlib.md5, } def main(): for infile_path in sys.argv[1:] or ['buildout.hash.cfg']: eol = None hash_file_path = None hash_name = None current_section = None infile_dirname = os.path.dirname(infile_path) outfile_path = infile_path + '.tmp' infile = open(infile_path, 'r') outfile_fd = os.open(outfile_path, os.O_EXCL | os.O_CREAT | os.O_WRONLY) try: outfile = os.fdopen(outfile_fd, 'w') write = outfile.write nextLine = iter(infile).next while True: try: line = nextLine() except StopIteration: line = None if line is None or not line.startswith('#'): if line is None or line.startswith('['): if hash_file_path is not None: current_section.insert( len([x for x in current_section if x.strip()]), '%s = %s%s' % ( hash_name, HASH_MAP[hash_name]( open( os.path.join( infile_dirname, hash_file_path, ) ).read() ).hexdigest(), eol, ), ) outfile.writelines(current_section) hash_file_path = hash_name = None if line is None: break current_section = [] elif '=' in line: assert current_section is not None, line name, value = line.split('=', 1) name = name.strip() value = value.strip() if name == FILENAME_KEY: hash_file_path = value current_section.append(line) else: for hash_name in HASH_MAP: if name == hash_name: break else: raise ValueError('Unknown key: %r' % (name, )) # NOT appending this line, it will be re-generated from # scratch eol = ''.join(x for x in line if x in ('\r', '\n')) continue if current_section is None: write(line) else: current_section.append(line) outfile.close() shutil.copymode(infile_path, outfile_path) shutil.move(outfile_path, infile_path) except Exception: os.unlink(outfile_path) raise if __name__ == '__main__': main()