--- /dev/null
+#!/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()))
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+
+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])