|
Pierre-Yves Chibon |
33b534 |
# -*- coding: utf-8 -*-
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
"""
|
|
Pierre-Yves Chibon |
8a5345 |
(c) 2014-2015 - Copyright Red Hat Inc
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
Authors:
|
|
Pierre-Yves Chibon |
2088eb |
Pierre-Yves Chibon <pingou@pingoured.fr></pingou@pingoured.fr>
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
"""
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
e04c77 |
# These two lines are needed to run on EL6
|
|
Pierre-Yves Chibon |
ee9b42 |
__requires__ = ['SQLAlchemy >= 0.8', 'jinja2 >= 2.4']
|
|
Pierre-Yves Chibon |
2088eb |
import pkg_resources
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
f006c4 |
__version__ = '0.0'
|
|
Pierre-Yves Chibon |
96098d |
__api_version__ = '0'
|
|
Pierre-Yves Chibon |
96098d |
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
eef6e0 |
import datetime
|
|
Pierre-Yves Chibon |
2088eb |
import logging
|
|
Pierre-Yves Chibon |
2088eb |
import os
|
|
Pierre-Yves Chibon |
df4e53 |
import subprocess
|
|
Pierre-Yves Chibon |
4118fb |
import textwrap
|
|
Pierre-Yves Chibon |
9f17e1 |
import urlparse
|
|
Pierre-Yves Chibon |
2088eb |
from logging.handlers import SMTPHandler
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
import arrow
|
|
Pierre-Yves Chibon |
2088eb |
import flask
|
|
Pierre-Yves Chibon |
d8f19f |
import pygit2
|
|
Pierre-Yves Chibon |
2088eb |
from flask_fas_openid import FAS
|
|
Pierre-Yves Chibon |
2088eb |
from functools import wraps
|
|
Pierre-Yves Chibon |
2088eb |
from sqlalchemy.exc import SQLAlchemyError
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
d558c6 |
import progit.lib
|
|
Pierre-Yves Chibon |
077809 |
import progit.mail_logging
|
|
Pierre-Yves Chibon |
89e681 |
import progit.doc_utils
|
|
Pierre-Yves Chibon |
01b5fa |
import progit.login_forms
|
|
Johan Cwiklinski |
fa06f9 |
import markdown
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Johan Cwiklinski |
002118 |
from pygments import highlight
|
|
Johan Cwiklinski |
002118 |
from pygments.lexers.text import DiffLexer
|
|
Johan Cwiklinski |
002118 |
from pygments.formatters import HtmlFormatter
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
# Create the application.
|
|
Pierre-Yves Chibon |
2088eb |
APP = flask.Flask(__name__)
|
|
Pierre-Yves Chibon |
76e062 |
APP.jinja_env.trim_blocks = True
|
|
Pierre-Yves Chibon |
76e062 |
APP.jinja_env.lstrip_blocks = True
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
# set up FAS
|
|
Pierre-Yves Chibon |
2088eb |
APP.config.from_object('progit.default_config')
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
if 'PROGIT_CONFIG' in os.environ:
|
|
Pierre-Yves Chibon |
2088eb |
APP.config.from_envvar('PROGIT_CONFIG')
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
FAS = FAS(APP)
|
|
Pierre-Yves Chibon |
d558c6 |
SESSION = progit.lib.create_session(APP.config['DB_URL'])
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
if not APP.debug:
|
|
Pierre-Yves Chibon |
077809 |
APP.logger.addHandler(progit.mail_logging.get_mail_handler(
|
|
Pierre-Yves Chibon |
077809 |
smtp_server=APP.config.get('SMTP_SERVER', '127.0.0.1'),
|
|
Pierre-Yves Chibon |
077809 |
mail_admin=APP.config.get('MAIL_ADMIN', APP.config['EMAIL_ERROR'])
|
|
Pierre-Yves Chibon |
077809 |
))
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
e04c77 |
# Send classic logs into syslog
|
|
Pierre-Yves Chibon |
2088eb |
handler = logging.StreamHandler()
|
|
Pierre-Yves Chibon |
2088eb |
handler.setLevel(APP.config.get('log_level', 'INFO'))
|
|
Pierre-Yves Chibon |
2088eb |
APP.logger.addHandler(handler)
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
LOG = APP.logger
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
def authenticated():
|
|
Pierre-Yves Chibon |
2088eb |
return hasattr(flask.g, 'fas_user') and flask.g.fas_user
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
9f17e1 |
def is_safe_url(target):
|
|
Pierre-Yves Chibon |
9f17e1 |
""" Checks that the target url is safe and sending to the current
|
|
Pierre-Yves Chibon |
9f17e1 |
website not some other malicious one.
|
|
Pierre-Yves Chibon |
9f17e1 |
"""
|
|
Pierre-Yves Chibon |
9f17e1 |
ref_url = urlparse.urlparse(flask.request.host_url)
|
|
Pierre-Yves Chibon |
9f17e1 |
test_url = urlparse.urlparse(
|
|
Pierre-Yves Chibon |
9f17e1 |
urlparse.urljoin(flask.request.host_url, target))
|
|
Pierre-Yves Chibon |
9f17e1 |
return test_url.scheme in ('http', 'https') and \
|
|
Pierre-Yves Chibon |
9f17e1 |
ref_url.netloc == test_url.netloc
|
|
Pierre-Yves Chibon |
9f17e1 |
|
|
Pierre-Yves Chibon |
9f17e1 |
|
|
Pierre-Yves Chibon |
2088eb |
def is_admin():
|
|
Pierre-Yves Chibon |
2088eb |
""" Return whether the user is admin for this application or not. """
|
|
Pierre-Yves Chibon |
c491be |
if not authenticated():
|
|
Pierre-Yves Chibon |
2088eb |
return False
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
f58951 |
user = flask.g.fas_user
|
|
Pierre-Yves Chibon |
f58951 |
|
|
Pierre-Yves Chibon |
c491be |
auth_method = APP.config.get('PROGIT_AUTH', None)
|
|
Pierre-Yves Chibon |
c491be |
if auth_method == 'fas':
|
|
Pierre-Yves Chibon |
c491be |
if not user.cla_done or len(user.groups) < 1:
|
|
Pierre-Yves Chibon |
c491be |
return False
|
|
Pierre-Yves Chibon |
c491be |
|
|
Pierre-Yves Chibon |
2088eb |
admins = APP.config['ADMIN_GROUP']
|
|
Pierre-Yves Chibon |
2088eb |
if isinstance(admins, basestring):
|
|
Pierre-Yves Chibon |
c491be |
admins = [admins]
|
|
Pierre-Yves Chibon |
c491be |
admins = set(admins)
|
|
Pierre-Yves Chibon |
f58951 |
groups = set(flask.g.fas_user.groups)
|
|
Pierre-Yves Chibon |
c491be |
|
|
Pierre-Yves Chibon |
f58951 |
return not groups.isdisjoint(admins)
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
d5f37b |
def is_repo_admin(repo_obj):
|
|
Pierre-Yves Chibon |
d5f37b |
""" Return whether the user is an admin of the provided repo. """
|
|
Pierre-Yves Chibon |
787f4e |
if not authenticated():
|
|
Pierre-Yves Chibon |
d5f37b |
return False
|
|
Pierre-Yves Chibon |
d5f37b |
|
|
Pierre-Yves Chibon |
d5f37b |
user = flask.g.fas_user.username
|
|
Pierre-Yves Chibon |
d5f37b |
|
|
Pierre-Yves Chibon |
7f7a0e |
return user == repo_obj.user.user or (
|
|
Pierre-Yves Chibon |
7f7a0e |
user in [user.user for user in repo_obj.users])
|
|
Pierre-Yves Chibon |
d5f37b |
|
|
Pierre-Yves Chibon |
d5f37b |
|
|
Pierre-Yves Chibon |
a2d51a |
def generate_gitolite_acls():
|
|
Pierre-Yves Chibon |
a2d51a |
""" Generate the gitolite configuration file for all repos
|
|
Pierre-Yves Chibon |
a2d51a |
"""
|
|
Pierre-Yves Chibon |
7d3f17 |
progit.lib.git.write_gitolite_acls(
|
|
Pierre-Yves Chibon |
a4cc93 |
SESSION, APP.config['GITOLITE_CONFIG'])
|
|
Pierre-Yves Chibon |
a2d51a |
|
|
Pierre-Yves Chibon |
77a4ae |
gitolite_folder = APP.config.get('GITOLITE_HOME', None)
|
|
Pierre-Yves Chibon |
77a4ae |
if gitolite_folder:
|
|
Pierre-Yves Chibon |
1280ca |
cmd = 'GL_RC=%s GL_BINDIR=%s gl-compile-conf' % (
|
|
Pierre-Yves Chibon |
1280ca |
APP.config.get('GL_RC'), APP.config.get('GL_BINDIR')
|
|
Pierre-Yves Chibon |
1280ca |
)
|
|
Pierre-Yves Chibon |
412880 |
output = subprocess.Popen(
|
|
Pierre-Yves Chibon |
412880 |
cmd,
|
|
Pierre-Yves Chibon |
1280ca |
shell=True,
|
|
Pierre-Yves Chibon |
1280ca |
stdout=subprocess.PIPE,
|
|
Pierre-Yves Chibon |
1280ca |
stderr=subprocess.PIPE,
|
|
Pierre-Yves Chibon |
1280ca |
cwd=gitolite_folder
|
|
Pierre-Yves Chibon |
1280ca |
)
|
|
Pierre-Yves Chibon |
77a4ae |
|
|
Pierre-Yves Chibon |
a2d51a |
|
|
Pierre-Yves Chibon |
f1d3a6 |
def generate_gitolite_key(user, key):
|
|
Pierre-Yves Chibon |
f1d3a6 |
""" Generate the gitolite ssh key file for the specified user
|
|
Pierre-Yves Chibon |
f1d3a6 |
"""
|
|
Pierre-Yves Chibon |
f1d3a6 |
gitolite_keydir = APP.config.get('GITOLITE_KEYDIR', None)
|
|
Pierre-Yves Chibon |
f1d3a6 |
if gitolite_keydir:
|
|
Pierre-Yves Chibon |
f1d3a6 |
keyfile = os.path.join(gitolite_keydir, '%s.pub' % user)
|
|
Pierre-Yves Chibon |
f1d3a6 |
with open(keyfile, 'w') as stream:
|
|
Pierre-Yves Chibon |
f1d3a6 |
stream.write(key + '\n')
|
|
Pierre-Yves Chibon |
f1d3a6 |
|
|
Pierre-Yves Chibon |
f1d3a6 |
|
|
Pierre-Yves Chibon |
078c2b |
def generate_authorized_key_file():
|
|
Pierre-Yves Chibon |
078c2b |
""" Regenerate the `authorized_keys` file used by gitolite.
|
|
Pierre-Yves Chibon |
078c2b |
"""
|
|
Pierre-Yves Chibon |
078c2b |
gitolite_home = APP.config.get('GITOLITE_HOME', None)
|
|
Pierre-Yves Chibon |
078c2b |
if gitolite_home:
|
|
Pierre-Yves Chibon |
a1a039 |
users = progit.lib.search_user(SESSION)
|
|
Pierre-Yves Chibon |
078c2b |
|
|
Pierre-Yves Chibon |
078c2b |
authorized_file = os.path.join(
|
|
Pierre-Yves Chibon |
078c2b |
gitolite_home, '.ssh', 'authorized_keys')
|
|
Pierre-Yves Chibon |
6310e8 |
with open(authorized_file, 'w') as stream:
|
|
Pierre-Yves Chibon |
078c2b |
stream.write('# gitolite start\n')
|
|
Pierre-Yves Chibon |
078c2b |
for user in users:
|
|
Pierre-Yves Chibon |
078c2b |
if not user.public_ssh_key:
|
|
Pierre-Yves Chibon |
078c2b |
continue
|
|
Pierre-Yves Chibon |
078c2b |
row = 'command="/usr/bin/gl-auth-command %s",' \
|
|
Pierre-Yves Chibon |
078c2b |
'no-port-forwarding,no-X11-forwarding,'\
|
|
Pierre-Yves Chibon |
078c2b |
'no-agent-forwarding,no-pty %s' % (
|
|
Pierre-Yves Chibon |
05748c |
user.user, user.public_ssh_key)
|
|
Pierre-Yves Chibon |
078c2b |
stream.write(row + '\n')
|
|
Pierre-Yves Chibon |
078c2b |
stream.write('# gitolite end\n')
|
|
Pierre-Yves Chibon |
078c2b |
|
|
Pierre-Yves Chibon |
078c2b |
|
|
Pierre-Yves Chibon |
2088eb |
def cla_required(function):
|
|
Pierre-Yves Chibon |
2088eb |
""" Flask decorator to retrict access to CLA signed user.
|
|
Pierre-Yves Chibon |
2088eb |
To use this decorator you need to have a function named 'auth_login'.
|
|
Pierre-Yves Chibon |
2088eb |
Without that function the redirect if the user is not logged in will not
|
|
Pierre-Yves Chibon |
2088eb |
work.
|
|
Pierre-Yves Chibon |
2088eb |
"""
|
|
Pierre-Yves Chibon |
aa949a |
auth_method = APP.config.get('PROGIT_AUTH', None)
|
|
Pierre-Yves Chibon |
e04c77 |
|
|
Pierre-Yves Chibon |
2088eb |
@wraps(function)
|
|
Pierre-Yves Chibon |
2088eb |
def decorated_function(*args, **kwargs):
|
|
Pierre-Yves Chibon |
2088eb |
""" Decorated function, actually does the work. """
|
|
Pierre-Yves Chibon |
2088eb |
if not authenticated():
|
|
Pierre-Yves Chibon |
2088eb |
return flask.redirect(
|
|
Pierre-Yves Chibon |
2088eb |
flask.url_for('auth_login', next=flask.request.url))
|
|
Pierre-Yves Chibon |
aa949a |
elif auth_method == 'fas' and not flask.g.fas_user.cla_done:
|
|
Pierre-Yves Chibon |
2088eb |
flask.flash('You must sign the CLA (Contributor License '
|
|
Pierre-Yves Chibon |
767d71 |
'Agreement to use progit', 'errors')
|
|
Pierre-Yves Chibon |
2088eb |
return flask.redirect(flask.url_for('.index'))
|
|
Pierre-Yves Chibon |
2088eb |
return function(*args, **kwargs)
|
|
Pierre-Yves Chibon |
2088eb |
return decorated_function
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
@APP.context_processor
|
|
Pierre-Yves Chibon |
2088eb |
def inject_variables():
|
|
Pierre-Yves Chibon |
2088eb |
""" With this decorator we can set some variables to all templates.
|
|
Pierre-Yves Chibon |
2088eb |
"""
|
|
Pierre-Yves Chibon |
2088eb |
user_admin = is_admin()
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
return dict(
|
|
Pierre-Yves Chibon |
2088eb |
version=__version__,
|
|
Pierre-Yves Chibon |
2088eb |
admin=user_admin,
|
|
Pierre-Yves Chibon |
b9c444 |
authenticated=authenticated(),
|
|
Pierre-Yves Chibon |
2088eb |
)
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
6428d8 |
|
|
Pierre-Yves Chibon |
25369f |
# pylint: disable=W0613
|
|
Pierre-Yves Chibon |
25369f |
@APP.before_request
|
|
Pierre-Yves Chibon |
25369f |
def set_session():
|
|
Pierre-Yves Chibon |
25369f |
""" Set the flask session as permanent. """
|
|
Pierre-Yves Chibon |
25369f |
flask.session.permanent = True
|
|
Pierre-Yves Chibon |
25369f |
|
|
Pierre-Yves Chibon |
25369f |
|
|
Pierre-Yves Chibon |
e108db |
@APP.template_filter('hasattr')
|
|
Pierre-Yves Chibon |
e108db |
def jinja_hasattr(obj, string):
|
|
Pierre-Yves Chibon |
e108db |
""" Template filter checking if the provided object at the provided
|
|
Pierre-Yves Chibon |
e108db |
string as attribute
|
|
Pierre-Yves Chibon |
e108db |
"""
|
|
Pierre-Yves Chibon |
e108db |
return hasattr(obj, string)
|
|
Pierre-Yves Chibon |
e108db |
|
|
Pierre-Yves Chibon |
e108db |
|
|
Pierre-Yves Chibon |
2088eb |
@APP.template_filter('lastcommit_date')
|
|
Pierre-Yves Chibon |
2088eb |
def lastcommit_date_filter(repo):
|
|
Pierre-Yves Chibon |
2088eb |
""" Template filter returning the last commit date of the provided repo.
|
|
Pierre-Yves Chibon |
2088eb |
"""
|
|
Pierre-Yves Chibon |
671d31 |
if not repo.is_empty:
|
|
Pierre-Yves Chibon |
671d31 |
commit = repo[repo.head.target]
|
|
Pierre-Yves Chibon |
671d31 |
return arrow.get(commit.commit_time).humanize()
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
6e710b |
@APP.template_filter('humanize')
|
|
Pierre-Yves Chibon |
6e710b |
def humanize_date(date):
|
|
Pierre-Yves Chibon |
6e710b |
""" Template filter returning the last commit date of the provided repo.
|
|
Pierre-Yves Chibon |
6e710b |
"""
|
|
Pierre-Yves Chibon |
6e710b |
return arrow.get(date).humanize()
|
|
Pierre-Yves Chibon |
6e710b |
|
|
Pierre-Yves Chibon |
6e710b |
|
|
Pierre-Yves Chibon |
c560ab |
@APP.template_filter('rst2html')
|
|
Pierre-Yves Chibon |
c560ab |
def rst2html(rst_string):
|
|
Pierre-Yves Chibon |
c560ab |
""" Template filter transforming rst text into html
|
|
Pierre-Yves Chibon |
c560ab |
"""
|
|
Pierre-Yves Chibon |
cbdf87 |
if rst_string:
|
|
Pierre-Yves Chibon |
cbdf87 |
return progit.doc_utils.convert_doc(unicode(rst_string))
|
|
Pierre-Yves Chibon |
c560ab |
|
|
Pierre-Yves Chibon |
c560ab |
|
|
Pierre-Yves Chibon |
eef6e0 |
@APP.template_filter('format_ts')
|
|
Pierre-Yves Chibon |
eef6e0 |
def format_ts(string):
|
|
Pierre-Yves Chibon |
eef6e0 |
""" Template filter transforming a timestamp to a date
|
|
Pierre-Yves Chibon |
eef6e0 |
"""
|
|
Pierre-Yves Chibon |
eef6e0 |
dt = datetime.datetime.fromtimestamp(int(string))
|
|
Pierre-Yves Chibon |
eef6e0 |
return dt.strftime('%b %d %Y %H:%M:%S')
|
|
Pierre-Yves Chibon |
eef6e0 |
|
|
Pierre-Yves Chibon |
eef6e0 |
|
|
Pierre-Yves Chibon |
8a11eb |
@APP.template_filter('format_loc')
|
|
Pierre-Yves Chibon |
6f8e9d |
def format_loc(loc, commit=None, filename=None, prequest=None, index=None):
|
|
Pierre-Yves Chibon |
8a11eb |
""" Template filter putting the provided lines of code into a table
|
|
Pierre-Yves Chibon |
8a11eb |
"""
|
|
Pierre-Yves Chibon |
126396 |
if loc is None:
|
|
Pierre-Yves Chibon |
126396 |
return
|
|
Pierre-Yves Chibon |
126396 |
|
|
Pierre-Yves Chibon |
8a11eb |
output = [
|
|
Pierre-Yves Chibon |
f7c6d4 |
'',
|
|
Pierre-Yves Chibon |
8a11eb |
''
|
|
Pierre-Yves Chibon |
8a11eb |
]
|
|
Pierre-Yves Chibon |
12e0df |
|
|
Pierre-Yves Chibon |
12e0df |
comments = {}
|
|
Pierre-Yves Chibon |
3d1811 |
if prequest and not isinstance(prequest, flask.wrappers.Request):
|
|
Pierre-Yves Chibon |
12e0df |
for com in prequest.comments:
|
|
Pierre-Yves Chibon |
6f8e9d |
if commit and com.commit_id == commit:
|
|
Pierre-Yves Chibon |
61123c |
if com.line in comments:
|
|
Pierre-Yves Chibon |
61123c |
comments[com.line].append(com)
|
|
Pierre-Yves Chibon |
61123c |
else:
|
|
Pierre-Yves Chibon |
61123c |
comments[com.line] = [com]
|
|
Pierre-Yves Chibon |
61123c |
for key in comments:
|
|
Pierre-Yves Chibon |
6f8e9d |
comments[key] = sorted(
|
|
Pierre-Yves Chibon |
6f8e9d |
comments[key], key=lambda obj: obj.date_created)
|
|
Pierre-Yves Chibon |
12e0df |
|
|
Pierre-Yves Chibon |
fe7ea3 |
if not index:
|
|
Pierre-Yves Chibon |
fe7ea3 |
index = ''
|
|
Pierre-Yves Chibon |
fe7ea3 |
|
|
Pierre-Yves Chibon |
8a11eb |
cnt = 1
|
|
Pierre-Yves Chibon |
8a11eb |
for line in loc.split('\n'):
|
|
Pierre-Yves Chibon |
6f8e9d |
if filename and commit:
|
|
Pierre-Yves Chibon |
03b8f4 |
output.append(
|
|
Pierre-Yves Chibon |
03b8f4 |
''
|
|
Pierre-Yves Chibon |
fe7ea3 |
'%(cnt_lbl)s'
|
|
Pierre-Yves Chibon |
6f8e9d |
'
|
|
Pierre-Yves Chibon |
6f8e9d |
' data-filename="%(filename)s" data-commit="%(commit)s">'
|
|
Pierre-Yves Chibon |
03b8f4 |
''
|
|
Pierre-Yves Chibon |
03b8f4 |
' '
|
|
Pierre-Yves Chibon |
03b8f4 |
''
|
|
Pierre-Yves Chibon |
03b8f4 |
'' % (
|
|
Pierre-Yves Chibon |
03b8f4 |
{
|
|
Pierre-Yves Chibon |
fe7ea3 |
'cnt': '%s_%s' % (index, cnt),
|
|
Pierre-Yves Chibon |
fe7ea3 |
'cnt_lbl': cnt,
|
|
Pierre-Yves Chibon |
03b8f4 |
'img': flask.url_for('static', filename='users.png'),
|
|
Pierre-Yves Chibon |
6f8e9d |
'filename': filename,
|
|
Pierre-Yves Chibon |
6f8e9d |
'commit': commit,
|
|
Pierre-Yves Chibon |
03b8f4 |
}
|
|
Pierre-Yves Chibon |
03b8f4 |
)
|
|
Pierre-Yves Chibon |
03b8f4 |
)
|
|
Pierre-Yves Chibon |
03b8f4 |
else:
|
|
Pierre-Yves Chibon |
03b8f4 |
output.append(
|
|
Pierre-Yves Chibon |
03b8f4 |
''
|
|
Pierre-Yves Chibon |
23b035 |
'%(cnt_lbl)s'
|
|
Pierre-Yves Chibon |
fe7ea3 |
% (
|
|
Pierre-Yves Chibon |
fe7ea3 |
{
|
|
Pierre-Yves Chibon |
fe7ea3 |
'cnt': '%s_%s' % (index, cnt),
|
|
Pierre-Yves Chibon |
fe7ea3 |
'cnt_lbl': cnt,
|
|
Pierre-Yves Chibon |
fe7ea3 |
}
|
|
Pierre-Yves Chibon |
fe7ea3 |
)
|
|
Pierre-Yves Chibon |
03b8f4 |
)
|
|
Pierre-Yves Chibon |
03b8f4 |
|
|
Pierre-Yves Chibon |
8a11eb |
cnt += 1
|
|
Pierre-Yves Chibon |
8a11eb |
if not line:
|
|
Pierre-Yves Chibon |
8a11eb |
output.append(line)
|
|
Pierre-Yves Chibon |
8a11eb |
continue
|
|
Pierre-Yves Chibon |
8a11eb |
if line == '':
|
|
Pierre-Yves Chibon |
8a11eb |
continue
|
|
Pierre-Yves Chibon |
8a11eb |
if line.startswith('
|
|
Pierre-Yves Chibon |
8a11eb |
line = line.split('')[1]
|
|
Pierre-Yves Chibon |
b7dd86 |
output.append('%s ' % line)
|
|
Pierre-Yves Chibon |
8a11eb |
output.append('')
|
|
Pierre-Yves Chibon |
12e0df |
|
|
Pierre-Yves Chibon |
12e0df |
if cnt - 1 in comments:
|
|
Pierre-Yves Chibon |
e04c77 |
for comment in comments[cnt - 1]:
|
|
Pierre-Yves Chibon |
692b92 |
output.append(
|
|
Pierre-Yves Chibon |
692b92 |
''
|
|
Pierre-Yves Chibon |
692b92 |
''
|
|
Pierre-Yves Chibon |
e04c77 |
'%(user)s'
|
|
Pierre-Yves Chibon |
e04c77 |
'%(date)s'
|
|
Pierre-Yves Chibon |
692b92 |
''
|
|
Pierre-Yves Chibon |
e04c77 |
'%(comment)s'
|
|
Pierre-Yves Chibon |
e04c77 |
''
|
|
Pierre-Yves Chibon |
692b92 |
'' % (
|
|
Pierre-Yves Chibon |
692b92 |
{
|
|
Pierre-Yves Chibon |
692b92 |
'url': flask.url_for(
|
|
Pierre-Yves Chibon |
692b92 |
'view_user', username=comment.user.user),
|
|
Pierre-Yves Chibon |
692b92 |
'user': comment.user.user,
|
|
Pierre-Yves Chibon |
692b92 |
'date': comment.date_created.strftime(
|
|
Pierre-Yves Chibon |
692b92 |
'%b %d %Y %H:%M:%S'),
|
|
Pierre-Yves Chibon |
692b92 |
'comment': comment.comment,
|
|
Pierre-Yves Chibon |
692b92 |
}
|
|
Pierre-Yves Chibon |
692b92 |
)
|
|
Pierre-Yves Chibon |
12e0df |
)
|
|
Pierre-Yves Chibon |
12e0df |
|
|
Pierre-Yves Chibon |
8a11eb |
output.append('')
|
|
Pierre-Yves Chibon |
8a11eb |
|
|
Pierre-Yves Chibon |
8a11eb |
return '\n'.join(output)
|
|
Pierre-Yves Chibon |
8a11eb |
|
|
Pierre-Yves Chibon |
8a11eb |
|
|
Pierre-Yves Chibon |
4118fb |
@APP.template_filter('wraps')
|
|
Pierre-Yves Chibon |
4118fb |
def text_wraps(text, size=10):
|
|
Pierre-Yves Chibon |
4118fb |
""" Template filter to wrap text at a specified size
|
|
Pierre-Yves Chibon |
4118fb |
"""
|
|
Pierre-Yves Chibon |
4118fb |
if text:
|
|
Pierre-Yves Chibon |
4118fb |
parts = textwrap.wrap(text, size)
|
|
Pierre-Yves Chibon |
4118fb |
if len(parts) > 1:
|
|
Pierre-Yves Chibon |
4118fb |
parts = '%s...' % parts[0]
|
|
Pierre-Yves Chibon |
4118fb |
else:
|
|
Pierre-Yves Chibon |
4118fb |
parts = parts[0]
|
|
Pierre-Yves Chibon |
4118fb |
return parts
|
|
Pierre-Yves Chibon |
4118fb |
|
|
Pierre-Yves Chibon |
4118fb |
|
|
Pierre-Yves Chibon |
8d50a9 |
@APP.template_filter('avatar')
|
|
Pierre-Yves Chibon |
8d50a9 |
def avatar(packager, size=64):
|
|
Pierre-Yves Chibon |
8d50a9 |
""" Template filter sorting the given branches, Fedora first then EPEL,
|
|
Pierre-Yves Chibon |
8d50a9 |
then whatever is left.
|
|
Pierre-Yves Chibon |
8d50a9 |
"""
|
|
Pierre-Yves Chibon |
8d50a9 |
output = ' ' % (
|
|
Pierre-Yves Chibon |
8d50a9 |
progit.lib.avatar_url(packager, size)
|
|
Pierre-Yves Chibon |
8d50a9 |
)
|
|
Pierre-Yves Chibon |
8d50a9 |
|
|
Pierre-Yves Chibon |
8d50a9 |
return output
|
|
Pierre-Yves Chibon |
8d50a9 |
|
|
Pierre-Yves Chibon |
8d50a9 |
|
|
Johan Cwiklinski |
b930cf |
@APP.template_filter('short')
|
|
Johan Cwiklinski |
b930cf |
def shorted_commit(cid):
|
|
Johan Cwiklinski |
b930cf |
"""Gets short version of the commit id"""
|
|
Johan Cwiklinski |
b930cf |
return cid[:APP.config['SHORT_LENGTH']]
|
|
Johan Cwiklinski |
b930cf |
|
|
Johan Cwiklinski |
b930cf |
|
|
Johan Cwiklinski |
fa06f9 |
@APP.template_filter('markdown')
|
|
Johan Cwiklinski |
fa06f9 |
def markdown_filter(text):
|
|
Johan Cwiklinski |
fa06f9 |
""" Template filter converting a string into html content using the
|
|
Johan Cwiklinski |
fa06f9 |
markdown library.
|
|
Johan Cwiklinski |
fa06f9 |
"""
|
|
Johan Cwiklinski |
fa06f9 |
if text:
|
|
Pierre-Yves Chibon |
cbb77d |
# Hack to allow blockquotes to be marked by ~~~
|
|
Pierre-Yves Chibon |
cbb77d |
ntext = []
|
|
Pierre-Yves Chibon |
cbb77d |
indent = False
|
|
Pierre-Yves Chibon |
cbb77d |
for line in text.split('\n'):
|
|
Pierre-Yves Chibon |
cbb77d |
if line.startswith('~~~'):
|
|
Pierre-Yves Chibon |
cbb77d |
indent = not indent
|
|
Pierre-Yves Chibon |
cbb77d |
continue
|
|
Pierre-Yves Chibon |
cbb77d |
if indent:
|
|
Pierre-Yves Chibon |
cbb77d |
line = ' %s' % line
|
|
Pierre-Yves Chibon |
cbb77d |
ntext.append(line)
|
|
Pierre-Yves Chibon |
cbb77d |
return markdown.markdown('\n'.join(ntext))
|
|
Johan Cwiklinski |
fa06f9 |
|
|
Johan Cwiklinski |
fa06f9 |
return ''
|
|
Johan Cwiklinski |
fa06f9 |
|
|
Johan Cwiklinski |
fa06f9 |
|
|
Johan Cwiklinski |
002118 |
@APP.template_filter('html_diff')
|
|
Johan Cwiklinski |
002118 |
def html_diff(diff):
|
|
Johan Cwiklinski |
002118 |
"""Display diff as HTML"""
|
|
Pierre-Yves Chibon |
126396 |
if diff is None:
|
|
Pierre-Yves Chibon |
126396 |
return
|
|
Johan Cwiklinski |
002118 |
return highlight(
|
|
Johan Cwiklinski |
002118 |
diff,
|
|
Johan Cwiklinski |
002118 |
DiffLexer(),
|
|
Johan Cwiklinski |
002118 |
HtmlFormatter(
|
|
Johan Cwiklinski |
002118 |
noclasses=True,
|
|
Johan Cwiklinski |
002118 |
style="tango",)
|
|
Johan Cwiklinski |
002118 |
)
|
|
Johan Cwiklinski |
002118 |
|
|
Johan Cwiklinski |
002118 |
|
|
Johan Cwiklinski |
002118 |
@APP.template_filter('patch_to_diff')
|
|
Johan Cwiklinski |
002118 |
def patch_to_diff(patch):
|
|
Johan Cwiklinski |
002118 |
"""Render a hunk as a diff"""
|
|
Johan Cwiklinski |
002118 |
content = ""
|
|
Johan Cwiklinski |
002118 |
for hunk in patch.hunks:
|
|
Pierre-Yves Chibon |
e04c77 |
content = content + "@@ -%i,%i +%i,%i @@\n" % (
|
|
Pierre-Yves Chibon |
e04c77 |
hunk.old_start, hunk.old_lines, hunk.new_start, hunk.new_lines)
|
|
Johan Cwiklinski |
002118 |
for line in hunk.lines:
|
|
Johan Cwiklinski |
002118 |
content = content + ' '.join(line)
|
|
Johan Cwiklinski |
002118 |
return content
|
|
Johan Cwiklinski |
002118 |
|
|
Johan Cwiklinski |
002118 |
|
|
Pierre-Yves Chibon |
e6a6e2 |
@FAS.postlogin
|
|
Pierre-Yves Chibon |
e6a6e2 |
def set_user(return_url):
|
|
Pierre-Yves Chibon |
e6a6e2 |
''' After login method. '''
|
|
Pierre-Yves Chibon |
e6a6e2 |
try:
|
|
Pierre-Yves Chibon |
e6a6e2 |
progit.lib.set_up_user(
|
|
Pierre-Yves Chibon |
e6a6e2 |
session=SESSION,
|
|
Pierre-Yves Chibon |
e6a6e2 |
username=flask.g.fas_user.username,
|
|
Pierre-Yves Chibon |
e6a6e2 |
fullname=flask.g.fas_user.fullname,
|
|
Pierre-Yves Chibon |
e6a6e2 |
user_email=flask.g.fas_user.email,
|
|
Pierre-Yves Chibon |
e6a6e2 |
)
|
|
Pierre-Yves Chibon |
e6a6e2 |
SESSION.commit()
|
|
Pierre-Yves Chibon |
e6a6e2 |
except SQLAlchemyError, err:
|
|
Pierre-Yves Chibon |
c3beca |
SESSION.rollback()
|
|
Pierre-Yves Chibon |
01f0bb |
LOG.debug(err)
|
|
Pierre-Yves Chibon |
01f0bb |
LOG.exception(err)
|
|
Pierre-Yves Chibon |
e6a6e2 |
flask.flash(
|
|
Pierre-Yves Chibon |
e6a6e2 |
'Could not set up you as a user properly, please contact '
|
|
Pierre-Yves Chibon |
e6a6e2 |
'an admin', 'error')
|
|
Pierre-Yves Chibon |
e6a6e2 |
return flask.redirect(return_url)
|
|
Pierre-Yves Chibon |
e6a6e2 |
|
|
Pierre-Yves Chibon |
e6a6e2 |
|
|
Johan Cwiklinski |
2eb061 |
@APP.errorhandler(404)
|
|
Johan Cwiklinski |
2eb061 |
def not_found(error):
|
|
Johan Cwiklinski |
2eb061 |
"""404 Not Found page"""
|
|
Johan Cwiklinski |
2eb061 |
return flask.render_template('not_found.html'), 404
|
|
Johan Cwiklinski |
2eb061 |
|
|
Johan Cwiklinski |
2eb061 |
|
|
Johan Cwiklinski |
2eb061 |
@APP.errorhandler(500)
|
|
Johan Cwiklinski |
2eb061 |
def fatal_error(error):
|
|
Johan Cwiklinski |
2eb061 |
"""500 Fatal Error page"""
|
|
Johan Cwiklinski |
2eb061 |
return flask.render_template('fatal_error.html'), 500
|
|
Johan Cwiklinski |
2eb061 |
|
|
Johan Cwiklinski |
2eb061 |
|
|
Johan Cwiklinski |
2eb061 |
@APP.errorhandler(401)
|
|
Johan Cwiklinski |
2eb061 |
def unauthorized(error):
|
|
Johan Cwiklinski |
2eb061 |
"""401 Unauthorized page"""
|
|
Johan Cwiklinski |
2eb061 |
return flask.render_template('unauthorized.html'), 401
|
|
Johan Cwiklinski |
2eb061 |
|
|
Johan Cwiklinski |
2eb061 |
|
|
Pierre-Yves Chibon |
2088eb |
@APP.route('/login/', methods=('GET', 'POST'))
|
|
Pierre-Yves Chibon |
2088eb |
def auth_login():
|
|
Pierre-Yves Chibon |
2088eb |
""" Method to log into the application using FAS OpenID. """
|
|
Pierre-Yves Chibon |
2088eb |
return_point = flask.url_for('index')
|
|
Pierre-Yves Chibon |
2088eb |
if 'next' in flask.request.args:
|
|
Pierre-Yves Chibon |
9f17e1 |
if is_safe_url(flask.request.args['next']):
|
|
Pierre-Yves Chibon |
9f17e1 |
return_point = flask.request.args['next']
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
if authenticated():
|
|
Pierre-Yves Chibon |
2088eb |
return flask.redirect(return_point)
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
85023f |
admins = APP.config['ADMIN_GROUP']
|
|
Pierre-Yves Chibon |
85023f |
if isinstance(admins, basestring):
|
|
Pierre-Yves Chibon |
85023f |
admins = set([admins])
|
|
Pierre-Yves Chibon |
85023f |
else: # pragma: no cover
|
|
Pierre-Yves Chibon |
85023f |
admins = set(admins)
|
|
Pierre-Yves Chibon |
85023f |
|
|
Pierre-Yves Chibon |
01b5fa |
if APP.config.get('PROGIT_AUTH', None) == 'fas':
|
|
Pierre-Yves Chibon |
01b5fa |
return FAS.login(return_url=return_point, groups=admins)
|
|
Pierre-Yves Chibon |
01b5fa |
elif APP.config.get('PROGIT_AUTH', None) == 'local':
|
|
Pierre-Yves Chibon |
01b5fa |
form = progit.login_forms.LoginForm()
|
|
Pierre-Yves Chibon |
01b5fa |
return flask.render_template(
|
|
Pierre-Yves Chibon |
01b5fa |
'login/login.html',
|
|
Pierre-Yves Chibon |
01b5fa |
next_url=return_point,
|
|
Pierre-Yves Chibon |
01b5fa |
form=form,
|
|
Pierre-Yves Chibon |
01b5fa |
)
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
2088eb |
@APP.route('/logout/')
|
|
Pierre-Yves Chibon |
2088eb |
def auth_logout():
|
|
Pierre-Yves Chibon |
2088eb |
""" Method to log out from the application. """
|
|
Pierre-Yves Chibon |
a3f381 |
return_point = flask.url_for('index')
|
|
Pierre-Yves Chibon |
a3f381 |
if 'next' in flask.request.args:
|
|
Pierre-Yves Chibon |
a3f381 |
if is_safe_url(flask.request.args['next']):
|
|
Pierre-Yves Chibon |
a3f381 |
return_point = flask.request.args['next']
|
|
Pierre-Yves Chibon |
a3f381 |
|
|
Pierre-Yves Chibon |
2088eb |
if not authenticated():
|
|
Pierre-Yves Chibon |
a3f381 |
return flask.redirect(return_point)
|
|
Pierre-Yves Chibon |
a3f381 |
|
|
Pierre-Yves Chibon |
09b4ef |
if APP.config.get('PROGIT_AUTH', None) == 'fas':
|
|
Pierre-Yves Chibon |
09b4ef |
if hasattr(flask.g, 'fas_user') and flask.g.fas_user is not None:
|
|
Pierre-Yves Chibon |
09b4ef |
FAS.logout()
|
|
Pierre-Yves Chibon |
09b4ef |
flask.flash("You are no longer logged-in")
|
|
Pierre-Yves Chibon |
09b4ef |
elif APP.config.get('PROGIT_AUTH', None) == 'local':
|
|
Pierre-Yves Chibon |
09b4ef |
login.logout()
|
|
Pierre-Yves Chibon |
a3f381 |
return flask.redirect(return_point)
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
d8f19f |
|
|
Pierre-Yves Chibon |
d8f19f |
def __get_file_in_tree(repo_obj, tree, filepath):
|
|
Pierre-Yves Chibon |
d8f19f |
''' Retrieve the entry corresponding to the provided filename in a
|
|
Pierre-Yves Chibon |
d8f19f |
given tree.
|
|
Pierre-Yves Chibon |
d8f19f |
'''
|
|
Pierre-Yves Chibon |
d8f19f |
filename = filepath[0]
|
|
Pierre-Yves Chibon |
d8f19f |
if isinstance(tree, pygit2.Blob):
|
|
Pierre-Yves Chibon |
d8f19f |
return
|
|
Pierre-Yves Chibon |
d8f19f |
for el in tree:
|
|
Pierre-Yves Chibon |
d8f19f |
if el.name == filename:
|
|
Pierre-Yves Chibon |
d8f19f |
if len(filepath) == 1:
|
|
Pierre-Yves Chibon |
d8f19f |
return repo_obj[el.oid]
|
|
Pierre-Yves Chibon |
d8f19f |
else:
|
|
Pierre-Yves Chibon |
d8f19f |
return __get_file_in_tree(
|
|
Pierre-Yves Chibon |
d8f19f |
repo_obj, repo_obj[el.oid], filepath[1:])
|
|
Pierre-Yves Chibon |
d8f19f |
|
|
Pierre-Yves Chibon |
2088eb |
|
|
Pierre-Yves Chibon |
e04c77 |
# Import the application
|
|
Pierre-Yves Chibon |
9a7c26 |
import progit.ui.app
|
|
Pierre-Yves Chibon |
9a7c26 |
import progit.ui.admin
|
|
Pierre-Yves Chibon |
9a7c26 |
import progit.ui.docs
|
|
Pierre-Yves Chibon |
9a7c26 |
import progit.ui.fork
|
|
Pierre-Yves Chibon |
9a7c26 |
import progit.ui.issues
|
|
Pierre-Yves Chibon |
9a7c26 |
import progit.ui.plugins
|
|
Pierre-Yves Chibon |
9a7c26 |
import progit.ui.repo
|
|
Pierre-Yves Chibon |
754cef |
|
|
Pierre-Yves Chibon |
96098d |
import progit.api
|
|
Pierre-Yves Chibon |
96098d |
APP.register_blueprint(progit.api.API)
|
|
Pierre-Yves Chibon |
96098d |
|
|
Pierre-Yves Chibon |
754cef |
|
|
Pierre-Yves Chibon |
754cef |
# Only import the login controller if the app is set up for local login
|
|
Pierre-Yves Chibon |
754cef |
if APP.config.get('PROGIT_AUTH', None) == 'local':
|
|
Pierre-Yves Chibon |
754cef |
import progit.ui.login as login
|
|
Pierre-Yves Chibon |
754cef |
APP.before_request(login._check_session_cookie)
|
|
Pierre-Yves Chibon |
754cef |
APP.after_request(login._send_session_cookie)
|