+#!/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()))