From 4af4ee14bf8b6d8938ba6615c1af4389453a213a Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Tue, 10 Jul 2018 19:03:05 +0200 Subject: [PATCH] Check evtags for submodules --- setup.sh | 8 ++ shell/bin/git-evtag-compute-py | 108 ++++++++++++++++++++++ shell/bin/git-update-and-verify-submodule | 51 ++++++++++ 3 files changed, 167 insertions(+) create mode 100755 shell/bin/git-evtag-compute-py create mode 100755 shell/bin/git-update-and-verify-submodule diff --git a/setup.sh b/setup.sh index d2a3815..56a9092 100755 --- a/setup.sh +++ b/setup.sh @@ -21,6 +21,14 @@ set -eu + +# Enforce evtag checks for submodules. +git submodule init > /dev/null +for x in `git config --local --get-regexp 'submodule.*\.url' \ + | awk '{print $1}'`; do + git config "${x%.url}.update" '!git-update-and-verify-submodule' +done + for path in */setup.sh; do # Skip non executable setup.sh files as an easy way to deactivate one. test ! -x "$path" && continue diff --git a/shell/bin/git-evtag-compute-py b/shell/bin/git-evtag-compute-py new file mode 100755 index 0000000..0babc5c --- /dev/null +++ b/shell/bin/git-evtag-compute-py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# +# An implementation of Git-EVTag in a mixture of Python and +# git-as-subprocess. Slower than C, but easier to understand +# and test. +# +# For correct submodule handling, requires the working directory have +# submodule checkouts. + +from __future__ import print_function + +import os +import sys +import argparse +import subprocess +import hashlib + +try: + from subprocess import DEVNULL # pylint: disable=no-name-in-module +except ImportError: + import os + DEVNULL = open(os.devnull, 'wb') + +parser = argparse.ArgumentParser(description="Compute Git-EVTag checksum") +parser.add_argument('rev', help='Revision to checksum') +opts = parser.parse_args() + +csum = hashlib.sha512() + +stats = {'commit': 0, + 'blob': 0, + 'tree': 0, + 'commitbytes': 0, + 'blobbytes': 0, + 'treebytes': 0} + +def checksum_bytes(otype, buf): + blen = len(buf) + csum.update(buf) + stats[otype + 'bytes'] += blen + return blen + +def checksum_object(repo, objid): + p = subprocess.Popen(['git', 'cat-file', '--batch'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + close_fds=True, + cwd=repo) + p.stdin.write(objid + '\n') + p.stdin.close() + (objid,objtype,lenstr) = p.stdout.readline().split(None, 2) + olen = int(lenstr) + lenstr = lenstr.strip() + buf = "{0} {1}\000".format(objtype, lenstr) + checksum_bytes(objtype, buf) + + stats[objtype] += 1 + + if objtype == 'commit': + buf = p.stdout.readline() + olen -= checksum_bytes(objtype, buf) + (treestr, treeobjid) = buf.split(None, 1) + treeobjid = treeobjid.strip() + assert treestr == 'tree' + else: + treeobjid = None + + while olen > 0: + b = p.stdout.read(min(8192, olen)) + bytes_read = checksum_bytes(objtype, b) + olen -= bytes_read + if olen > 0: + raise ValueError("Failed to read {0} bytes from object".format(olen)) + p.wait() + if p.returncode != 0: + raise subprocess.CalledProcessError(p.returncode, 'git cat-file') + return treeobjid + +def checksum_tree(repo, objid): + checksum_object(repo, objid) + p = subprocess.Popen(['git', 'ls-tree', objid], + stdin=DEVNULL, + stdout=subprocess.PIPE, + close_fds=True, + cwd=repo) + for line in p.stdout: + (mode, otype, subid, fname) = line.split(None, 3) + fname = fname.strip() + if otype == 'blob': + checksum_object(repo, subid) + elif otype == 'tree': + checksum_tree(repo, subid) + elif otype == 'commit': + checksum_repo(os.path.join(repo, fname), subid) + else: + assert False + p.wait() + if p.returncode != 0: + raise subprocess.CalledProcessError(p.returncode, 'git ls-tree') + +def checksum_repo(repo, objid): + treeid = checksum_object(repo, objid) + checksum_tree(repo, treeid) + +checksum_repo('.', opts.rev) + +print("# git-evtag comment: submodules={0} commits={1} ({2}) trees={3} ({4}) blobs={5} ({6})".format(stats['commit']-1, stats['commit'], stats['commitbytes'], stats['tree'], stats['treebytes'], stats['blob'], stats['blobbytes'])) +print("Git-EVTag-v0-SHA512: {0}".format(csum.hexdigest())) diff --git a/shell/bin/git-update-and-verify-submodule b/shell/bin/git-update-and-verify-submodule new file mode 100755 index 0000000..c4ba1f6 --- /dev/null +++ b/shell/bin/git-update-and-verify-submodule @@ -0,0 +1,51 @@ +#!/usr/bin/python + +# Copyright (C) 2018 Simon Ruderich +# +# 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 3 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, see . + + +import os +import subprocess +import sys + + +sha1 = sys.argv[1] + +repo_path = os.environ['OLDPWD'] +submodule_path = os.getcwd() +submodule = os.path.relpath(submodule_path, repo_path) + +evtag = subprocess.check_output(['git-evtag-compute-py', sha1]).split('\n') +assert evtag[0].startswith('# git-evtag comment: ') +assert evtag[1].startswith('Git-EVTag-v0-SHA512: ') + +os.chdir(repo_path) + +log = subprocess.check_output(['git', 'log', '-1', '--', submodule]) + +found_comment = False +found_evtag = False +for line in log.split('\n'): + if evtag[0] in line: + found_comment = True + elif evtag[1] in line: + found_evtag = True + +if not found_comment or not found_evtag: + print('missing or invalid evtag, aborting') + sys.exit(1) + +subprocess.check_call(['git', 'submodule', 'update', '--checkout', '--', + submodule]) -- 2.43.2