summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2019-04-22 13:34:45 -0400
committerDrew DeVault <sir@cmpwn.com>2019-04-22 13:34:45 -0400
commitb083bd0030f0f7212227f6f3b7a13cef83f02deb (patch)
treec327cc09e080b48f515fd2fdfca891df804bd56a
parent3ac4646627061166b68bc60fc3a6429fe7061fd5 (diff)
Implement repo data API
-rw-r--r--gitsrht/app.py2
-rw-r--r--gitsrht/blueprints/api.py177
2 files changed, 179 insertions, 0 deletions
diff --git a/gitsrht/app.py b/gitsrht/app.py
index d67e0db..dc0229b 100644
--- a/gitsrht/app.py
+++ b/gitsrht/app.py
@@ -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)
diff --git a/gitsrht/blueprints/api.py b/gitsrht/blueprints/api.py
new file mode 100644
index 0000000..49018b4
--- /dev/null
+++ b/gitsrht/blueprints/api.py
@@ -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)