Blame view

update-hash 4.54 KB
Vincent Pelletier committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
#!/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(
Vincent Pelletier committed
75
                                len([x for x in current_section if x.strip()]),
Vincent Pelletier committed
76 77 78 79 80 81
                                '%s = %s%s' % (
                                    hash_name,
                                    HASH_MAP[hash_name](
                                        open(
                                            os.path.join(
                                                infile_dirname,
Vincent Pelletier committed
82
                                                *hash_file_path.split('/')
Vincent Pelletier committed
83 84 85 86 87 88 89 90 91 92
                                            )
                                        ).read()
                                    ).hexdigest(),
                                    eol,
                                ),
                            )
                            outfile.writelines(current_section)
                            hash_file_path = hash_name = None
                        if line is None:
                            break
Vincent Pelletier committed
93
                        hash_file_path, _ = line[1:].split(']', 1)
Vincent Pelletier committed
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
                        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()