~edwargix/git.sr.ht

fa4ef72f8b95aba00b106a82598608debb8fa449 — Eli Schwartz 3 years ago ab1183a
add backend route to retrieve cgit-style git notes as PGP sig

For any available tarball {gitref}.tar.gz, attempt to see if there is a
`git notes` attachment in:

refs/signatures/tar    -> {gitref}.tar.asc
refs/signatures/tar.gz -> {gitref}.tar.gz.asc

cgit supports this for any of its configurable archive compression
formats, and additionally always supports delivering the signature for an
uncompressed tarball.

This is useful in at least two cases:

- Some people may mirror to, or migrate between, multiple forges,
  including cgit with lots of compression formats enabled. One notes
  attachment then works for all formats.

- git-archive guarantees to be a fully stable tarball output format, but
  the compressor program it pipes to may not be, depending on
  implementation (GNU gzip is, busybox gzip used to not be but now is,
  zstd dedicatedly breaks output reproducibility on every release).
  Some people rely on this, some people don't.

Since sourcehut only supports tar.gz, only tar.gz-compatible signatures
are implemented here.

Implements https://todo.sr.ht/~sircmpwn/git.sr.ht/231
1 files changed, 32 insertions(+), 0 deletions(-)

M gitsrht/blueprints/repo.py
M gitsrht/blueprints/repo.py => gitsrht/blueprints/repo.py +32 -0
@@ 187,6 187,27 @@ def lookup_ref(git_repo, ref, path):
        abort(404)
    return commit, ref, "/".join(path)

def lookup_signature(git_repo, ref, fmt=None):
    commit_or_tag = git_repo.revparse_single(ref)
    if not isinstance(commit_or_tag, (pygit2.Commit, pygit2.Tag)):
        return None, None

    fmts = ['tar.gz', 'tar']

    if fmt is not None:
        if fmt not in fmts:
            return None, None
        fmts = [fmt]

    for trial in fmts:
        try:
            note = git_repo.lookup_note(commit_or_tag.hex, f'refs/notes/signatures/{trial}')
        except KeyError:
            continue

        return note.message, trial
    return None, None

@repo.route("/<owner>/<repo>/tree", defaults={"ref": None, "path": ""})
@repo.route("/<owner>/<repo>/tree/<path:ref>", defaults={"path": ""})
@repo.route("/<owner>/<repo>/tree/<path:ref>/item/<path:path>")


@@ 365,6 386,17 @@ def archive(owner, repo, ref):
        return send_file(subp.stdout, mimetype="application/tar+gzip",
                as_attachment=True, attachment_filename=f"{repo.name}-{refname}.tar.gz")

@repo.route("/<owner>/<repo>/archive/<path:ref>.<fmt>.asc")
def archivesig(owner, repo, ref, fmt):
    owner, repo = get_repo_or_redir(owner, repo)
    with GitRepository(repo.path) as git_repo:
        sigdata, _ = lookup_signature(git_repo, ref, fmt)
        if sigdata is None:
            abort(404)

        return send_file(BytesIO(sigdata.encode('utf-8')), mimetype="application/pgp-signature",
                as_attachment=True, attachment_filename=f"{repo.name}-{ref}.{fmt}.asc")

class _AnnotatedRef:
    def __init__(self, repo, ref):
        self.ref = ref