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)
Exemple #3
0
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)