Skip to content
Snippets Groups Projects
Commit 8f8de89a authored by Jonas Haag's avatar Jonas Haag
Browse files

Basic repo history and commit views

parent 2f411cc5
No related branches found
No related tags found
No related merge requests found
......@@ -34,6 +34,7 @@ class ReloadApplicationMiddleware(object):
def import_app():
sys.modules.pop('klaus', None)
sys.modules.pop('repo', None)
from klaus import app
app.repos = {repo.rstrip(os.sep).split(os.sep)[-1] : repo
for repo in sys.argv[1:]}
......
import os
import time
from functools import wraps
from nano import NanoApplication
from dulwich.objects import Commit
from jinja2 import Environment, FileSystemLoader
from pygments import highlight
from pygments.lexers import get_lexer_by_name, guess_lexer
from pygments.formatters import HtmlFormatter
from nano import NanoApplication, HttpError
from repo import Repo
class KlausApplication(NanoApplication):
def __init__(self, *args, **kwargs):
super(KlausApplication, self).__init__(*args, **kwargs)
......@@ -20,11 +31,51 @@ class KlausApplication(NanoApplication):
return super_decorator(wrapper)
return decorator
def render_template(self, **kwargs):
def render_template(self, template_name, **kwargs):
return self.jinja_env.get_template(template_name).render(**kwargs)
app = KlausApplication(debug=True, default_content_type='text/html')
#pygments_formatter = HtmlFormatter(linenos=True, cssclass='code')
def pygmentize(code, language=None, formatter=HtmlFormatter(linenos=True)):
if language is None:
lexer = guess_lexer(code)
else:
lexer = get_lexer_by_name(language, stripall=True, tabsize=4)
return highlight(code, lexer, formatter)
def timesince(when, now=time.time):
delta = time.time() - when
result = []
for unit, seconds in [
('year', 365*24*60*60),
('month', 30*24*60*60),
('week', 7*24*60*60),
('day', 24*60*60),
('hour', 60*60),
('minute', 60),
('second', 1),
]:
if delta > seconds:
n = int(delta/seconds)
delta -= n*seconds
result.append((n, unit))
if result[0][1] != 'year':
result = result[1:]
return ', '.join('%d %s%s' % (n, unit, 's' if n != 1 else '')
for n, unit in result[:2])
app.jinja_env.filters['timesince'] = timesince
app.jinja_env.filters['shorten_id'] = lambda id: id[:7]
app.jinja_env.filters['pygmentize'] = pygmentize
def get_repo(name):
try:
return Repo(name, app.repos[name])
except KeyError:
raise HttpError(404, 'No repository named "%s"' % name)
@app.route('/')
def repo_list(env):
......@@ -32,4 +83,28 @@ def repo_list(env):
@app.route('/:repo:/')
def view_repo(env, repo):
pass
return {'repo' : get_repo(repo)}
@app.route('/:repo:/commit/:id:/')
def view_commit(env, repo, id):
repo = get_repo(repo)
try:
commit = repo[id]
if not isinstance(commit, Commit):
raise KeyError
except KeyError:
raise HttpError(404, '"%s" has no commit "%s"' % (repo.name, id))
return {'commit' : commit, 'repo' : repo}
if app.debug:
@app.route('/static/(?P<path>.+)')
def view(env, path):
path = './static/' + path
relpath = os.path.join(os.getcwd(), path)
if os.path.isdir(relpath):
return index(relpath, path)
elif os.path.isfile(relpath):
return open(relpath)
else:
raise HttpError(404, 'Not Found')
repo.py 0 → 100644
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
import difflib
import dulwich, dulwich.patch
class RepoWrapper(dulwich.repo.Repo):
def get_branch(self, name=None):
if name is None:
name = 'master'
return self['refs/heads/'+name]
def history(self, branch=None, max_commits=None):
if max_commits is None:
max_commits = float('inf')
head = self.get_branch(branch)
while max_commits and head.parents:
yield head
head = self[head.parents[0]]
max_commits -= 1
def listdir(self, branch=None, root=None):
branch = self.get_branch(branch)
tree = self[branch.tree]
if root is not None:
for directory in root.split('/'):
tree = self[tree[directory].sha]
return tree.iteritems()
def commit_diff(self, commit):
parent = self[commit.parents[0]]
stringio = StringIO()
dulwich.patch.write_tree_diff(stringio, self.object_store,
parent.tree, commit.tree)
return stringio.getvalue()
class ChangeWrapper(dulwich.diff_tree.TreeChange):
def as_udiff(self):
with open(self.old.path) as f1, open(self.new.path) as f2:
return ''.join(difflib.unified_diff(f1, f2))
def Repo(name, path, _cache={}):
repo = _cache.get(path)
if repo is None:
repo = _cache[path] = RepoWrapper(path)
repo.name = name
return repo
/* This is the Pygments Trac theme */
.code .hll { background-color: #ffffcc }
.code { background: #ffffff; }
.code .c { color: #999988; font-style: italic } /* Comment */
.code .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.code .k { font-weight: bold } /* Keyword */
.code .o { font-weight: bold } /* Operator */
.code .cm { color: #999988; font-style: italic } /* Comment.Multiline */
.code .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
.code .c1 { color: #999988; font-style: italic } /* Comment.Single */
.code .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
.code .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.code .ge { font-style: italic } /* Generic.Emph */
.code .gr { color: #aa0000 } /* Generic.Error */
.code .gh { color: #999999 } /* Generic.Heading */
.code .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.code .go { color: #888888 } /* Generic.Output */
.code .gp { color: #555555 } /* Generic.Prompt */
.code .gs { font-weight: bold } /* Generic.Strong */
.code .gu { color: #aaaaaa } /* Generic.Subheading */
.code .gt { color: #aa0000 } /* Generic.Traceback */
.code .kc { font-weight: bold } /* Keyword.Constant */
.code .kd { font-weight: bold } /* Keyword.Declaration */
.code .kn { font-weight: bold } /* Keyword.Namespace */
.code .kp { font-weight: bold } /* Keyword.Pseudo */
.code .kr { font-weight: bold } /* Keyword.Reserved */
.code .kt { color: #445588; font-weight: bold } /* Keyword.Type */
.code .m { color: #009999 } /* Literal.Number */
.code .s { color: #bb8844 } /* Literal.String */
.code .na { color: #008080 } /* Name.Attribute */
.code .nb { color: #999999 } /* Name.Builtin */
.code .nc { color: #445588; font-weight: bold } /* Name.Class */
.code .no { color: #008080 } /* Name.Constant */
.code .ni { color: #800080 } /* Name.Entity */
.code .ne { color: #990000; font-weight: bold } /* Name.Exception */
.code .nf { color: #990000; font-weight: bold } /* Name.Function */
.code .nn { color: #555555 } /* Name.Namespace */
.code .nt { color: #000080 } /* Name.Tag */
.code .nv { color: #008080 } /* Name.Variable */
.code .ow { font-weight: bold } /* Operator.Word */
.code .w { color: #bbbbbb } /* Text.Whitespace */
.code .mf { color: #009999 } /* Literal.Number.Float */
.code .mh { color: #009999 } /* Literal.Number.Hex */
.code .mi { color: #009999 } /* Literal.Number.Integer */
.code .mo { color: #009999 } /* Literal.Number.Oct */
.code .sb { color: #bb8844 } /* Literal.String.Backtick */
.code .sc { color: #bb8844 } /* Literal.String.Char */
.code .sd { color: #bb8844 } /* Literal.String.Doc */
.code .s2 { color: #bb8844 } /* Literal.String.Double */
.code .se { color: #bb8844 } /* Literal.String.Escape */
.code .sh { color: #bb8844 } /* Literal.String.Heredoc */
.code .si { color: #bb8844 } /* Literal.String.Interpol */
.code .sx { color: #bb8844 } /* Literal.String.Other */
.code .sr { color: #808000 } /* Literal.String.Regex */
.code .s1 { color: #bb8844 } /* Literal.String.Single */
.code .ss { color: #bb8844 } /* Literal.String.Symbol */
.code .bp { color: #999999 } /* Name.Builtin.Pseudo */
.code .vc { color: #008080 } /* Name.Variable.Class */
.code .vg { color: #008080 } /* Name.Variable.Global */
.code .vi { color: #008080 } /* Name.Variable.Instance */
.code .il { color: #009999 } /* Literal.Number.Integer.Long */
<!doctype html>
<link rel=stylesheet href=/static/pygments.css>
<title>{{ title }}</title>
<h1>{{ title }}</h1>
<h1>{% block h1 %}{{ title }}{% endblock %}</h1>
{% block content %}{% endblock %}
{% set title = 'Repository list' %}
{% extends 'base.html' %}
{% block content %}
<ul class=repolist>
{% for name, _ in repos %}
<li><a href="{{ build_url('view_repo', repo=name) }}">{{ name }}</a></li>
{% endfor %}
</ul>
{% endblock %}
{% set title = 'Commit %s to %s' % (commit.id, repo.name) %}
{% extends 'base.html' %}
{% block h1 %}
Commit "{{commit.message}}" to
<a href="{{ build_url('view_repo', repo=repo.name) }}">{{ repo.name }}</a>
{% endblock %}
{% block content %}
<div class=commit>
<div class=meta>
<span class=id>{{ commit.id|shorten_id }}</span>
<span class=message>{{ commit.message.decode('utf-8') }}</span>
<span class=datetime>{{ commit.commit_time|timesince }} ago</span>
</div>
<div class=changes>
{{ repo.commit_diff(commit)|pygmentize }}
</div>
</div>
{% endblock %}
{% set title = repo.name %}
{% extends 'base.html' %}
{% block content %}
<ul class=history>
{% for commit in repo.history(max_commits=10) %}
<li>
<span class=id>
<a href="{{ build_url('view_commit', repo=repo.name, id=commit.id) }}">
{{ commit.id|shorten_id }}
</a>
</span>
<span class=message>{{ commit.message.decode('utf-8') }}</span>
<span class=datetime>{{ commit.commit_time|timesince }} ago</span>
</li>
{% endfor %}
</ul>
<ul class=tree>
{% for file in repo.listdir() %}
<li>{{ file }}</li>
{% endfor %}
</ul>
{% endblock %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment