~edwargix/git.sr.ht

4502aa711cf239b018646aa46ee283619b04ab55 — Ivan Habunek 7 years ago 28434c2
Implement RSS feeds for refs and log

https://todo.sr.ht/~sircmpwn/git.sr.ht/100
4 files changed, 180 insertions(+), 6 deletions(-)

M gitsrht/blueprints/repo.py
A gitsrht/rss.py
M gitsrht/templates/log.html
M gitsrht/templates/refs.html
M gitsrht/blueprints/repo.py => gitsrht/blueprints/repo.py +58 -6
@@ 15,6 15,7 @@ from gitsrht.redis import redis
from gitsrht.git import Repository as GitRepository, commit_time, annotate_tree
from gitsrht.git import diffstat
from gitsrht.types import User, Repository, Redirect
from gitsrht.rss import generate_feed
from io import BytesIO
from pygments import highlight
from pygments.lexers import guess_lexer_for_filename, TextLexer


@@ 278,6 279,15 @@ def collect_refs(git_repo):
        refs[_ref.commit.id.hex].append(_ref)
    return refs

def get_log(git_repo, commit, commits_per_page=20):
    commits = list()
    for commit in git_repo.walk(commit.id, pygit2.GIT_SORT_TIME):
        commits.append(commit)
        if len(commits) >= commits_per_page + 1:
            break

    return commits

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


@@ 291,17 301,31 @@ def log(owner, repo, ref, path):
        if from_id:
            commit = git_repo.get(from_id)

        commits_per_page = 20
        commits = list()
        for commit in git_repo.walk(commit.id, pygit2.GIT_SORT_TIME):
            commits.append(commit)
            if len(commits) >= commits_per_page + 1:
                break
        commits = get_log(git_repo, commit)

        return render_template("log.html", view="log",
                owner=owner, repo=repo, ref=ref, path=path,
                commits=commits, refs=refs)


@repo.route("/<owner>/<repo>/log/rss.xml", defaults={"ref": None})
@repo.route("/<owner>/<repo>/log/<ref>/rss.xml")
def log_rss(owner, repo, ref):
    owner, repo = get_repo_or_redir(owner, repo)
    with GitRepository(repo.path) as git_repo:
        commit, ref = lookup_ref(git_repo, ref)
        commits = get_log(git_repo, commit)

    repo_name = f"{repo.owner.canonical_name}/{repo.name}"
    title = f"{repo_name} log"
    description = f"Git log for {repo_name} {ref}"
    link = cfg("git.sr.ht", "origin") + url_for("repo.log",
        owner=repo.owner.canonical_name,
        repo=repo.name,
        ref=ref if ref != "master" else None).replace("%7E", "~")  # hack

    return generate_feed(repo, commits, title, link, description)

@repo.route("/<owner>/<repo>/commit/<ref>")
def commit(owner, repo, ref):
    owner, repo = get_repo_or_redir(owner, repo)


@@ 383,6 407,34 @@ def refs(owner, repo):
                git_repo=git_repo, isinstance=isinstance, pygit2=pygit2,
                page=page + 1, total_pages=total_pages)


@repo.route("/<owner>/<repo>/refs/rss.xml")
def refs_rss(owner, repo):
    owner, repo = get_repo_or_redir(owner, repo)
    with GitRepository(repo.path) as git_repo:
        references = [
            git_repo.references[name]
            for name in git_repo.references
            if name.startswith("refs/tags/")
        ]

    def _ref_sort_key(ref):
        target = git_repo.get(ref.target)
        author = target.author if hasattr(target, 'author') else target.tagger
        return author.time + author.offset

    references = sorted(references, key=_ref_sort_key, reverse=True)[:20]

    repo_name = f"{repo.owner.canonical_name}/{repo.name}"
    title = f"{repo_name} log"
    description = f"Git refs for {repo_name}"
    link = cfg("git.sr.ht", "origin") + url_for("repo.refs",
        owner=repo.owner.canonical_name,
        repo=repo.name).replace("%7E", "~")  # hack

    return generate_feed(repo, references, title, link, description)


@repo.route("/<owner>/<repo>/refs/<ref>")
def ref(owner, repo, ref):
    owner, repo = get_repo_or_redir(owner, repo)

A gitsrht/rss.py => gitsrht/rss.py +101 -0
@@ 0,0 1,101 @@
import pygit2
import xml.etree.ElementTree as ET
from datetime import datetime, timedelta, timezone
from flask import Response, url_for
from gitsrht.git import Repository as GitRepository
from srht.config import cfg

# Date format used by RSS
RFC_822_FORMAT = "%a, %d %b %Y %H:%M:%S %z"

ORIGIN = cfg("git.sr.ht", "origin")

def aware_time(author):
    tzinfo = timezone(timedelta(minutes=author.offset))
    return datetime.fromtimestamp(author.time, tzinfo)

def ref_name(reference):
    return reference.name.split("/")[-1]

def ref_url(repo, reference):
    return ORIGIN + url_for("repo.ref",
        owner=repo.owner.canonical_name,
        repo=repo.name,
        ref=ref_name(reference)).replace("%7E", "~")  # hack

def commit_url(repo, commit):
    return ORIGIN + url_for("repo.commit",
        owner=repo.owner.canonical_name,
        repo=repo.name,
        ref=commit.id.hex).replace("%7E", "~")  # hack

def commit_title_description(commit):
    """Split the commit message to title (first line) and the description
    (remaining lines)."""
    lines = commit.message.strip().split("\n")
    if lines:
        title = lines[0]
        description = "\n".join(lines[1:]).strip().replace("\n", "<br />")
        return title, description

    # Empty message fallback
    return commit.id.hex, ""

def ref_to_item(repo, reference):
    with GitRepository(repo.path) as git_repo:
        target = git_repo.get(reference.target)

    author = target.author if hasattr(target, 'author') else target.tagger
    time = aware_time(author).strftime(RFC_822_FORMAT)
    url = ref_url(repo, reference)
    description = target.message.strip().replace("\n", "<br />")

    element = ET.Element("item")
    ET.SubElement(element, "title").text = ref_name(reference)
    ET.SubElement(element, "description").text = description
    ET.SubElement(element, "author").text = f"{author.email} ({author.name})"
    ET.SubElement(element, "link").text = url
    ET.SubElement(element, "guid").text = url
    ET.SubElement(element, "pubDate").text = time

    return element

def commit_to_item(repo, commit):
    time = aware_time(commit.author).strftime(RFC_822_FORMAT)
    url = commit_url(repo, commit)
    title, description = commit_title_description(commit)
    author = f"{commit.author.email} ({commit.author.name})"

    element = ET.Element("item")
    ET.SubElement(element, "title").text = title
    ET.SubElement(element, "description").text = description
    ET.SubElement(element, "author").text = author
    ET.SubElement(element, "link").text = url
    ET.SubElement(element, "guid").text = url
    ET.SubElement(element, "pubDate").text = time

    return element

def to_item(repo, item):
    if isinstance(item, pygit2.Reference):
        return ref_to_item(repo, item)

    if isinstance(item, pygit2.Commit):
        return commit_to_item(repo, item)

    raise ValueError(f"Don't know how to convert {type(item)} to an RSS item.")

def generate_feed(repo, items, title, link, description):
    root = ET.Element("rss", version="2.0")
    channel = ET.SubElement(root, "channel")

    ET.SubElement(channel, "title").text = title
    ET.SubElement(channel, "link").text = link
    ET.SubElement(channel, "description").text = description
    ET.SubElement(channel, "language").text = "en"

    for item in items:
        channel.append(to_item(repo, item))

    xml = ET.tostring(root, encoding="UTF-8")
    return Response(xml, mimetype='application/rss+xml')

M gitsrht/templates/log.html => gitsrht/templates/log.html +11 -0
@@ 3,6 3,17 @@
{% block title %}
<title>{{repo.owner.canonical_name}}/{{repo.name}}: {{ref}} - {{cfg("sr.ht", "site-name")}} git</title>
{% endblock %}

{% block head %}
  <link rel="alternate"
    title="{{ repo.owner.canonical_name }}/{{ repo.name }}: {{ ref }} log"
    type="application/rss+xml"
    href="{{ root }}{{ url_for('repo.log_rss',
      owner=repo.owner.canonical_name,
      repo=repo.name,
      ref=ref if ref != "master" else None) }}">
{% endblock %}

{% block content %}
<div class="container">
  <div class="row">

M gitsrht/templates/refs.html => gitsrht/templates/refs.html +10 -0
@@ 3,6 3,16 @@
{% block title %}
<title>{{repo.owner.canonical_name}}/{{repo.name}} refs - {{cfg("sr.ht", "site-name")}} git</title>
{% endblock %}

{% block head %}
  <link rel="alternate"
    title="{{ repo.owner.canonical_name }}/{{ repo.name }} refs"
    type="application/rss+xml"
    href="{{ root }}{{ url_for('repo.refs_rss',
      owner=repo.owner.canonical_name,
      repo=repo.name) }}">
{% endblock %}

{% block content %}
<div class="container">
  <div class="row">