~edwargix/git.sr.ht

35854188daf6f2ec4ff77ff63e795b0c7d89da9b — Drew DeVault 6 years ago 662257f
Implement tree annotation
4 files changed, 103 insertions(+), 7 deletions(-)

M gitsrht/app.py
M gitsrht/blueprints/repo.py
M gitsrht/git.py
M gitsrht/templates/tree.html
M gitsrht/app.py => gitsrht/app.py +2 -0
@@ 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
            }

M gitsrht/blueprints/repo.py => gitsrht/blueprints/repo.py +3 -3
@@ 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",

M gitsrht/git.py => gitsrht/git.py +90 -1
@@ 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"<AnnotatedTreeEntry {self.name} {self.id}>"

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()]

M gitsrht/templates/tree.html => gitsrht/templates/tree.html +8 -3
@@ 27,11 27,16 @@
        <a href="#">{{entry.name}}{% if entry.type == "tree" %}/{% endif %}</a>
      </div>
      <div class="commit">
        <a href="#">{{trim_commit(commit.message)}}</a> &mdash;
        {{ commit_time(commit) | date }}
        {% if entry.commit %}
        <a href="#">{{trim_commit(entry.commit.message)}}</a> &mdash;
        {{ commit_time(entry.commit) | date }}
        {% endif %}
      </div>
      <div class="size">
        1234 bytes
        {% if entry.type == "blob" %}
        {{humanize.naturalsize(entry.blob.size,
          binary=True).replace("Byte", "byte")}}
        {% endif %}
      </div>
      {% endfor %}
      </div>