~edwargix/git.sr.ht

b083bd0030f0f7212227f6f3b7a13cef83f02deb — Drew DeVault 6 years ago 3ac4646
Implement repo data API
2 files changed, 179 insertions(+), 0 deletions(-)

M gitsrht/app.py
A gitsrht/blueprints/api.py
M gitsrht/app.py => gitsrht/app.py +2 -0
@@ 24,9 24,11 @@ class GitApp(ScmSrhtFlask):
                repo_api=GitRepoApi(),
                oauth_service=oauth_service)

        from gitsrht.blueprints.api import data
        from gitsrht.blueprints.repo import repo
        from gitsrht.blueprints.stats import stats

        self.register_blueprint(data)
        self.register_blueprint(repo)
        self.register_blueprint(stats)
        self.register_blueprint(webhooks_notify)

A gitsrht/blueprints/api.py => gitsrht/blueprints/api.py +177 -0
@@ 0,0 1,177 @@
import base64
import pygit2
from flask import Blueprint, current_app, request, send_file, abort
from gitsrht.blueprints.repo import lookup_ref, get_log, collect_refs
from gitsrht.git import Repository as GitRepository, commit_time, annotate_tree
from io import BytesIO
from scmsrht.blueprints.api import get_user, get_repo
from srht.api import paginated_response
from srht.oauth import current_token, oauth

data = Blueprint("api.data", __name__)

def _commit_to_dict(c):
    return {
        "id": str(c.id),
        "short_id": c.short_id,
        "author": {
            "email": c.author.email,
            "name": c.author.name,
        },
        "committer": {
            "email": c.committer.email,
            "name": c.committer.name,
        },
        "timestamp": commit_time(c),
        "message": c.message,
        "tree": str(c.tree_id),
        "parents": [str(p.id) for p in c.parents],
        "signature": {
            "signature": base64.b64encode(c.gpg_signature[0]).decode(),
            "data": base64.b64encode(c.gpg_signature[1]).decode(),
        } if c.gpg_signature[0] else None
    }

def _tree_to_dict(t):
    return {
        "id": str(t.id),
        "short_id": t.short_id,
        "entries": [
            {
                "name": e.name,
                "id": str(e.id),
                "type": e.type,
                "mode": e.filemode,
            } for e in t
        ]
    }

@data.route("/api/repos/<reponame>/refs", defaults={"username": None})
@data.route("/api/<username>/repos/<reponame>/refs")
@oauth("data:read")
def repo_refs_GET(username, reponame):
    user = get_user(username)
    repo = get_repo(user, reponame)

    with GitRepository(repo.path) as git_repo:
        refs = list(git_repo.references)
        # TODO: pagination
        return {
            "next": None,
            "results": [
                {
                    "target": str(git_repo.references[ref].target),
                    "name": ref,
                } for ref in refs
            ],
            "total": len(refs),
            "results_per_page": len(refs),
        }

# dear god, this routing
@data.route("/api/repos/<reponame>/log",
        defaults={"username": None, "ref": None, "path": ""})
@data.route("/api/repos/<reponame>/log/<path:ref>",
        defaults={"username": None, "path": ""})
@data.route("/api/repos/<reponame>/log/<ref>/<path:path>",
        defaults={"username": None})
@data.route("/api/<username>/repos/<reponame>/log",
        defaults={"ref": None, "path": ""})
@data.route("/api/<username>/repos/<reponame>/log/<path:ref>",
        defaults={"path": ""})
@data.route("/api/repos/<username>/<reponame>/log/<ref>/<path:path>")
@oauth("data:read")
def repo_commits_GET(username, reponame, ref, path):
    user = get_user(username)
    repo = get_repo(user, reponame)

    commits_per_page=50
    with GitRepository(repo.path) as git_repo:
        if git_repo.is_empty:
            return { "next": next_id, "results": [],
                    "total": 0, "results_per_page": commits_per_page }
        commit, ref, path = lookup_ref(git_repo, ref, path)
        start = request.args.get("start")
        if start:
            commit = git_repo.get(start)
        commits = get_log(git_repo, commit, commits_per_page)
        next_id = None
        if len(commits) > commits_per_page:
            next_id = str(commits[-1].id)
        return {
            "next": next_id,
            "results": [_commit_to_dict(c) for c in commits],
            # TODO: Track total commits per repo per branch
            "total": -1,
            "results_per_page": commits_per_page
        }

@data.route("/api/repos/<reponame>/tree",
        defaults={"username": None, "ref": None, "path": ""})
@data.route("/api/repos/<reponame>/tree/<path:ref>",
        defaults={"username": None, "path": ""})
@data.route("/api/repos/<reponame>/tree/<ref>/<path:path>",
        defaults={"username": None})
@data.route("/api/<username>/repos/<reponame>/tree",
        defaults={"ref": None, "path": ""})
@data.route("/api/<username>/repos/<reponame>/tree/<path:ref>",
        defaults={"path": ""})
@data.route("/api/repos/<username>/<reponame>/tree/<ref>/<path:path>")
@oauth("data:read")
def repo_tree_GET(username, reponame, ref, path):
    user = get_user(username)
    repo = get_repo(user, reponame)

    with GitRepository(repo.path) as git_repo:
        commit, ref, path = lookup_ref(git_repo, ref, path)
        if isinstance(commit, pygit2.Commit):
            tree = commit.tree
        elif isinstance(commit, pygit2.Tree):
            tree = commit
        else:
            abort(404)
        return _tree_to_dict(tree)

@data.route("/api/repos/<reponame>/blob/<path:ref>",
        defaults={"username": None, "path": ""})
@data.route("/api/repos/<reponame>/blob/<ref>/<path:path>",
        defaults={"username": None})
@data.route("/api/<username>/blob/<reponame>/blob/<path:ref>",
        defaults={"path": ""})
@data.route("/api/repos/<username>/<reponame>/blob/<ref>/<path:path>")
@oauth("data:read")
def repo_blob_GET(username, reponame, ref, path):
    user = get_user(username)
    repo = get_repo(user, reponame)

    with GitRepository(repo.path) as git_repo:
        commit, ref, path = lookup_ref(git_repo, ref, path)
        if not commit:
            abort(404)

        entry = None
        if isinstance(commit, pygit2.Blob):
            blob = commit
        else:
            blob = None
            tree = commit.tree
            path = path.split("/")
            for part in path:
                if part == "":
                    continue
                if part not in tree:
                    abort(404)
                entry = tree[part]
                if entry.type == "blob":
                    tree = annotate_tree(git_repo, tree, commit)
                    commit = next(e.commit for e in tree if e.name == entry.name)
                    blob = git_repo.get(entry.id)
                    break
                tree = git_repo.get(entry.id)
        if not blob:
            abort(404)

        return send_file(BytesIO(blob.data),
                as_attachment=blob.is_binary,
                attachment_filename=entry.name if entry else None,
                mimetype="text/plain" if not blob.is_binary else None)