def git_extract_command(args): import git from otsclient.git import deserialize_ascii_armored_timestamp, extract_sig_from_git_commit from opentimestamps.core.git import GitTreeTimestamper repo = git.Repo(search_parent_directories=True) repo_base_path = repo.working_tree_dir commit = repo.commit(args.commit) serialized_signed_commit = commit.data_stream[3].read() git_commit, gpg_sig = extract_sig_from_git_commit(serialized_signed_commit) if not gpg_sig: logging.error("%s is not signed" % args.commit) sys.exit(1) (major_version, minor_version, commit_stamp) = deserialize_ascii_armored_timestamp(git_commit, gpg_sig) if commit_stamp is None: logging.error("%s is signed, but not timestamped" % args.commit) sys.exit(1) elif minor_version != 1: logging.error("Commit was timestamped, but --rehash-trees was not used; can't extract per-file timestamp.") sys.exit(1) stamper = GitTreeTimestamper(commit.tree) # args.path is relative to the CWD, but for git we need a path relative to # the repo base. # # FIXME: Does this work with bare repos? # FIXME: Does this always work when the user has specified a different # commit than HEAD? git_tree_path = os.path.relpath(args.path, start=repo_base_path) if git_tree_path.startswith('..'): logging.error("%r is outside repository" % args.path) sys.exit(1) try: file_stamp = stamper[git_tree_path] # FIXME: better if these were ots-git-specific exceptions except (FileNotFoundError, ValueError) as exp: logging.error("%s", exp) sys.exit(1) blob = commit.tree[git_tree_path] if args.annex and blob.mode == 0o120000: fd = io.BytesIO() blob.stream_data(fd) link_contents = fd.getvalue() if b'SHA256' in link_contents: hex_digest_start = link_contents.find(b'--') if hex_digest_start < 0: logging.error("%r not a git-annex symlink" % args.path) sys.exit(1) hex_digest_start += 2 hex_digest = link_contents[hex_digest_start:hex_digest_start+32*2] new_file_stamp = DetachedTimestampFile(OpSHA256(), Timestamp(binascii.unhexlify(hex_digest))) new_file_stamp.timestamp.ops.add(OpHexlify()) \ .ops.add(OpPrepend(link_contents[0:hex_digest_start])) \ .ops.add(OpAppend(link_contents[hex_digest_start+32*2:])) \ .ops[OpSHA256()] = file_stamp.timestamp file_stamp = new_file_stamp else: logging.error("%r not a SHA256 git-annex symlink" % args.path) sys.exit(1) elif blob.mode == 0o120000: logging.error("%r is a symlink; see --annex" % args.path) sys.exit(1) # Merge the two timestamps # First, we need to find the tip of the file timestamp tip = file_stamp.timestamp while tip.ops: assert len(tip.ops) == 1 # FIXME: should handle divergence tip = tuple(tip.ops.values())[0] # Second, splice it to the commit timestamp. # # Remember that the commit timestamp was on SHA256(SHA256(git_commit) + # SHA256(gpg_sig)), and the commitment to the tree is in the first op - an # OpAppend - so we have to create an OpPrepend: append_commit_stamp = tip.ops.add(OpPrepend(commit_stamp.msg)) append_commit_stamp.merge(tuple(commit_stamp.ops.values())[0]) timestamp_file_path = None try: if args.timestamp_file is None: timestamp_file_path = args.path + '.ots' args.timestamp_file = open(timestamp_file_path, 'xb') else: timestamp_file_path = args.timestamp_file.name with args.timestamp_file as fd: ctx = StreamSerializationContext(fd) file_stamp.serialize(ctx) except IOError as exp: logging.error("Failed to create timestamp %r: %s" % (timestamp_file_path, exp)) sys.exit(1)
def git_extract_command(args): import git from otsclient.git import deserialize_ascii_armored_timestamp, extract_sig_from_git_commit from opentimestamps.core.git import GitTreeTimestamper repo = git.Repo(search_parent_directories=True) repo_base_path = repo.working_tree_dir commit = repo.commit(args.commit) serialized_signed_commit = commit.data_stream[3].read() git_commit, gpg_sig = extract_sig_from_git_commit(serialized_signed_commit) if not gpg_sig: logging.error("%s is not signed" % args.commit) sys.exit(1) (major_version, minor_version, commit_stamp) = deserialize_ascii_armored_timestamp(git_commit, gpg_sig) if commit_stamp is None: logging.error("%s is signed, but not timestamped" % args.commit) sys.exit(1) elif minor_version != 1: logging.error( "Commit was timestamped, but --rehash-trees was not used; can't extract per-file timestamp." ) sys.exit(1) stamper = GitTreeTimestamper(commit.tree) # args.path is relative to the CWD, but for git we need a path relative to # the repo base. # # FIXME: Does this work with bare repos? # FIXME: Does this always work when the user has specified a different # commit than HEAD? git_tree_path = os.path.relpath(args.path, start=repo_base_path) if git_tree_path.startswith('..'): logging.error("%r is outside repository" % args.path) sys.exit(1) try: file_stamp = stamper[git_tree_path] # FIXME: better if these were ots-git-specific exceptions except (FileNotFoundError, ValueError) as exp: logging.error("%s", exp) sys.exit(1) blob = commit.tree[git_tree_path] if args.annex and blob.mode == 0o120000: fd = io.BytesIO() blob.stream_data(fd) link_contents = fd.getvalue() if b'SHA256' in link_contents: hex_digest_start = link_contents.find(b'--') if hex_digest_start < 0: logging.error("%r not a git-annex symlink" % args.path) sys.exit(1) hex_digest_start += 2 hex_digest = link_contents[hex_digest_start:hex_digest_start + 32 * 2] new_file_stamp = DetachedTimestampFile( OpSHA256(), Timestamp(binascii.unhexlify(hex_digest))) new_file_stamp.timestamp.ops.add(OpHexlify()) \ .ops.add(OpPrepend(link_contents[0:hex_digest_start])) \ .ops.add(OpAppend(link_contents[hex_digest_start+32*2:])) \ .ops[OpSHA256()] = file_stamp.timestamp file_stamp = new_file_stamp else: logging.error("%r not a SHA256 git-annex symlink" % args.path) sys.exit(1) elif blob.mode == 0o120000: logging.error("%r is a symlink; see --annex" % args.path) sys.exit(1) # Merge the two timestamps # First, we need to find the tip of the file timestamp tip = file_stamp.timestamp while tip.ops: assert len(tip.ops) == 1 # FIXME: should handle divergence tip = tuple(tip.ops.values())[0] # Second, splice it to the commit timestamp. # # Remember that the commit timestamp was on SHA256(SHA256(git_commit) + # SHA256(gpg_sig)), and the commitment to the tree is in the first op - an # OpAppend - so we have to create an OpPrepend: append_commit_stamp = tip.ops.add(OpPrepend(commit_stamp.msg)) append_commit_stamp.merge(tuple(commit_stamp.ops.values())[0]) timestamp_file_path = None try: if args.timestamp_file is None: timestamp_file_path = args.path + '.ots' args.timestamp_file = open(timestamp_file_path, 'xb') else: timestamp_file_path = args.timestamp_file.name with args.timestamp_file as fd: ctx = StreamSerializationContext(fd) file_stamp.serialize(ctx) except IOError as exp: logging.error("Failed to create timestamp %r: %s" % (timestamp_file_path, exp)) sys.exit(1)
def git_extract_command(args): import git from otsclient.git import deserialize_ascii_armored_timestamp, extract_sig_from_git_commit from opentimestamps.core.git import GitTreeTimestamper repo = git.Repo() commit = repo.commit(args.commit) serialized_signed_commit = commit.data_stream[3].read() git_commit, gpg_sig = extract_sig_from_git_commit(serialized_signed_commit) if not gpg_sig: logging.error("%s is not signed" % args.commit) sys.exit(1) commit_stamp = deserialize_ascii_armored_timestamp(git_commit, gpg_sig) if commit_stamp is None: logging.error("%s is signed, but not timestamped" % args.commit) sys.exit(1) stamper = GitTreeTimestamper(commit.tree) try: file_stamp = stamper[args.path] except Exception as exp: # FIXME logging.error("%r", exp) sys.exit(1) blob = commit.tree[args.path] if args.annex and blob.mode == 0o120000: fd = io.BytesIO() blob.stream_data(fd) link_contents = fd.getvalue() if b'SHA256' in link_contents: hex_digest_start = link_contents.find(b'--') if hex_digest_start < 0: logging.error("%r not a git-annex symlink" % args.path) sys.exit(1) hex_digest_start += 2 hex_digest = link_contents[hex_digest_start:hex_digest_start+32*2] new_file_stamp = DetachedTimestampFile(OpSHA256(), Timestamp(binascii.unhexlify(hex_digest))) new_file_stamp.timestamp.ops.add(OpHexlify()) \ .ops.add(OpPrepend(link_contents[0:hex_digest_start])) \ .ops.add(OpAppend(link_contents[hex_digest_start+32*2:])) \ .ops[OpSHA256()] = file_stamp.timestamp file_stamp = new_file_stamp else: logging.error("%r not a SHA256 git-annex symlink" % args.path) sys.exit(1) elif blob.mode == 0o120000: logging.error("%r is a symlink; see --annex" % args.path) sys.exit(1) # Merge the two timestamps # First, we need to find the tip of the file timestamp tip = file_stamp.timestamp while tip.ops: assert len(tip.ops) == 1 # FIXME: should handle divergence tip = tuple(tip.ops.values())[0] # Second, splice it to the commit timestamp. # # Remember that the commit timestamp was on SHA256(SHA256(git_commit) + # SHA256(gpg_sig)), and the commitment to the tree is in the first op - an # OpAppend - so we have to create an OpPrepend: append_commit_stamp = tip.ops.add(OpPrepend(commit_stamp.msg)) append_commit_stamp.merge(tuple(commit_stamp.ops.values())[0]) if args.timestamp_file is None: args.timestamp_file = open(args.path + '.ots', 'xb') with args.timestamp_file as fd: ctx = StreamSerializationContext(fd) file_stamp.serialize(ctx)