From 0adb80afaa9900269de17719b47a2a3d660ce04f Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:18 +0000 Subject: [PATCH 1/20] Add a DOC_APP_URL containing the base URL of the doc application --- diff --git a/pagure/default_config.py b/pagure/default_config.py index 8314098..c6a176b 100644 --- a/pagure/default_config.py +++ b/pagure/default_config.py @@ -32,6 +32,7 @@ EMAIL_ERROR = 'pingou@pingoured.fr' # The URL at which the project is available. APP_URL = 'https://pagure.org/' +DOC_APP_URL = 'https://docs.pagure.org/' # The URL to use to clone the git repositories. GIT_URL_SSH = 'git@pagure.org' From 6df6eee8f359a75fa10ff8c67127bca0f1ae42f8 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:18 +0000 Subject: [PATCH 2/20] Remove pagure.ui.docs --- diff --git a/pagure/__init__.py b/pagure/__init__.py index d866414..0ddb990 100644 --- a/pagure/__init__.py +++ b/pagure/__init__.py @@ -401,7 +401,6 @@ pagure.pfmarkdown.inject() # Import the application import pagure.ui.app import pagure.ui.admin -import pagure.ui.docs import pagure.ui.fork import pagure.ui.groups import pagure.ui.issues diff --git a/pagure/ui/docs.py b/pagure/ui/docs.py deleted file mode 100644 index 58e361d..0000000 --- a/pagure/ui/docs.py +++ /dev/null @@ -1,146 +0,0 @@ -# -*- coding: utf-8 -*- - -""" - (c) 2014-2015 - Copyright Red Hat Inc - - Authors: - Pierre-Yves Chibon - -""" - -import flask -import os - -import pygit2 - -import pagure.doc_utils -import pagure.exceptions -import pagure.lib -import pagure.forms -from pagure import APP, SESSION - - -def __get_tree(repo_obj, tree, filepath, index=0, extended=False): - ''' Retrieve the entry corresponding to the provided filename in a - given tree. - ''' - filename = filepath[index] - if isinstance(tree, pygit2.Blob): # pragma: no cover - # If we were given a blob, then let's just return it - return (tree, None, None) - - for element in tree: - if element.name == filename or element.name.startswith('index'): - # If we have a folder we must go one level deeper - if element.filemode == 16384: - if (index + 1) == len(filepath): - filepath.append('') - return __get_tree( - repo_obj, repo_obj[element.oid], filepath, - index=index + 1, extended=True) - else: - return (element, tree, False) - - if filename == '': - return (None, tree, extended) - else: - raise pagure.exceptions.FileNotFoundException( - 'File %s not found' % ('/'.join(filepath),)) - - -def __get_tree_and_content(repo_obj, commit, path): - ''' Return the tree and the content of the specified file. ''' - - (blob_or_tree, tree_obj, extended) = __get_tree( - repo_obj, commit.tree, path) - - if blob_or_tree is None: - return (tree_obj, None, False, extended) - - if not repo_obj[blob_or_tree.oid]: - # Not tested and no idea how to test it, but better safe than sorry - flask.abort(404, 'File not found') - - if isinstance(blob_or_tree, pygit2.TreeEntry): # Returned a file - ext = os.path.splitext(blob_or_tree.name)[1] - blob_obj = repo_obj[blob_or_tree.oid] - content, safe = pagure.doc_utils.convert_readme(blob_obj.data, ext) - - tree = sorted(tree_obj, key=lambda x: x.filemode) - return (tree, content, safe, extended) - - -# URLs - - -@APP.route('//docs/') -@APP.route('//docs') -@APP.route('//docs/') -@APP.route('//docs/') -@APP.route('//docs//') -@APP.route('/fork///docs/') -@APP.route('/fork///docs') -@APP.route('/fork///docs/') -@APP.route('/fork///docs/') -@APP.route('/fork///docs//') -def view_docs(repo, username=None, branchname=None, filename=None): - """ Display the documentation - """ - - repo = pagure.lib.get_project(SESSION, repo, user=username) - - if not repo: - flask.abort(404, 'Project not found') - - if not repo.settings.get('project_documentation', True): - flask.abort(404, 'No documentation found for this project') - - reponame = os.path.join(APP.config['DOCS_FOLDER'], repo.path) - if not os.path.exists(reponame): - flask.flash( - 'No docs repository could be found, please contact an admin', - 'error') - return flask.redirect(flask.url_for( - 'view_repo', repo=repo.name, username=username)) - - repo_obj = pygit2.Repository(reponame) - - if branchname in repo_obj.listall_branches(): - branch = repo_obj.lookup_branch(branchname) - commit = branch.get_object() - else: - if not repo_obj.is_empty: - commit = repo_obj[repo_obj.head.target] - else: - commit = None - branchname = 'master' - - content = None - tree = None - safe = False - if not filename: - path = [''] - else: - path = [it for it in filename.split('/') if it] - - if commit: - try: - (tree, content, safe, extended) = __get_tree_and_content( - repo_obj, commit, path) - if extended: - filename += '/' - except pagure.exceptions.FileNotFoundException as err: - flask.flash(err.message, 'error') - - return flask.render_template( - 'docs.html', - select='docs', - repo_obj=repo_obj, - repo=repo, - username=username, - branchname=branchname, - filename=filename, - tree=tree, - content=content, - safe=safe, - ) From d71d6bc0d4dbb360d878b0f22188e95612805bfb Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:18 +0000 Subject: [PATCH 3/20] Create a new flask application just to server the docs --- diff --git a/pagure/docs_server.py b/pagure/docs_server.py new file mode 100644 index 0000000..c7d1f30 --- /dev/null +++ b/pagure/docs_server.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2014-2015 - Copyright Red Hat Inc + + Authors: + Pierre-Yves Chibon + +""" + +import logging +import os + +import flask +import pygit2 + +import pagure.doc_utils +import pagure.exceptions +import pagure.lib +import pagure.forms + +# Create the application. +APP = flask.Flask(__name__) + +# set up FAS +APP.config.from_object('pagure.default_config') + +if 'PAGURE_CONFIG' in os.environ: + APP.config.from_envvar('PAGURE_CONFIG') + +SESSION = pagure.lib.create_session(APP.config['DB_URL']) + +if not APP.debug: + APP.logger.addHandler(pagure.mail_logging.get_mail_handler( + smtp_server=APP.config.get('SMTP_SERVER', '127.0.0.1'), + mail_admin=APP.config.get('MAIL_ADMIN', APP.config['EMAIL_ERROR']) + )) + +# Send classic logs into syslog +handler = logging.StreamHandler() +handler.setLevel(APP.config.get('log_level', 'INFO')) +APP.logger.addHandler(handler) + +LOG = APP.logger + + +def __get_tree(repo_obj, tree, filepath, index=0, extended=False): + ''' Retrieve the entry corresponding to the provided filename in a + given tree. + ''' + filename = filepath[index] + if isinstance(tree, pygit2.Blob): # pragma: no cover + # If we were given a blob, then let's just return it + return (tree, None, None) + + for element in tree: + if element.name == filename or element.name.startswith('index'): + # If we have a folder we must go one level deeper + if element.filemode == 16384: + if (index + 1) == len(filepath): + filepath.append('') + return __get_tree( + repo_obj, repo_obj[element.oid], filepath, + index=index + 1, extended=True) + else: + return (element, tree, False) + + if filename == '': + return (None, tree, extended) + else: + raise pagure.exceptions.FileNotFoundException( + 'File %s not found' % ('/'.join(filepath),)) + + +def __get_tree_and_content(repo_obj, commit, path): + ''' Return the tree and the content of the specified file. ''' + + (blob_or_tree, tree_obj, extended) = __get_tree( + repo_obj, commit.tree, path) + + if blob_or_tree is None: + return (tree_obj, None, False, extended) + + if not repo_obj[blob_or_tree.oid]: + # Not tested and no idea how to test it, but better safe than sorry + flask.abort(404, 'File not found') + + if isinstance(blob_or_tree, pygit2.TreeEntry): # Returned a file + ext = os.path.splitext(blob_or_tree.name)[1] + blob_obj = repo_obj[blob_or_tree.oid] + content, safe = pagure.doc_utils.convert_readme(blob_obj.data, ext) + + tree = sorted(tree_obj, key=lambda x: x.filemode) + return (tree, content, safe, extended) + + +# URLs + + +@APP.route('//') +@APP.route('/') +@APP.route('//') +@APP.route('//') +@APP.route('///') +@APP.route('/fork///') +@APP.route('/fork//') +@APP.route('/fork///') +@APP.route('/fork///') +@APP.route('/fork////') +def view_docs(repo, username=None, branchname=None, filename=None): + """ Display the documentation + """ + + repo = pagure.lib.get_project(SESSION, repo, user=username) + + if not repo: + flask.abort(404, 'Project not found') + + if not repo.settings.get('project_documentation', True): + flask.abort(404, 'No documentation found for this project') + + reponame = os.path.join(APP.config['DOCS_FOLDER'], repo.path) + if not os.path.exists(reponame): + flask.abort(404, 'Documentation not found') + + repo_obj = pygit2.Repository(reponame) + + if branchname in repo_obj.listall_branches(): + branch = repo_obj.lookup_branch(branchname) + commit = branch.get_object() + else: + if not repo_obj.is_empty: + commit = repo_obj[repo_obj.head.target] + else: + commit = None + branchname = 'master' + + content = None + tree = None + safe = False + if not filename: + path = [''] + else: + path = [it for it in filename.split('/') if it] + + if commit: + try: + (tree, content, safe, extended) = __get_tree_and_content( + repo_obj, commit, path) + if extended: + filename += '/' + except pagure.exceptions.FileNotFoundException as err: + flask.flash(err.message, 'error') + + return flask.render_template( + 'docs.html', + select='docs', + repo_obj=repo_obj, + repo=repo, + username=username, + branchname=branchname, + filename=filename, + tree=tree, + content=content, + safe=safe, + ) From 814bc8307953f364085253573e368d3e56d81b3b Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:18 +0000 Subject: [PATCH 4/20] Move injecting our custom markdown to the backend library, allowing to make it optional This is needed if we want to share code with the docs server while not activating our own markdown syntax in the documentations. --- diff --git a/pagure/__init__.py b/pagure/__init__.py index 0ddb990..3153926 100644 --- a/pagure/__init__.py +++ b/pagure/__init__.py @@ -394,10 +394,6 @@ def get_repo_path(repo): return repopath -# Install our markdown modifications -import pagure.pfmarkdown -pagure.pfmarkdown.inject() - # Import the application import pagure.ui.app import pagure.ui.admin diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 749bdee..57b238a 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -38,6 +38,7 @@ import pagure.exceptions import pagure.lib.git import pagure.lib.login import pagure.lib.notify +import pagure.pfmarkdown from pagure.lib import model # pylint: disable=R0913 @@ -2272,9 +2273,13 @@ def add_token_to_user(session, project, acls, username): return 'Token created' -def text2markdown(text): +def text2markdown(text, extended=True): """ Simple text to html converter using the markdown library. """ + if extended: + # Install our markdown modifications + pagure.pfmarkdown.inject() + if text: # Hack to allow blockquotes to be marked by ~~~ ntext = [] From 85fa2fe7b2761e7091387f425f6e13d4f3dbf2cb Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:18 +0000 Subject: [PATCH 5/20] Display the content of the page as safe, all the time Good thing we're using a separate domain name for this --- diff --git a/pagure/templates/docs.html b/pagure/templates/docs.html index 46bd41c..731c9ec 100644 --- a/pagure/templates/docs.html +++ b/pagure/templates/docs.html @@ -72,11 +72,7 @@ {% if content %}
- {% if safe %} - {{ content |noJS |safe }} - {% else %} - {{ content |noJS }} - {% endif %} + {{ content |safe }}
{% endif %} From 3723baeabb722ad024a66b3d26a8b7962cef9275 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:18 +0000 Subject: [PATCH 6/20] Only show the login page if there is a reason for it, ie: not in the docs page --- diff --git a/pagure/docs_server.py b/pagure/docs_server.py index c7d1f30..21f7798 100644 --- a/pagure/docs_server.py +++ b/pagure/docs_server.py @@ -163,4 +163,5 @@ def view_docs(repo, username=None, branchname=None, filename=None): tree=tree, content=content, safe=safe, + nologin=True, ) diff --git a/pagure/templates/master.html b/pagure/templates/master.html index e905dfe..48f83cb 100644 --- a/pagure/templates/master.html +++ b/pagure/templates/master.html @@ -49,6 +49,7 @@ + {% if not nologin %} {% if g.fas_user %} logged in as @@ -59,6 +60,7 @@ login {% endif %} + {% endif %}
From 4e25cb5278566c99ce8a25bd7865d3bd1cb23adf Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:18 +0000 Subject: [PATCH 7/20] Define the jinja filter and URL endpoints required to re-use the templates In order to re-use the same template as pagure, the docs server must re-define a number of endpoints, used in these templates, except that they are all just redirects to pagure itself. --- diff --git a/pagure/docs_server.py b/pagure/docs_server.py index 21f7798..7f6b180 100644 --- a/pagure/docs_server.py +++ b/pagure/docs_server.py @@ -94,8 +94,129 @@ def __get_tree_and_content(repo_obj, commit, path): return (tree, content, safe, extended) -# URLs +# Jinja filter required +@APP.template_filter('markdown') +def markdown_filter(text): + """ Template filter converting a string into html content using the + markdown library. + """ + return pagure.lib.text2markdown(text, extended=False) + + +# Placeholder to allow re-using pagure's templates +@APP.route('/') +def index(): + return flask.redirect(APP.config['APP_URL']) + + +@APP.route('/users/') +def view_users(): + root_url = APP.config['APP_URL'] + if root_url.endswith('/'): + root_url = root_url[:-1] + return flask.redirect(root_url + '/users/') + + +@APP.route('/groups/') +def group_lists(): + root_url = APP.config['APP_URL'] + if root_url.endswith('/'): + root_url = root_url[:-1] + return flask.redirect(root_url + '/groups/') + + +@APP.route('/new/') +def new_project(): + root_url = APP.config['APP_URL'] + if root_url.endswith('/'): + root_url = root_url[:-1] + return flask.redirect(root_url + '/new/') + + +@APP.route('/repo//') +@APP.route('/repo/fork///') +def view_repo(repo, username=None): + root_url = APP.config['APP_URL'] + if root_url.endswith('/'): + root_url = root_url[:-1] + return flask.redirect( + root_url + flask.url_for('.view_docs', repo=repo, username=username)) + + +@APP.route('//issues/') +@APP.route('/fork///issues/') +def view_issues(repo, username=None): + root_url = APP.config['APP_URL'] + if root_url.endswith('/'): + root_url = root_url[:-1] + return flask.redirect( + root_url + + flask.url_for('.view_docs', repo=repo, username=username) + + 'issues/') + + +@APP.route('//commits/') +@APP.route('/fork///commits/') +def view_commits(repo, username=None): + root_url = APP.config['APP_URL'] + if root_url.endswith('/'): + root_url = root_url[:-1] + return flask.redirect( + root_url + + flask.url_for('.view_docs', repo=repo, username=username) + + 'commits/') + + +@APP.route('//tree/') +@APP.route('/fork///tree/') +def view_tree(repo, username=None): + root_url = APP.config['APP_URL'] + if root_url.endswith('/'): + root_url = root_url[:-1] + return flask.redirect( + root_url + + flask.url_for('.view_docs', repo=repo, username=username) + + 'tree/') + + +@APP.route('//tags/') +@APP.route('/fork///tags/') +def view_tags(repo, username=None): + root_url = APP.config['APP_URL'] + if root_url.endswith('/'): + root_url = root_url[:-1] + return flask.redirect( + root_url + + flask.url_for('.view_docs', repo=repo, username=username) + + 'tags/') + + +@APP.route('//pull-requests/') +@APP.route('/fork///pull-requests/') +def request_pulls(repo, username=None): + root_url = APP.config['APP_URL'] + if root_url.endswith('/'): + root_url = root_url[:-1] + return flask.redirect( + root_url + + flask.url_for('.view_docs', repo=repo, username=username) + + 'pull-requests/') + + +@APP.route('//forks/') +@APP.route('/fork///forks/') +def view_forks(repo, username=None): + root_url = APP.config['APP_URL'] + if root_url.endswith('/'): + root_url = root_url[:-1] + return flask.redirect( + root_url + + flask.url_for('.view_docs', repo=repo, username=username) + + 'forks/') + + +# The actual logic of the doc server @APP.route('//') @APP.route('/') From b05555d1367dc58964194546717ed6b96dd6da32 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:18 +0000 Subject: [PATCH 8/20] Fix the repo_master template to link properly to the docs server --- diff --git a/pagure/templates/repo_master.html b/pagure/templates/repo_master.html index ec64907..eb56af7 100644 --- a/pagure/templates/repo_master.html +++ b/pagure/templates/repo_master.html @@ -39,9 +39,9 @@ repo=repo.name) }}">Overview - {% if repo.settings.get('project_documentation', True) %} + {% if repo.settings.get('project_documentation', True) and config['DOC_APP_URL'] %}
  • - Docs
  • {% endif %} From 153c707f0d1519e5d6bf4f781c65f9add7c36409 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:19 +0000 Subject: [PATCH 9/20] Make the flask app not run as threaded, this is no longer needed --- diff --git a/runserver.py b/runserver.py index 270b1b2..33d6dec 100755 --- a/runserver.py +++ b/runserver.py @@ -14,4 +14,4 @@ if '--profile' in sys.argv: APP.config['PROFILE'] = True APP.wsgi_app = ProfilerMiddleware(APP.wsgi_app, restrictions=[30]) -APP.run(threaded=True) +APP.run() From f8556c5a0f51b51f3c8fe6b6579cc825210bcf53 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:19 +0000 Subject: [PATCH 10/20] Add a rundocserver script allowing to run the documentation server --- diff --git a/rundocserver.py b/rundocserver.py new file mode 100755 index 0000000..ad845f0 --- /dev/null +++ b/rundocserver.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python2 + +# These two lines are needed to run on EL6 +__requires__ = ['SQLAlchemy >= 0.8', 'jinja2 >= 2.4'] +import pkg_resources + +import sys +from werkzeug.contrib.profiler import ProfilerMiddleware + +from pagure import APP +from pagure.docs_server import APP +APP.debug = True + +if '--profile' in sys.argv: + APP.config['PROFILE'] = True + APP.wsgi_app = ProfilerMiddleware(APP.wsgi_app, restrictions=[30]) + +APP.run(port=5001) From 84571af9da3bc8727d67e02498e541f771db644c Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:19 +0000 Subject: [PATCH 11/20] Document DOC_APP_URL in the sample configuration file --- diff --git a/files/pagure.cfg.sample b/files/pagure.cfg.sample index 8d20c25..2f80884 100644 --- a/files/pagure.cfg.sample +++ b/files/pagure.cfg.sample @@ -30,6 +30,10 @@ SALT_EMAIL = '' ### The URL at which the project is available. APP_URL = 'https://pagure.io/' +### The URL at which the documentation of projects will be available +## This should be in a different domain to avoid XSS issues since we want +## to allow raw html to be displayed. +DOC_APP_URL = 'https://docs.pagure.org' ### The URL to use to clone git repositories. GIT_URL_SSH = 'git@pagure.io' From f5f7aa5395faaab21b9f6996c463ddf94b6c53eb Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:19 +0000 Subject: [PATCH 12/20] Add sample apache and wsgi configuration files for the pagure docs server --- diff --git a/files/doc_pagure.wsgi b/files/doc_pagure.wsgi new file mode 100644 index 0000000..b63b58a --- /dev/null +++ b/files/doc_pagure.wsgi @@ -0,0 +1,23 @@ +#-*- coding: utf-8 -*- + +# The three lines below are required to run on EL6 as EL6 has +# two possible version of python-sqlalchemy and python-jinja2 +# These lines make sure the application uses the correct version. +#import __main__ +#__main__.__requires__ = ['SQLAlchemy >= 0.8', 'jinja2 >= 2.4'] +#import pkg_resources + +#import os +## Set the environment variable pointing to the configuration file +#os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg' + + +## The following is only needed if you did not install pagure +## as a python module (for example if you run it from a git clone). +#import sys +#sys.path.insert(0, '/path/to/pagure/') + + +## The most import line to make the wsgi working +#from pagure.docs_server import APP as application +#application.debug = True diff --git a/files/pagure.conf b/files/pagure.conf index 9e17633..e713c75 100644 --- a/files/pagure.conf +++ b/files/pagure.conf @@ -22,3 +22,28 @@ # # + +# + #WSGIDaemonProcess pagure user=git group=git maximum-requests=50000 display-name=pagure processes=8 threads=4 inactivity-timeout=300 + + #WSGISocketPrefix run/wsgi + #WSGIRestrictStdout On + #WSGIRestrictSignal Off + #WSGIPythonOptimize 1 + + #WSGIScriptAlias / /usr/share/pagure/doc_pagure.wsgi + + # + # WSGIProcessGroup pagure + # + # # Apache 2.4 + # Require all granted + # + # + # # Apache 2.2 + # Order deny,allow + # Allow from all + # + # + +# From 37991ea2f0338df288d143b57bc6e50994ee41ae Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:19 +0000 Subject: [PATCH 13/20] Fix the redirect to pagure from the docs server --- diff --git a/pagure/docs_server.py b/pagure/docs_server.py index 7f6b180..14096cc 100644 --- a/pagure/docs_server.py +++ b/pagure/docs_server.py @@ -153,7 +153,7 @@ def view_issues(repo, username=None): return flask.redirect( root_url + flask.url_for('.view_docs', repo=repo, username=username) - + 'issues/') + + '/issues/') @APP.route('//commits/') @@ -165,7 +165,7 @@ def view_commits(repo, username=None): return flask.redirect( root_url + flask.url_for('.view_docs', repo=repo, username=username) - + 'commits/') + + '/commits/') @APP.route('//tree/') @@ -177,7 +177,7 @@ def view_tree(repo, username=None): return flask.redirect( root_url + flask.url_for('.view_docs', repo=repo, username=username) - + 'tree/') + + '/tree/') @APP.route('//tags/') @@ -189,7 +189,7 @@ def view_tags(repo, username=None): return flask.redirect( root_url + flask.url_for('.view_docs', repo=repo, username=username) - + 'tags/') + + '/tags/') @APP.route('//pull-requests/') @@ -201,7 +201,7 @@ def request_pulls(repo, username=None): return flask.redirect( root_url + flask.url_for('.view_docs', repo=repo, username=username) - + 'pull-requests/') + + '/pull-requests/') @APP.route('//forks/') @@ -213,7 +213,7 @@ def view_forks(repo, username=None): return flask.redirect( root_url + flask.url_for('.view_docs', repo=repo, username=username) - + 'forks/') + + '/forks/') # The actual logic of the doc server From bf2dfd7eec8cba0f0fad76e8e4d33604a2a4f906 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Jul 01 2015 07:29:19 +0000 Subject: [PATCH 14/20] Fix unit-tests --- diff --git a/pagure/templates/docs.html b/pagure/templates/docs.html index 731c9ec..67769df 100644 --- a/pagure/templates/docs.html +++ b/pagure/templates/docs.html @@ -54,15 +54,15 @@