~edwargix/git.sr.ht

31c9eff0540cbd7e0ee727bfcb4118e9ffff0afa — Drew DeVault 7 years ago e95fbad
Tweaks to contributors page
M gitsrht/app.py => gitsrht/app.py +2 -0
@@ 29,11 29,13 @@ class GitApp(SrhtFlask):
        from gitsrht.blueprints.api import api
        from gitsrht.blueprints.public import public
        from gitsrht.blueprints.repo import repo
        from gitsrht.blueprints.stats import stats
        from gitsrht.blueprints.manage import manage

        self.register_blueprint(api)
        self.register_blueprint(public)
        self.register_blueprint(repo)
        self.register_blueprint(stats)
        self.register_blueprint(manage)

        meta_client_id = cfg("git.sr.ht", "oauth-client-id")

M gitsrht/blueprints/repo.py => gitsrht/blueprints/repo.py +2 -88
@@ 16,8 16,9 @@ from gitsrht.editorconfig import EditorConfig
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.repos import get_repo_or_redir
from gitsrht.rss import generate_feed
from gitsrht.types import User, Repository, Redirect
from io import BytesIO
from pygments import highlight
from pygments.lexers import guess_lexer, guess_lexer_for_filename, TextLexer


@@ 94,23 95,6 @@ def _highlight_file(name, data, blob_id):
    redis.setex(key, html, timedelta(days=7))
    return Markup(html)

def get_repo_or_redir(owner, repo):
    owner, repo = get_repo(owner, repo)
    if not repo:
        abort(404)
    if not has_access(repo, UserAccess.read):
        abort(401)
    if isinstance(repo, Redirect):
        view_args = request.view_args
        if not "repo" in view_args or not "owner" in view_args:
            return redirect(url_for(".summary",
                owner=repo.new_repo.owner.canonical_name,
                repo=repo.new_repo.name))
        view_args["owner"] = repo.new_repo.owner.canonical_name
        view_args["repo"] = repo.new_repo.name
        abort(redirect(url_for(request.endpoint, **view_args)))
    return owner, repo

def get_last_3_commits(commit):
    commits = [commit]
    for parent in commit.parents:


@@ 471,73 455,3 @@ def ref(owner, repo, ref):
            abort(404)
        return render_template("ref.html", view="refs",
                owner=owner, repo=repo, git_repo=git_repo, tag=tag)

def _week(time):
    """Used to group contributions by week"""
    return time.strftime('%Y/%W')

@lru_cache(maxsize=256)
def _user(email):
    """Used to grouped contributions by either username or email."""
    email = email.lower()
    user = User.query.filter_by(email=email).one_or_none()
    return (None, user.username) if user else (email, None)

def get_contributions(git_repo, tip, since):
    contributions = defaultdict(lambda: {
        "commits": 0,
        "weekly": defaultdict(lambda: 0)
    })

    since_ts = since.timestamp()
    for commit in git_repo.walk(tip.id, pygit2.GIT_SORT_TIME):
        timestamp = commit.commit_time + commit.commit_time_offset
        if timestamp < since_ts:
            break

        week = _week(datetime.fromtimestamp(timestamp))
        user = _user(commit.author.email)
        contributions[user]['commits'] += 1
        contributions[user]['weekly'][week] += 1

    return contributions

def get_contrib_chart_data(contributions):
    # Max number of commits by a contributor in a single week
    max_commits = max(
        max(commits for _, commits in data['weekly'].items())
        for _, data in contributions.items())

    all_weeks = [_week(date.today() - timedelta(weeks=51 - n))
        for n in range(52)]

    def bars(contributions):
        bars = list()
        for ordinal, week in enumerate(all_weeks):
            if week in contributions:
                week_commits = contributions[week]
                bars.append({
                    "ordinal": ordinal,
                    "week": week,
                    "commits": week_commits,
                    "height": 100 * week_commits // max_commits
                })
        return bars

    chart_data = [(email, username, data['commits'], bars(data['weekly']))
        for (email, username), data in contributions.items()]
    return sorted(chart_data, key=lambda x: x[2], reverse=True)

@repo.route("/<owner>/<repo>/contributors")
def contributors(owner, repo):
    owner, repo = get_repo_or_redir(owner, repo)
    since = datetime.now() - timedelta(weeks=52)

    with GitRepository(repo.path) as git_repo:
        default_branch = git_repo.default_branch()
        tip = git_repo.get(default_branch.target)
        contributions = get_contributions(git_repo, tip, since)
        chart_data = get_contrib_chart_data(contributions)

    return render_template("contributors.html", view="contributors",
        owner=owner, repo=repo, chart_data=chart_data)

A gitsrht/blueprints/stats.py => gitsrht/blueprints/stats.py +81 -0
@@ 0,0 1,81 @@
import pygit2
from collections import defaultdict
from datetime import date, datetime, timedelta
from flask import Blueprint, render_template
from functools import lru_cache
from gitsrht.git import Repository as GitRepository
from gitsrht.repos import get_repo_or_redir
from gitsrht.types import User

stats = Blueprint('stats', __name__)

def _week(time):
    """Used to group contributions by week"""
    return time.strftime('%Y/%W')

@lru_cache(maxsize=256)
def _user(email, name):
    """Used to grouped contributions by either username or email."""
    email = email.lower()
    user = User.query.filter_by(email=email).one_or_none()
    return (None, name, user.username) if user else (email, name, None)

def get_contributions(git_repo, tip, since):
    contributions = defaultdict(lambda: {
        "commits": 0,
        "weekly": defaultdict(lambda: 0)
    })

    since_ts = since.timestamp()
    for commit in git_repo.walk(tip.id, pygit2.GIT_SORT_TIME):
        timestamp = commit.commit_time + commit.commit_time_offset
        if timestamp < since_ts:
            break

        week = _week(datetime.fromtimestamp(timestamp))
        user = _user(commit.author.email, commit.author.name)
        contributions[user]['commits'] += 1
        contributions[user]['weekly'][week] += 1

    return contributions

def get_contrib_chart_data(contributions):
    # Max number of commits by a contributor in a single week
    max_commits = max(
        max(commits for _, commits in data['weekly'].items())
        for _, data in contributions.items())

    all_weeks = [_week(date.today() - timedelta(weeks=51 - n))
        for n in range(52)]

    def bars(contributions):
        bars = list()
        for ordinal, week in enumerate(all_weeks):
            if week in contributions:
                week_commits = contributions[week]
                bars.append({
                    "ordinal": ordinal,
                    "week": week,
                    "commits": week_commits,
                    "height": 100 * week_commits // max_commits
                })
        return bars

    chart_data = [
        (email, full_name, username, data['commits'], bars(data['weekly']))
        for (email, full_name, username), data in contributions.items()]
    return sorted(chart_data, key=lambda x: x[3], reverse=True)

@stats.route("/<owner>/<repo>/contributors")
def contributors(owner, repo):
    owner, repo = get_repo_or_redir(owner, repo)
    since = datetime.now() - timedelta(weeks=52)

    with GitRepository(repo.path) as git_repo:
        default_branch = git_repo.default_branch()
        tip = git_repo.get(default_branch.target)
        contributions = get_contributions(git_repo, tip, since)
        chart_data = get_contrib_chart_data(contributions)

    return render_template("contributors.html", view="contributors",
        owner=owner, repo=repo, chart_data=chart_data)

M gitsrht/repos.py => gitsrht/repos.py +20 -1
@@ 1,7 1,9 @@
import subprocess
from flask import redirect, abort, url_for
from gitsrht.access import get_repo, has_access, UserAccess
from gitsrht.types import User, Repository, RepoVisibility, Redirect
from srht.database import db
from srht.config import cfg
from gitsrht.types import Repository, RepoVisibility, Redirect
import shutil
import re
import os


@@ 94,3 96,20 @@ def delete_repo(repo):
        pass
    db.session.delete(repo)
    db.session.commit()

def get_repo_or_redir(owner, repo):
    owner, repo = get_repo(owner, repo)
    if not repo:
        abort(404)
    if not has_access(repo, UserAccess.read):
        abort(401)
    if isinstance(repo, Redirect):
        view_args = request.view_args
        if not "repo" in view_args or not "owner" in view_args:
            return redirect(url_for(".summary",
                owner=repo.new_repo.owner.canonical_name,
                repo=repo.new_repo.name))
        view_args["owner"] = repo.new_repo.owner.canonical_name
        view_args["repo"] = repo.new_repo.name
        abort(redirect(url_for(request.endpoint, **view_args)))
    return owner, repo

M gitsrht/templates/contributors.html => gitsrht/templates/contributors.html +12 -9
@@ 10,17 10,19 @@
{% block head %}
  <style type="text/css">
    svg { background-color: WhiteSmoke }
    rect { fill: #007bff }
    rect { fill: #116bdd }
    rect:hover { fill: #dc3545 }
  </style>
{% endblock %}

{% block content %}
<div class="container">
  <p>Contributions in the last 52 weeks</p>

  <p>
    Contributions to {{repo.owner.canonical_name}}/{{repo.name}}
    in the last 52 weeks:
  </p>
  <div class="row">
  {% for email, username, commits, bars in chart_data %}
  {% for email, full_name, username, commits, bars in chart_data %}
    <div class="col-md-6">
      <h3>
        {% if username %}


@@ 28,8 30,9 @@
            ~{{ username }}
          </a>
        {% else %}
          {{ email }}
          {{ full_name }}
        {% endif %}
        <small>{{ commits }} commits</small>
      </h3>

      <svg


@@ 42,15 45,15 @@
          <rect
            x="{{ bar.ordinal * 10 }}"
            y="{{ 100 - bar.height }}%"
            width="10"
            width="11"
            height="{{ bar.height }}%"
          >
            <title>{{ bar.commits }} commits in week {{ bar.week }}</title>
            <title>
              {{ bar.commits }} commits in the week of {{ bar.week }}
            </title>
          </rect>
        {% endfor %}
      </svg>

      <p>Commits: {{ commits }}</p>
    </div>
  {% endfor %}
  </div>

M gitsrht/templates/repo.html => gitsrht/templates/repo.html +1 -1
@@ 36,7 36,7 @@
            repo=repo.name), "refs")}}
        </li>
        <li class="nav-item">
          {{link(url_for("repo.contributors",
          {{link(url_for("stats.contributors",
            owner=repo.owner.canonical_name,
            repo=repo.name), "contributors")}}
        </li>

M setup.py => setup.py +0 -1
@@ 1,7 1,6 @@
#!/usr/bin/env python3
from setuptools import setup
import subprocess
import glob
import os
import site
import sys