From 35854188daf6f2ec4ff77ff63e795b0c7d89da9b Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 21 Sep 2018 18:21:32 -0400 Subject: [PATCH] Implement tree annotation --- gitsrht/app.py | 2 + gitsrht/blueprints/repo.py | 6 +-- gitsrht/git.py | 91 ++++++++++++++++++++++++++++++++++++- gitsrht/templates/tree.html | 11 +++-- 4 files changed, 103 insertions(+), 7 deletions(-) diff --git a/gitsrht/app.py b/gitsrht/app.py index 34c5207..2376a51 100644 --- a/gitsrht/app.py +++ b/gitsrht/app.py @@ -1,3 +1,4 @@ +import humanize import stat from flask import session from srht.flask import SrhtFlask @@ -43,6 +44,7 @@ class GitApp(SrhtFlask): return { "commit_time": commit_time, "trim_commit": trim_commit, + "humanize": humanize, "stat": stat, "notice": notice } diff --git a/gitsrht/blueprints/repo.py b/gitsrht/blueprints/repo.py index 054afc6..e9e5651 100644 --- a/gitsrht/blueprints/repo.py +++ b/gitsrht/blueprints/repo.py @@ -4,7 +4,7 @@ from flask import Blueprint, render_template, abort from flask_login import current_user from gitsrht.access import get_repo, has_access, UserAccess from gitsrht.redis import redis -from gitsrht.git import CachedRepository, commit_time +from gitsrht.git import CachedRepository, commit_time, annotate_tree from gitsrht.types import User, Repository from srht.config import cfg from srht.markdown import markdown @@ -83,8 +83,8 @@ def tree(owner, repo, branch, path): if not branch: abort(404) commit = git_repo.get(branch.target) - # TODO: annotate tree with latest commit for each file (and cache it) - tree = [entry for entry in commit.tree] + + tree = annotate_tree(git_repo, commit) tree = sorted(tree, key=lambda e: e.name) # TODO: follow path return render_template("tree.html", view="tree", diff --git a/gitsrht/git.py b/gitsrht/git.py index 8c0a5a4..acfb6ab 100644 --- a/gitsrht/git.py +++ b/gitsrht/git.py @@ -1,6 +1,9 @@ +from collections import deque from datetime import datetime, timedelta, timezone -from pygit2 import Repository, Tag from functools import lru_cache +from gitsrht.redis import redis +from pygit2 import Repository, Tag +import json def trim_commit(msg): if "\n" not in msg: @@ -39,3 +42,89 @@ class _CachedRepository(Repository): branch = list(self.branches.local)[0] branch = self.branches.get(branch) return branch + +class AnnotatedTreeEntry: + def __init__(self, repo, entry): + self._entry = entry + self._repo = repo + if entry: + self.id = entry.id.hex + self.name = entry.name + self.type = entry.type + self.filemode = entry.filemode + + def fetch_blob(self): + if self.type == "tree": + self.tree = self._repo.get(self.id) + else: + self.blob = self._repo.get(self.id) + return self + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "type": self.type, + "filemode": self.filemode, + "commit": (self.commit.id.hex + if hasattr(self, "commit") and self.commit else None), + } + + @staticmethod + def deserialize(res, repo): + _id = res["id"] + self = AnnotatedTreeEntry(repo, None) + self.id = res["id"] + self.name = res["name"] + self.type = res["type"] + self.filemode = res["filemode"] + self.commit = repo.get(res["commit"]) if "commit" in res else None + return self + + def __hash__(self): + return hash(f"{self.id}:{self.name}") + + def __eq__(self, other): + return self.id == other.id and self.name == other.name + + def __repr__(self): + return f"" + +def annotate_tree(repo, commit): + key = f"git.sr.ht:git:tree:{commit.tree.id.hex}" + cache = redis.get(key) + if cache: + cache = json.loads(cache.decode()) + return [AnnotatedTreeEntry.deserialize( + e, repo).fetch_blob() for e in cache.values()] + + tree = { entry.id.hex: AnnotatedTreeEntry( + repo, entry) for entry in commit.tree } + + parents = deque(commit.parents) + left_tree = set(v for v in tree.values()) + unfinished = set(left_tree) + if not any(commit.parents): + return [entry.fetch_blob() for entry in tree.values()] + parent = commit.parents[0] + + while any(unfinished): + right_tree = { entry.id.hex: AnnotatedTreeEntry(repo, entry) + for entry in parent.tree } + right_tree = set(v for v in right_tree.values()) + diff = left_tree - right_tree + for entry in diff: + if entry.id in tree: + tree[entry.id].commit = commit + unfinished = unfinished - diff + left_tree = right_tree + commit = parent + if not any(commit.parents): + break + parent = commit.parents[0] + + cache = {entry.name: entry.serialize() for entry in tree.values()} + cache = json.dumps(cache) + redis.setex(key, cache, timedelta(days=30)) + + return [entry.fetch_blob() for entry in tree.values()] diff --git a/gitsrht/templates/tree.html b/gitsrht/templates/tree.html index 3e79a26..d2d4df0 100644 --- a/gitsrht/templates/tree.html +++ b/gitsrht/templates/tree.html @@ -27,11 +27,16 @@ {{entry.name}}{% if entry.type == "tree" %}/{% endif %}
- {{trim_commit(commit.message)}} — - {{ commit_time(commit) | date }} + {% if entry.commit %} + {{trim_commit(entry.commit.message)}} — + {{ commit_time(entry.commit) | date }} + {% endif %}
- 1234 bytes + {% if entry.type == "blob" %} + {{humanize.naturalsize(entry.blob.size, + binary=True).replace("Byte", "byte")}} + {% endif %}
{% endfor %} -- 2.38.4