|
Pierre-Yves Chibon |
15e568 |
# -*- coding: utf-8 -*-
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
"""
|
|
Pierre-Yves Chibon |
15e568 |
(c) 2014 - Copyright Red Hat Inc
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
Authors:
|
|
Pierre-Yves Chibon |
15e568 |
Pierre-Yves Chibon <pingou@pingoured.fr></pingou@pingoured.fr>
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
"""
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
import datetime
|
|
Pierre-Yves Chibon |
15e568 |
import textwrap
|
|
Pierre-Yves Chibon |
15e568 |
import os
|
|
Pierre-Yves Chibon |
8774fb |
import re
|
|
Pierre-Yves Chibon |
15e568 |
from math import ceil
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
import flask
|
|
Pierre-Yves Chibon |
15e568 |
import arrow
|
|
Pierre-Yves Chibon |
15e568 |
import markdown
|
|
Pierre-Yves Chibon |
15e568 |
import pygit2
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
from sqlalchemy.exc import SQLAlchemyError
|
|
Pierre-Yves Chibon |
15e568 |
from pygments import highlight
|
|
Pierre-Yves Chibon |
15e568 |
from pygments.lexers import guess_lexer
|
|
Pierre-Yves Chibon |
15e568 |
from pygments.lexers.text import DiffLexer
|
|
Pierre-Yves Chibon |
15e568 |
from pygments.formatters import HtmlFormatter
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
import progit.exceptions
|
|
Pierre-Yves Chibon |
15e568 |
import progit.lib
|
|
Pierre-Yves Chibon |
15e568 |
import progit.forms
|
|
Pierre-Yves Chibon |
15e568 |
from progit import (APP, SESSION, LOG, __get_file_in_tree, cla_required,
|
|
Pierre-Yves Chibon |
15e568 |
generate_gitolite_acls, generate_gitolite_key,
|
|
Pierre-Yves Chibon |
15e568 |
generate_authorized_key_file, authenticated)
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
# Jinja filters
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
@APP.template_filter('humanize')
|
|
Pierre-Yves Chibon |
15e568 |
def humanize_date(date):
|
|
Pierre-Yves Chibon |
15e568 |
""" Template filter returning the last commit date of the provided repo.
|
|
Pierre-Yves Chibon |
15e568 |
"""
|
|
Pierre-Yves Chibon |
15e568 |
return arrow.get(date).humanize()
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
@APP.template_filter('format_ts')
|
|
Pierre-Yves Chibon |
15e568 |
def format_ts(string):
|
|
Pierre-Yves Chibon |
15e568 |
""" Template filter transforming a timestamp to a date
|
|
Pierre-Yves Chibon |
15e568 |
"""
|
|
Pierre-Yves Chibon |
15e568 |
dt = datetime.datetime.fromtimestamp(int(string))
|
|
Pierre-Yves Chibon |
15e568 |
return dt.strftime('%b %d %Y %H:%M:%S')
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
@APP.template_filter('format_loc')
|
|
Pierre-Yves Chibon |
15e568 |
def format_loc(loc, commit=None, filename=None, prequest=None, index=None):
|
|
Pierre-Yves Chibon |
15e568 |
""" Template filter putting the provided lines of code into a table
|
|
Pierre-Yves Chibon |
15e568 |
"""
|
|
Pierre-Yves Chibon |
15e568 |
if loc is None:
|
|
Pierre-Yves Chibon |
15e568 |
return
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
output = [
|
|
Pierre-Yves Chibon |
15e568 |
'',
|
|
Pierre-Yves Chibon |
15e568 |
''
|
|
Pierre-Yves Chibon |
15e568 |
]
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
comments = {}
|
|
Pierre-Yves Chibon |
15e568 |
if prequest and not isinstance(prequest, flask.wrappers.Request):
|
|
Pierre-Yves Chibon |
15e568 |
for com in prequest.comments:
|
|
Pierre-Yves Chibon |
15e568 |
if commit and com.commit_id == commit:
|
|
Pierre-Yves Chibon |
15e568 |
if com.line in comments:
|
|
Pierre-Yves Chibon |
15e568 |
comments[com.line].append(com)
|
|
Pierre-Yves Chibon |
15e568 |
else:
|
|
Pierre-Yves Chibon |
15e568 |
comments[com.line] = [com]
|
|
Pierre-Yves Chibon |
15e568 |
for key in comments:
|
|
Pierre-Yves Chibon |
15e568 |
comments[key] = sorted(
|
|
Pierre-Yves Chibon |
15e568 |
comments[key], key=lambda obj: obj.date_created)
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
if not index:
|
|
Pierre-Yves Chibon |
15e568 |
index = ''
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
cnt = 1
|
|
Pierre-Yves Chibon |
15e568 |
for line in loc.split('\n'):
|
|
Pierre-Yves Chibon |
15e568 |
if filename and commit:
|
|
Pierre-Yves Chibon |
15e568 |
output.append(
|
|
Pierre-Yves Chibon |
15e568 |
''
|
|
Pierre-Yves Chibon |
15e568 |
'%(cnt_lbl)s'
|
|
Pierre-Yves Chibon |
15e568 |
'
|
|
Pierre-Yves Chibon |
15e568 |
' data-filename="%(filename)s" data-commit="%(commit)s">'
|
|
Pierre-Yves Chibon |
15e568 |
''
|
|
Pierre-Yves Chibon |
15e568 |
' '
|
|
Pierre-Yves Chibon |
15e568 |
''
|
|
Pierre-Yves Chibon |
15e568 |
'' % (
|
|
Pierre-Yves Chibon |
15e568 |
{
|
|
Pierre-Yves Chibon |
15e568 |
'cnt': '%s_%s' % (index, cnt),
|
|
Pierre-Yves Chibon |
15e568 |
'cnt_lbl': cnt,
|
|
Pierre-Yves Chibon |
15e568 |
'img': flask.url_for('static', filename='users.png'),
|
|
Pierre-Yves Chibon |
15e568 |
'filename': filename,
|
|
Pierre-Yves Chibon |
15e568 |
'commit': commit,
|
|
Pierre-Yves Chibon |
15e568 |
}
|
|
Pierre-Yves Chibon |
15e568 |
)
|
|
Pierre-Yves Chibon |
15e568 |
)
|
|
Pierre-Yves Chibon |
15e568 |
else:
|
|
Pierre-Yves Chibon |
15e568 |
output.append(
|
|
Pierre-Yves Chibon |
15e568 |
''
|
|
Pierre-Yves Chibon |
15e568 |
'%(cnt_lbl)s'
|
|
Pierre-Yves Chibon |
15e568 |
% (
|
|
Pierre-Yves Chibon |
15e568 |
{
|
|
Pierre-Yves Chibon |
15e568 |
'cnt': '%s_%s' % (index, cnt),
|
|
Pierre-Yves Chibon |
15e568 |
'cnt_lbl': cnt,
|
|
Pierre-Yves Chibon |
15e568 |
}
|
|
Pierre-Yves Chibon |
15e568 |
)
|
|
Pierre-Yves Chibon |
15e568 |
)
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
cnt += 1
|
|
Pierre-Yves Chibon |
15e568 |
if not line:
|
|
Pierre-Yves Chibon |
15e568 |
output.append(line)
|
|
Pierre-Yves Chibon |
15e568 |
continue
|
|
Pierre-Yves Chibon |
15e568 |
if line == '':
|
|
Pierre-Yves Chibon |
15e568 |
continue
|
|
Pierre-Yves Chibon |
15e568 |
if line.startswith('
|
|
Pierre-Yves Chibon |
15e568 |
line = line.split('')[1]
|
|
Pierre-Yves Chibon |
15e568 |
output.append('%s ' % line)
|
|
Pierre-Yves Chibon |
15e568 |
output.append('')
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
if cnt - 1 in comments:
|
|
Pierre-Yves Chibon |
15e568 |
for comment in comments[cnt - 1]:
|
|
Pierre-Yves Chibon |
15e568 |
output.append(
|
|
Pierre-Yves Chibon |
15e568 |
''
|
|
Pierre-Yves Chibon |
15e568 |
''
|
|
Pierre-Yves Chibon |
15e568 |
'%(user)s'
|
|
Pierre-Yves Chibon |
15e568 |
'%(date)s'
|
|
Pierre-Yves Chibon |
15e568 |
''
|
|
Pierre-Yves Chibon |
15e568 |
'%(comment)s'
|
|
Pierre-Yves Chibon |
15e568 |
''
|
|
Pierre-Yves Chibon |
15e568 |
'' % (
|
|
Pierre-Yves Chibon |
15e568 |
{
|
|
Pierre-Yves Chibon |
15e568 |
'url': flask.url_for(
|
|
Pierre-Yves Chibon |
15e568 |
'view_user', username=comment.user.user),
|
|
Pierre-Yves Chibon |
15e568 |
'user': comment.user.user,
|
|
Pierre-Yves Chibon |
15e568 |
'date': comment.date_created.strftime(
|
|
Pierre-Yves Chibon |
15e568 |
'%b %d %Y %H:%M:%S'),
|
|
Pierre-Yves Chibon |
15e568 |
'comment': comment.comment,
|
|
Pierre-Yves Chibon |
15e568 |
}
|
|
Pierre-Yves Chibon |
15e568 |
)
|
|
Pierre-Yves Chibon |
15e568 |
)
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
output.append('')
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
return '\n'.join(output)
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
@APP.template_filter('wraps')
|
|
Pierre-Yves Chibon |
15e568 |
def text_wraps(text, size=10):
|
|
Pierre-Yves Chibon |
15e568 |
""" Template filter to wrap text at a specified size
|
|
Pierre-Yves Chibon |
15e568 |
"""
|
|
Pierre-Yves Chibon |
15e568 |
if text:
|
|
Pierre-Yves Chibon |
15e568 |
parts = textwrap.wrap(text, size)
|
|
Pierre-Yves Chibon |
15e568 |
if len(parts) > 1:
|
|
Pierre-Yves Chibon |
15e568 |
parts = '%s...' % parts[0]
|
|
Pierre-Yves Chibon |
15e568 |
else:
|
|
Pierre-Yves Chibon |
15e568 |
parts = parts[0]
|
|
Pierre-Yves Chibon |
15e568 |
return parts
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
@APP.template_filter('avatar')
|
|
Pierre-Yves Chibon |
15e568 |
def avatar(packager, size=64):
|
|
Pierre-Yves Chibon |
15e568 |
""" Template filter sorting the given branches, Fedora first then EPEL,
|
|
Pierre-Yves Chibon |
15e568 |
then whatever is left.
|
|
Pierre-Yves Chibon |
15e568 |
"""
|
|
Pierre-Yves Chibon |
15e568 |
output = ' ' % (
|
|
Pierre-Yves Chibon |
15e568 |
progit.lib.avatar_url(packager, size)
|
|
Pierre-Yves Chibon |
15e568 |
)
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
return output
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
@APP.template_filter('short')
|
|
Pierre-Yves Chibon |
15e568 |
def shorted_commit(cid):
|
|
Pierre-Yves Chibon |
15e568 |
"""Gets short version of the commit id"""
|
|
Pierre-Yves Chibon |
15e568 |
return cid[:APP.config['SHORT_LENGTH']]
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
f89a82 |
@APP.template_filter('crossref')
|
|
Pierre-Yves Chibon |
f89a82 |
def crossref_filter(text):
|
|
Pierre-Yves Chibon |
f89a82 |
""" Template filter adding a link when the provided text references
|
|
Pierre-Yves Chibon |
f89a82 |
another issue or pull-request.
|
|
Pierre-Yves Chibon |
f89a82 |
"""
|
|
Pierre-Yves Chibon |
f89a82 |
regex = re.compile('.*\s#(\d+)', re.I)
|
|
Pierre-Yves Chibon |
f89a82 |
if text:
|
|
Pierre-Yves Chibon |
f89a82 |
if regex.match(text):
|
|
Pierre-Yves Chibon |
f89a82 |
|
|
Pierre-Yves Chibon |
f89a82 |
url = flask.request.url
|
|
Pierre-Yves Chibon |
f89a82 |
username = None
|
|
Pierre-Yves Chibon |
f89a82 |
if 'fork/' in flask.request.url:
|
|
Pierre-Yves Chibon |
f89a82 |
username, project = url.split('fork/')[1].split('/', 2)[:2]
|
|
Pierre-Yves Chibon |
f89a82 |
else:
|
|
Pierre-Yves Chibon |
f89a82 |
project = url.split(
|
|
Pierre-Yves Chibon |
f89a82 |
flask.request.url_root)[1].split('/', 1)[0]
|
|
Pierre-Yves Chibon |
f89a82 |
|
|
Pierre-Yves Chibon |
f89a82 |
issueid = regex.match(text).group(1)
|
|
Pierre-Yves Chibon |
f89a82 |
text = text.replace('#%s' % issueid, '[#%s](%s)' % (
|
|
Pierre-Yves Chibon |
f89a82 |
issueid, flask.url_for(
|
|
Pierre-Yves Chibon |
f89a82 |
'view_issue', username=username, repo=project,
|
|
Pierre-Yves Chibon |
f89a82 |
issueid=issueid)
|
|
Pierre-Yves Chibon |
f89a82 |
)
|
|
Pierre-Yves Chibon |
f89a82 |
)
|
|
Pierre-Yves Chibon |
f89a82 |
|
|
Pierre-Yves Chibon |
f89a82 |
return text
|
|
Pierre-Yves Chibon |
f89a82 |
else:
|
|
Pierre-Yves Chibon |
f89a82 |
return ''
|
|
Pierre-Yves Chibon |
f89a82 |
|
|
Pierre-Yves Chibon |
f89a82 |
|
|
Pierre-Yves Chibon |
15e568 |
@APP.template_filter('markdown')
|
|
Pierre-Yves Chibon |
15e568 |
def markdown_filter(text):
|
|
Pierre-Yves Chibon |
15e568 |
""" Template filter converting a string into html content using the
|
|
Pierre-Yves Chibon |
15e568 |
markdown library.
|
|
Pierre-Yves Chibon |
15e568 |
"""
|
|
Pierre-Yves Chibon |
15e568 |
if text:
|
|
Pierre-Yves Chibon |
15e568 |
# Hack to allow blockquotes to be marked by ~~~
|
|
Pierre-Yves Chibon |
15e568 |
ntext = []
|
|
Pierre-Yves Chibon |
15e568 |
indent = False
|
|
Pierre-Yves Chibon |
8774fb |
regexes = [
|
|
Pierre-Yves Chibon |
8774fb |
re.compile('.*\s(http(s)?:\/\/\S+).*'),
|
|
Pierre-Yves Chibon |
8774fb |
re.compile('^(http(s)?:\/\/\S+).*'),
|
|
Pierre-Yves Chibon |
8774fb |
re.compile('.*\s(ftp(s)?:\/\/\S+).*'),
|
|
Pierre-Yves Chibon |
8774fb |
re.compile('^(ftp(s)?:\/\/\S+).*'),
|
|
Pierre-Yves Chibon |
8774fb |
]
|
|
Pierre-Yves Chibon |
15e568 |
for line in text.split('\n'):
|
|
Pierre-Yves Chibon |
8774fb |
# Automatically link URLs
|
|
Pierre-Yves Chibon |
8774fb |
for regex in regexes:
|
|
Pierre-Yves Chibon |
8774fb |
if regex.match(line):
|
|
Pierre-Yves Chibon |
8774fb |
line = line.replace(
|
|
Pierre-Yves Chibon |
8774fb |
regex.match(line).group(1),
|
|
Pierre-Yves Chibon |
8774fb |
regex.sub(r'<\1>', line)
|
|
Pierre-Yves Chibon |
8774fb |
)
|
|
Pierre-Yves Chibon |
8774fb |
|
|
Pierre-Yves Chibon |
15e568 |
if line.startswith('~~~'):
|
|
Pierre-Yves Chibon |
15e568 |
indent = not indent
|
|
Pierre-Yves Chibon |
15e568 |
continue
|
|
Pierre-Yves Chibon |
15e568 |
if indent:
|
|
Pierre-Yves Chibon |
15e568 |
line = ' %s' % line
|
|
Pierre-Yves Chibon |
15e568 |
ntext.append(line)
|
|
Pierre-Yves Chibon |
15e568 |
return markdown.markdown('\n'.join(ntext))
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
return ''
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
@APP.template_filter('html_diff')
|
|
Pierre-Yves Chibon |
15e568 |
def html_diff(diff):
|
|
Pierre-Yves Chibon |
15e568 |
"""Display diff as HTML"""
|
|
Pierre-Yves Chibon |
15e568 |
if diff is None:
|
|
Pierre-Yves Chibon |
15e568 |
return
|
|
Pierre-Yves Chibon |
15e568 |
return highlight(
|
|
Pierre-Yves Chibon |
15e568 |
diff,
|
|
Pierre-Yves Chibon |
15e568 |
DiffLexer(),
|
|
Pierre-Yves Chibon |
15e568 |
HtmlFormatter(
|
|
Pierre-Yves Chibon |
15e568 |
noclasses=True,
|
|
Pierre-Yves Chibon |
15e568 |
style="tango",)
|
|
Pierre-Yves Chibon |
15e568 |
)
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
|
|
Pierre-Yves Chibon |
15e568 |
@APP.template_filter('patch_to_diff')
|
|
Pierre-Yves Chibon |
15e568 |
def patch_to_diff(patch):
|
|
Pierre-Yves Chibon |
15e568 |
"""Render a hunk as a diff"""
|
|
Pierre-Yves Chibon |
15e568 |
content = ""
|
|
Pierre-Yves Chibon |
15e568 |
for hunk in patch.hunks:
|
|
Pierre-Yves Chibon |
15e568 |
content = content + "@@ -%i,%i +%i,%i @@\n" % (
|
|
Pierre-Yves Chibon |
15e568 |
hunk.old_start, hunk.old_lines, hunk.new_start, hunk.new_lines)
|
|
Pierre-Yves Chibon |
15e568 |
for line in hunk.lines:
|
|
Pierre-Yves Chibon |
15e568 |
content = content + ' '.join(line)
|
|
Pierre-Yves Chibon |
15e568 |
return content
|