Blob Blame Raw
# -*- coding: utf-8 -*-

"""
 (c) 2014-2017 - Copyright Red Hat Inc

 Authors:
   Pierre-Yves Chibon <pingou@pingoured.fr>

"""

# pylint: disable=too-many-return-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-arguments
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
# pylint: disable=too-many-lines


from __future__ import unicode_literals

import logging
import os
from math import ceil

import flask
import pygit2
from sqlalchemy.exc import SQLAlchemyError

import pagure
import pagure.doc_utils
import pagure.exceptions
import pagure.lib
import pagure.lib.git
import pagure.lib.tasks
import pagure.forms
from pagure.config import config as pagure_config
from pagure.ui import UI_NS
from pagure.utils import (
    login_required, __get_file_in_tree, get_parent_repo_path)


_log = logging.getLogger(__name__)


def _get_parent_request_repo_path(repo):
    """ Return the path of the parent git repository corresponding to the
    provided Repository object from the DB.
    """
    if repo.parent:
        parentpath = os.path.join(
            pagure_config['REQUESTS_FOLDER'], repo.parent.path)
    else:
        parentpath = os.path.join(
            pagure_config['REQUESTS_FOLDER'], repo.path)

    return parentpath


@UI_NS.route('/<repo>/pull-requests/')
@UI_NS.route('/<repo>/pull-requests')
@UI_NS.route('/<namespace>/<repo>/pull-requests/')
@UI_NS.route('/<namespace>/<repo>/pull-requests')
@UI_NS.route('/fork/<username>/<repo>/pull-requests/')
@UI_NS.route('/fork/<username>/<repo>/pull-requests')
@UI_NS.route('/fork/<username>/<namespace>/<repo>/pull-requests/')
@UI_NS.route('/fork/<username>/<namespace>/<repo>/pull-requests')
def request_pulls(repo, username=None, namespace=None):
    """ Create a pull request with the changes from the fork into the project.
    """
    status = flask.request.args.get('status', 'Open')
    assignee = flask.request.args.get('assignee', None)
    author = flask.request.args.get('author', None)

    repo = flask.g.repo

    if not repo.settings.get('pull_requests', True):
        flask.abort(404, 'No pull-requests found for this project')

    if str(status).lower() in ['false', '0']:
        status = False
    elif str(status).lower() in ['all']:
        status = None

    if str(status).lower() in ['true', '1', 'open']:
        requests = pagure.lib.search_pull_requests(
            flask.g.session,
            project_id=repo.id,
            status=True,
            assignee=assignee,
            author=author,
            offset=flask.g.offset,
            limit=flask.g.limit)
        requests_cnt = pagure.lib.search_pull_requests(
            flask.g.session,
            project_id=repo.id,
            status=True,
            assignee=assignee,
            author=author,
            count=True)
        oth_requests = pagure.lib.search_pull_requests(
            flask.g.session,
            project_id=repo.id,
            status=False,
            assignee=assignee,
            author=author,
            count=True)
    else:
        requests = pagure.lib.search_pull_requests(
            flask.g.session,
            project_id=repo.id,
            assignee=assignee,
            author=author,
            status=status,
            offset=flask.g.offset,
            limit=flask.g.limit)
        requests_cnt = pagure.lib.search_pull_requests(
            flask.g.session,
            project_id=repo.id,
            assignee=assignee,
            author=author,
            status=status,
            count=True)
        oth_requests = pagure.lib.search_pull_requests(
            flask.g.session,
            project_id=repo.id,
            status=True,
            assignee=assignee,
            author=author,
            count=True)

    repo_obj = flask.g.repo_obj
    if not repo_obj.is_empty and not repo_obj.head_is_unborn:
        head = repo_obj.head.shorthand
    else:
        head = 'master'

    total_page = 1
    if requests_cnt:
        total_page = int(ceil(requests_cnt / float(flask.g.limit)))

    return flask.render_template(
        'requests.html',
        select='requests',
        repo=repo,
        username=username,
        requests=requests,
        requests_cnt=requests_cnt,
        oth_requests=oth_requests,
        status=status,
        assignee=assignee,
        author=author,
        form=pagure.forms.ConfirmationForm(),
        head=head,
        total_page=total_page,
    )


@UI_NS.route('/<repo>/pull-request/<int:requestid>/')
@UI_NS.route('/<repo>/pull-request/<int:requestid>')
@UI_NS.route('/<namespace>/<repo>/pull-request/<int:requestid>/')
@UI_NS.route('/<namespace>/<repo>/pull-request/<int:requestid>')
@UI_NS.route('/fork/<username>/<repo>/pull-request/<int:requestid>/')
@UI_NS.route('/fork/<username>/<repo>/pull-request/<int:requestid>')
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/')
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>')
def request_pull(repo, requestid, username=None, namespace=None):
    """ Create a pull request with the changes from the fork into the project.
    """
    repo = flask.g.repo

    _log.info('Viewing pull Request #%s repo: %s', requestid, repo.fullname)

    if not repo.settings.get('pull_requests', True):
        flask.abort(404, 'No pull-requests found for this project')

    request = pagure.lib.search_pull_requests(
        flask.g.session, project_id=repo.id, requestid=requestid)

    if not request:
        flask.abort(404, 'Pull-request not found')

    if request.remote:
        repopath = pagure.utils.get_remote_repo_path(
            request.remote_git, request.branch_from)
        parentpath = pagure.utils.get_repo_path(request.project)
    else:
        repo_from = request.project_from
        parentpath = pagure.utils.get_repo_path(request.project)
        repopath = parentpath
        if repo_from:
            repopath = pagure.utils.get_repo_path(repo_from)

    repo_obj = pygit2.Repository(repopath)
    orig_repo = pygit2.Repository(parentpath)

    diff_commits = []
    diff = None
    # Closed pull-request
    if request.status != 'Open':
        commitid = request.commit_stop
        try:
            for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_TIME):
                diff_commits.append(commit)
                if commit.oid.hex == request.commit_start:
                    break
        except KeyError:
            # This happens when repo.walk() cannot find commitid
            pass

        if diff_commits:
            diff = repo_obj.diff(
                repo_obj.revparse_single(diff_commits[-1].parents[0].oid.hex),
                repo_obj.revparse_single(diff_commits[0].oid.hex)
            )
    else:
        try:
            diff_commits, diff = pagure.lib.git.diff_pull_request(
                flask.g.session, request, repo_obj, orig_repo,
                requestfolder=pagure_config['REQUESTS_FOLDER'])
        except pagure.exceptions.PagureException as err:
            flask.flash('%s' % err, 'error')
            return flask.redirect(flask.url_for(
                'ui_ns.view_repo', username=username, repo=repo.name,
                namespace=namespace))
        except SQLAlchemyError as err:  # pragma: no cover
            flask.g.session.rollback()
            _log.exception(err)
            flask.flash(
                'Could not update this pull-request in the database',
                'error')

    if diff:
        diff.find_similar()

    form = pagure.forms.MergePRForm()

    can_delete_branch = (
        pagure_config.get('ALLOW_DELETE_BRANCH', True)
        and not request.remote_git
        and pagure.utils.is_repo_committer(request.project_from)
    )
    return flask.render_template(
        'pull_request.html',
        select='requests',
        requestid=requestid,
        repo=repo,
        username=username,
        repo_obj=repo_obj,
        pull_request=request,
        diff_commits=diff_commits,
        diff=diff,
        mergeform=form,
        subscribers=pagure.lib.get_watch_list(flask.g.session, request),
        tag_list=pagure.lib.get_tags_of_project(flask.g.session, repo),
        can_delete_branch=can_delete_branch,
    )


@UI_NS.route('/<repo>/pull-request/<int:requestid>.patch')
@UI_NS.route('/<namespace>/<repo>/pull-request/<int:requestid>.patch')
@UI_NS.route('/fork/<username>/<repo>/pull-request/<int:requestid>.patch')
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>.patch')
def request_pull_patch(repo, requestid, username=None, namespace=None):
    """ Returns the commits from the specified pull-request as patches.
    """
    return request_pull_to_diff_or_patch(
        repo, requestid, username, namespace, diff=False)


@UI_NS.route('/<repo>/pull-request/<int:requestid>.diff')
@UI_NS.route('/<namespace>/<repo>/pull-request/<int:requestid>.diff')
@UI_NS.route('/fork/<username>/<repo>/pull-request/<int:requestid>.diff')
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>.diff')
def request_pull_diff(repo, requestid, username=None, namespace=None):
    """ Returns the commits from the specified pull-request as patches.
    """
    return request_pull_to_diff_or_patch(
        repo, requestid, username, namespace, diff=True)


def request_pull_to_diff_or_patch(
        repo, requestid, username=None, namespace=None, diff=False):
    """ Returns the commits from the specified pull-request as patches.

    :arg repo: the `pagure.lib.model.Project` object of the current pagure
        project browsed
    :type repo: `pagure.lib.model.Project`
    :arg requestid: the identifier of the pull-request to convert to patch
        or diff
    :type requestid: int
    :kwarg username: the username of the user who forked then project when
        the project viewed is a fork
    :type username: str or None
    :kwarg namespace: the namespace of the project if it has one
    :type namespace: str or None
    :kwarg diff: a boolean whether the data returned is a patch or a diff
    :type diff: boolean
    :return: the patch or diff representation of the specified pull-request
    :rtype: str

    """
    repo = flask.g.repo

    if not repo.settings.get('pull_requests', True):
        flask.abort(404, 'No pull-requests found for this project')

    request = pagure.lib.search_pull_requests(
        flask.g.session, project_id=repo.id, requestid=requestid)

    if not request:
        flask.abort(404, 'Pull-request not found')

    if request.remote:
        repopath = pagure.utils.get_remote_repo_path(
            request.remote_git, request.branch_from)
        parentpath = pagure.utils.get_repo_path(request.project)
    else:
        repo_from = request.project_from
        parentpath = pagure.utils.get_repo_path(request.project)
        repopath = parentpath
        if repo_from:
            repopath = pagure.utils.get_repo_path(repo_from)

    repo_obj = pygit2.Repository(repopath)
    orig_repo = pygit2.Repository(parentpath)

    branch = repo_obj.lookup_branch(request.branch_from)
    commitid = None
    if branch:
        commitid = branch.get_object().hex

    diff_commits = []
    if request.status != 'Open':
        commitid = request.commit_stop
        try:
            for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_TIME):
                diff_commits.append(commit)
                if commit.oid.hex == request.commit_start:
                    break
        except KeyError:
            # This happens when repo.walk() cannot find commitid
            pass
    else:
        try:
            diff_commits = pagure.lib.git.diff_pull_request(
                flask.g.session, request, repo_obj, orig_repo,
                requestfolder=pagure_config['REQUESTS_FOLDER'],
                with_diff=False)
        except pagure.exceptions.PagureException as err:
            flask.flash('%s' % err, 'error')
            return flask.redirect(flask.url_for(
                'ui_ns.view_repo', username=username, repo=repo.name,
                namespace=namespace))
        except SQLAlchemyError as err:  # pragma: no cover
            flask.g.session.rollback()
            _log.exception(err)
            flask.flash(
                'Could not update this pull-request in the database',
                'error')

    diff_commits.reverse()
    patch = pagure.lib.git.commit_to_patch(
        repo_obj, diff_commits, diff_view=diff)

    return flask.Response(patch, content_type="text/plain;charset=UTF-8")


@UI_NS.route(
    '/<repo>/pull-request/<int:requestid>/edit/', methods=('GET', 'POST'))
@UI_NS.route(
    '/<repo>/pull-request/<int:requestid>/edit', methods=('GET', 'POST'))
@UI_NS.route(
    '/<namespace>/<repo>/pull-request/<int:requestid>/edit/',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/<namespace>/<repo>/pull-request/<int:requestid>/edit',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<repo>/pull-request/<int:requestid>/edit/',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<repo>/pull-request/<int:requestid>/edit',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/edit/',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/edit',
    methods=('GET', 'POST'))
@login_required
def request_pull_edit(repo, requestid, username=None, namespace=None):
    """ Edit the title of a pull-request.
    """

    repo = flask.g.repo

    if not repo.settings.get('pull_requests', True):
        flask.abort(404, 'No pull-requests found for this project')

    request = pagure.lib.search_pull_requests(
        flask.g.session, project_id=repo.id, requestid=requestid)

    if not request:
        flask.abort(404, 'Pull-request not found')

    if request.status != 'Open':
        flask.abort(400, 'Pull-request is already closed')

    if not flask.g.repo_committer \
            and flask.g.fas_user.username != request.user.username:
        flask.abort(403, 'You are not allowed to edit this pull-request')

    form = pagure.forms.RequestPullForm()
    if form.validate_on_submit():
        request.title = form.title.data.strip()
        request.initial_comment = form.initial_comment.data.strip()
        flask.g.session.add(request)
        try:
            flask.g.session.commit()
            flask.flash('Pull request edited!')
        except SQLAlchemyError as err:  # pragma: no cover
            flask.g.session.rollback()
            _log.exception(err)
            flask.flash(
                'Could not edit this pull-request in the database',
                'error')
        return flask.redirect(flask.url_for(
            'ui_ns.request_pull', username=username, namespace=namespace,
            repo=repo.name, requestid=requestid))
    elif flask.request.method == 'GET':
        form.title.data = request.title
        form.initial_comment.data = request.initial_comment

    return flask.render_template(
        'pull_request_title.html',
        select='requests',
        request=request,
        repo=repo,
        username=username,
        form=form,
    )


@UI_NS.route(
    '/<repo>/pull-request/<int:requestid>/comment',
    methods=['POST'])
@UI_NS.route(
    '/<repo>/pull-request/<int:requestid>/comment/<commit>/'
    '<path:filename>/<row>', methods=('GET', 'POST'))
@UI_NS.route(
    '/<namespace>/<repo>/pull-request/<int:requestid>/comment',
    methods=['POST'])
@UI_NS.route(
    '/<namespace>/<repo>/pull-request/<int:requestid>/comment/<commit>/'
    '<path:filename>/<row>', methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<repo>/pull-request/<int:requestid>/comment',
    methods=['POST'])
@UI_NS.route(
    '/fork/<username>/<repo>/pull-request/<int:requestid>/comment/'
    '<commit>/<path:filename>/<row>', methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/'
    'comment', methods=['POST'])
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/'
    'comment/<commit>/<path:filename>/<row>', methods=('GET', 'POST'))
@login_required
def pull_request_add_comment(
        repo, requestid, commit=None,
        filename=None, row=None, username=None, namespace=None):
    """ Add a comment to a commit in a pull-request.
    """
    repo = flask.g.repo

    if not repo.settings.get('pull_requests', True):
        flask.abort(404, 'No pull-requests found for this project')

    request = pagure.lib.search_pull_requests(
        flask.g.session, project_id=repo.id, requestid=requestid)

    if not request:
        flask.abort(404, 'Pull-request not found')

    is_js = flask.request.args.get('js', False)
    tree_id = flask.request.args.get('tree_id') or None

    form = pagure.forms.AddPullRequestCommentForm()
    form.commit.data = commit
    form.filename.data = filename
    form.requestid.data = requestid
    form.row.data = row
    form.tree_id.data = tree_id

    if form.validate_on_submit():
        comment = form.comment.data

        try:
            message = pagure.lib.add_pull_request_comment(
                flask.g.session,
                request=request,
                commit=commit,
                tree_id=tree_id,
                filename=filename,
                row=row,
                comment=comment,
                user=flask.g.fas_user.username,
                requestfolder=pagure_config['REQUESTS_FOLDER'],
                trigger_ci=pagure_config['TRIGGER_CI'],
            )
            flask.g.session.commit()
            if not is_js:
                flask.flash(message)
        except SQLAlchemyError as err:  # pragma: no cover
            flask.g.session.rollback()
            _log.exception(err)
            if is_js:
                return 'error'
            else:
                flask.flash(str(err), 'error')

        if is_js:
            return 'ok'
        return flask.redirect(flask.url_for(
            'ui_ns.request_pull', username=username, namespace=namespace,
            repo=repo.name, requestid=requestid))

    if is_js and flask.request.method == 'POST':
        return 'failed'

    return flask.render_template(
        'pull_request_comment.html',
        select='requests',
        requestid=requestid,
        repo=repo,
        username=username,
        commit=commit,
        tree_id=tree_id,
        filename=filename,
        row=row,
        form=form,
    )


@UI_NS.route(
    '/<repo>/pull-request/<int:requestid>/comment/drop',
    methods=['POST'])
@UI_NS.route(
    '/<namespace>/<repo>/pull-request/<int:requestid>/comment/drop',
    methods=['POST'])
@UI_NS.route(
    '/fork/<username>/<repo>/pull-request/<int:requestid>/comment/drop',
    methods=['POST'])
@UI_NS.route(
    '/fork/<namespace>/<username>/<repo>/pull-request/<int:requestid>/'
    'comment/drop', methods=['POST'])
@login_required
def pull_request_drop_comment(
        repo, requestid, username=None, namespace=None):
    """ Delete a comment of a pull-request.
    """
    repo = flask.g.repo

    if not repo:
        flask.abort(404, 'Project not found')

    if not repo.settings.get('pull_requests', True):
        flask.abort(404, 'No pull-requests found for this project')

    request = pagure.lib.search_pull_requests(
        flask.g.session, project_id=repo.id, requestid=requestid)

    if not request:
        flask.abort(404, 'Pull-request not found')

    if flask.request.form.get('edit_comment'):
        commentid = flask.request.form.get('edit_comment')
        form = pagure.forms.EditCommentForm()
        if form.validate_on_submit():
            return pull_request_edit_comment(
                repo.name, requestid, commentid, username=username)

    form = pagure.forms.ConfirmationForm()
    if form.validate_on_submit():

        if flask.request.form.get('drop_comment'):
            commentid = flask.request.form.get('drop_comment')

            comment = pagure.lib.get_request_comment(
                flask.g.session, request.uid, commentid)
            if comment is None or comment.pull_request.project != repo:
                flask.abort(404, 'Comment not found')

            if (flask.g.fas_user.username != comment.user.username
                    or comment.parent.status is False) \
                    and not flask.g.repo_committer:
                flask.abort(
                    403,
                    'You are not allowed to remove this comment from '
                    'this issue')

            flask.g.session.delete(comment)
            try:
                flask.g.session.commit()
                flask.flash('Comment removed')
            except SQLAlchemyError as err:  # pragma: no cover
                flask.g.session.rollback()
                _log.error(err)
                flask.flash(
                    'Could not remove the comment: %s' % commentid, 'error')

    return flask.redirect(flask.url_for(
        'ui_ns.request_pull', username=username, namespace=namespace,
        repo=repo.name, requestid=requestid))


@UI_NS.route(
    '/<repo>/pull-request/<int:requestid>/comment/<int:commentid>/edit',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/<namespace>/<repo>/pull-request/<int:requestid>/comment/'
    '<int:commentid>/edit', methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<repo>/pull-request/<int:requestid>/comment'
    '/<int:commentid>/edit', methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/'
    '<int:requestid>/comment/<int:commentid>/edit',
    methods=('GET', 'POST'))
@login_required
def pull_request_edit_comment(
        repo, requestid, commentid, username=None, namespace=None):
    """Edit comment of a pull request
    """
    is_js = flask.request.args.get('js', False)

    project = flask.g.repo

    if not project.settings.get('pull_requests', True):
        flask.abort(404, 'No pull-requests found for this project')

    request = pagure.lib.search_pull_requests(
        flask.g.session, project_id=project.id, requestid=requestid)

    if not request:
        flask.abort(404, 'Pull-request not found')

    comment = pagure.lib.get_request_comment(
        flask.g.session, request.uid, commentid)

    if comment is None or comment.parent.project != project:
        flask.abort(404, 'Comment not found')

    if (flask.g.fas_user.username != comment.user.username
            or comment.parent.status != 'Open') \
            and not flask.g.repo_committer:
        flask.abort(403, 'You are not allowed to edit the comment')

    form = pagure.forms.EditCommentForm()

    if form.validate_on_submit():

        updated_comment = form.update_comment.data
        try:
            message = pagure.lib.edit_comment(
                flask.g.session,
                parent=request,
                comment=comment,
                user=flask.g.fas_user.username,
                updated_comment=updated_comment,
                folder=pagure_config['REQUESTS_FOLDER'],
            )
            flask.g.session.commit()
            if not is_js:
                flask.flash(message)
        except SQLAlchemyError as err:  # pragma: no cover
            flask.g.session.rollback()
            _log.error(err)
            if is_js:
                return 'error'
            else:
                flask.flash(
                    'Could not edit the comment: %s' % commentid, 'error')

        if is_js:
            return 'ok'
        return flask.redirect(flask.url_for(
            'ui_ns.request_pull', username=username, namespace=namespace,
            repo=project.name, requestid=requestid))

    if is_js and flask.request.method == 'POST':
        return 'failed'

    return flask.render_template(
        'comment_update.html',
        select='requests',
        requestid=requestid,
        repo=project,
        username=username,
        form=form,
        comment=comment,
        is_js=is_js,
    )


@UI_NS.route(
    '/<repo>/pull-request/<int:requestid>/merge', methods=['POST'])
@UI_NS.route(
    '/<namespace>/<repo>/pull-request/<int:requestid>/merge',
    methods=['POST'])
@UI_NS.route(
    '/fork/<username>/<repo>/pull-request/<int:requestid>/merge',
    methods=['POST'])
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/merge',
    methods=['POST'])
@login_required
def merge_request_pull(repo, requestid, username=None, namespace=None):
    """ Create a pull request with the changes from the fork into the project.
    """

    form = pagure.forms.MergePRForm()
    if not form.validate_on_submit():
        flask.flash('Invalid input submitted', 'error')
        return flask.redirect(flask.url_for(
            'ui_ns.request_pull', repo=repo, requestid=requestid,
            username=username, namespace=namespace))

    repo = flask.g.repo

    _log.info(
        'called merge_request_pull for repo: %s - requestid: %s',
        repo.fullname, requestid)

    if not repo.settings.get('pull_requests', True):
        flask.abort(404, 'No pull-requests found for this project')

    request = pagure.lib.search_pull_requests(
        flask.g.session, project_id=repo.id, requestid=requestid)

    if not request:
        flask.abort(404, 'Pull-request not found')

    if not flask.g.repo_committer:
        flask.abort(
            403,
            'You are not allowed to merge pull-request for this project')

    if repo.settings.get('Only_assignee_can_merge_pull-request', False):
        if not request.assignee:
            flask.flash(
                'This request must be assigned to be merged', 'error')
            return flask.redirect(flask.url_for(
                'ui_ns.request_pull', username=username, namespace=namespace,
                repo=repo.name, requestid=requestid))
        if request.assignee.username != flask.g.fas_user.username:
            flask.flash('Only the assignee can merge this review', 'error')
            return flask.redirect(flask.url_for(
                'ui_ns.request_pull', username=username, namespace=namespace,
                repo=repo.name, requestid=requestid))

    threshold = repo.settings.get('Minimum_score_to_merge_pull-request', -1)
    if threshold > 0 and int(request.score) < int(threshold):
        flask.flash(
            'This request does not have the minimum review score necessary '
            'to be merged', 'error')
        return flask.redirect(flask.url_for(
            'ui_ns.request_pull', username=username, namespace=namespace,
            repo=repo.name, requestid=requestid))

    if form.delete_branch.data:
        if not pagure_config.get('ALLOW_DELETE_BRANCH', True):
            flask.flash(
                'This pagure instance does not allow branch deletion', 'error')
            return flask.redirect(flask.url_for(
                'ui_ns.request_pull', username=username, namespace=namespace,
                repo=repo.name, requestid=requestid))
        if not pagure.utils.is_repo_committer(request.project_from):
            flask.flash(
                'You do not have permissions to delete the branch in the '
                'source repo', 'error')
            return flask.redirect(flask.url_for(
                'ui_ns.request_pull', username=username, namespace=namespace,
                repo=repo.name, requestid=requestid))
        if request.remote_git:
            flask.flash(
                'You can not delete branch in remote repo', 'error')
            return flask.redirect(flask.url_for(
                'ui_ns.request_pull', username=username, namespace=namespace,
                repo=repo.name, requestid=requestid))

    _log.info('All checks in the controller passed')

    try:
        task = pagure.lib.tasks.merge_pull_request.delay(
            repo.name, namespace, username, requestid,
            flask.g.fas_user.username,
            delete_branch_after=form.delete_branch.data)
        return pagure.utils.wait_for_task(
            task,
            prev=flask.url_for('ui_ns.request_pull',
                               repo=repo.name,
                               namespace=namespace,
                               username=username,
                               requestid=requestid))
    except pygit2.GitError as err:
        _log.info('GitError exception raised')
        flask.flash('%s' % err, 'error')
        return flask.redirect(flask.url_for(
            'ui_ns.request_pull', repo=repo.name, requestid=requestid,
            username=username, namespace=namespace))
    except pagure.exceptions.PagureException as err:
        _log.info('PagureException exception raised')
        flask.flash(str(err), 'error')
        return flask.redirect(flask.url_for(
            'ui_ns.request_pull', repo=repo.name, requestid=requestid,
            username=username, namespace=namespace))

    _log.info('All fine, returning')
    return flask.redirect(flask.url_for(
        'ui_ns.view_repo', repo=repo.name, username=username,
        namespace=namespace))


@UI_NS.route(
    '/<repo>/pull-request/cancel/<int:requestid>', methods=['POST'])
@UI_NS.route(
    '/<namespace>/<repo>/pull-request/cancel/<int:requestid>',
    methods=['POST'])
@UI_NS.route(
    '/fork/<username>/<repo>/pull-request/cancel/<int:requestid>',
    methods=['POST'])
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/cancel/<int:requestid>',
    methods=['POST'])
@login_required
def cancel_request_pull(repo, requestid, username=None, namespace=None):
    """ Cancel a pull request.
    """

    form = pagure.forms.ConfirmationForm()
    if form.validate_on_submit():

        if not flask.g.repo.settings.get('pull_requests', True):
            flask.abort(404, 'No pull-requests found for this project')

        request = pagure.lib.search_pull_requests(
            flask.g.session, project_id=flask.g.repo.id, requestid=requestid)

        if not request:
            flask.abort(404, 'Pull-request not found')

        if not flask.g.repo_committer \
                and not flask.g.fas_user.username == request.user.username:
            flask.abort(
                403,
                'You are not allowed to cancel pull-request for this project')

        pagure.lib.close_pull_request(
            flask.g.session, request, flask.g.fas_user.username,
            requestfolder=pagure_config['REQUESTS_FOLDER'],
            merged=False)
        try:
            flask.g.session.commit()
            flask.flash('Pull request canceled!')
        except SQLAlchemyError as err:  # pragma: no cover
            flask.g.session.rollback()
            _log.exception(err)
            flask.flash(
                'Could not update this pull-request in the database',
                'error')

    else:
        flask.flash('Invalid input submitted', 'error')

    return flask.redirect(flask.url_for(
        'ui_ns.view_repo', repo=repo, username=username, namespace=namespace))


@UI_NS.route(
    '/<repo>/pull-request/refresh/<int:requestid>', methods=['POST'])
@UI_NS.route(
    '/<namespace>/<repo>/pull-request/refresh/<int:requestid>',
    methods=['POST'])
@UI_NS.route(
    '/fork/<username>/<repo>/pull-request/refresh/<int:requestid>',
    methods=['POST'])
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/refresh/<int:requestid>',
    methods=['POST'])
@login_required
def refresh_request_pull(repo, requestid, username=None, namespace=None):
    """ Refresh a remote pull request.
    """

    form = pagure.forms.ConfirmationForm()
    if form.validate_on_submit():

        if not flask.g.repo.settings.get('pull_requests', True):
            flask.abort(404, 'No pull-requests found for this project')

        request = pagure.lib.search_pull_requests(
            flask.g.session, project_id=flask.g.repo.id, requestid=requestid)

        if not request:
            flask.abort(404, 'Pull-request not found')

        if not flask.g.repo_committer \
                and not flask.g.fas_user.username == request.user.username:
            flask.abort(
                403,
                'You are not allowed to refresh this pull request')

        task = pagure.lib.tasks.refresh_remote_pr.delay(
            flask.g.repo.name, namespace, username, requestid)
        return pagure.utils.wait_for_task(
            task,
            prev=flask.url_for('ui_ns.request_pull',
                               repo=flask.g.repo.name,
                               namespace=namespace,
                               username=username,
                               requestid=requestid))
    else:
        flask.flash('Invalid input submitted', 'error')

    return flask.redirect(flask.url_for(
        'ui_ns.request_pull', username=username, namespace=namespace,
        repo=flask.g.repo.name, requestid=requestid))


@UI_NS.route(
    '/<repo>/pull-request/<int:requestid>/update', methods=['POST'])
@UI_NS.route(
    '/<namespace>/<repo>/pull-request/<int:requestid>/update',
    methods=['POST'])
@UI_NS.route(
    '/fork/<username>/<repo>/pull-request/<int:requestid>/update',
    methods=['POST'])
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/update',
    methods=['POST'])
@login_required
def update_pull_requests(repo, requestid, username=None, namespace=None):
    ''' Update the metadata of a pull-request. '''
    repo = flask.g.repo

    if not repo.settings.get('pull_requests', True):
        flask.abort(404, 'No pull-request allowed on this project')

    request = pagure.lib.search_pull_requests(
        flask.g.session, project_id=repo.id, requestid=requestid)

    if not request:
        flask.abort(404, 'Pull-request not found')

    if request.status != 'Open':
        flask.abort(403, 'Pull-request closed')

    if not flask.g.repo_committer \
            and flask.g.fas_user.username != request.user.username:
        flask.abort(403, 'You are not allowed to update this pull-request')

    form = pagure.forms.ConfirmationForm()
    if form.validate_on_submit():
        tags = [
            tag.strip()
            for tag in flask.request.form.get('tag', '').strip().split(',')
            if tag.strip()]

        messages = set()
        try:
            # Adjust (add/remove) tags
            msgs = pagure.lib.update_tags(
                flask.g.session,
                obj=request,
                tags=tags,
                username=flask.g.fas_user.username,
                gitfolder=pagure_config['TICKETS_FOLDER'],
            )
            messages = messages.union(set(msgs))

            if flask.g.repo_committer:
                # Assign or update assignee of the ticket
                msg = pagure.lib.add_pull_request_assignee(
                    flask.g.session,
                    request=request,
                    assignee=flask.request.form.get(
                        'user', '').strip() or None,
                    user=flask.g.fas_user.username,
                    requestfolder=pagure_config['REQUESTS_FOLDER'],
                )
                if msg:
                    messages.add(msg)

            if messages:
                # Add the comment for field updates:
                not_needed = set(['Comment added', 'Updated comment'])
                pagure.lib.add_metadata_update_notif(
                    session=flask.g.session,
                    obj=request,
                    messages=messages - not_needed,
                    user=flask.g.fas_user.username,
                    gitfolder=pagure_config['REQUESTS_FOLDER']
                )
                messages.add('Metadata fields updated')

                flask.g.session.commit()
                for message in messages:
                    flask.flash(message)

        except pagure.exceptions.PagureException as err:
            flask.g.session.rollback()
            flask.flash('%s' % err, 'error')
        except SQLAlchemyError as err:  # pragma: no cover
            flask.g.session.rollback()
            _log.exception(err)
            flask.flash(str(err), 'error')

    return flask.redirect(flask.url_for(
        'ui_ns.request_pull', username=username, namespace=namespace,
        repo=repo.name, requestid=requestid))


# Specific actions


@UI_NS.route('/do_fork/<repo>', methods=['POST'])
@UI_NS.route('/do_fork/<namespace>/<repo>', methods=['POST'])
@UI_NS.route('/do_fork/fork/<username>/<repo>', methods=['POST'])
@UI_NS.route(
    '/do_fork/fork/<username>/<namespace>/<repo>', methods=['POST'])
@login_required
def fork_project(repo, username=None, namespace=None):
    """ Fork the project specified into the user's namespace
    """
    repo = flask.g.repo

    form = pagure.forms.ConfirmationForm()
    if not form.validate_on_submit():
        flask.abort(400)

    if pagure.lib._get_project(
            flask.g.session, repo.name, user=flask.g.fas_user.username,
            namespace=namespace,
            case=pagure_config.get('CASE_SENSITIVE', False)):
        return flask.redirect(flask.url_for(
            'ui_ns.view_repo',
            repo=repo.name,
            username=flask.g.fas_user.username,
            namespace=namespace))

    try:
        task = pagure.lib.fork_project(
            session=flask.g.session,
            repo=repo,
            gitfolder=pagure_config['GIT_FOLDER'],
            docfolder=pagure_config.get('DOCS_FOLDER'),
            ticketfolder=pagure_config.get('TICKETS_FOLDER'),
            requestfolder=pagure_config['REQUESTS_FOLDER'],
            user=flask.g.fas_user.username)

        flask.g.session.commit()
        return pagure.utils.wait_for_task(
            task,
            prev=flask.url_for(
                'ui_ns.view_repo', repo=repo.name,
                username=username, namespace=namespace,
                _external=True
            )
        )
    except pagure.exceptions.PagureException as err:
        flask.flash(str(err), 'error')
    except SQLAlchemyError as err:  # pragma: no cover
        flask.g.session.rollback()
        flask.flash(str(err), 'error')

    return flask.redirect(flask.url_for(
        'ui_ns.view_repo', repo=repo.name, username=username,
        namespace=namespace
    ))


@UI_NS.route(
    '/<repo>/diff/<path:branch_to>..<path:branch_from>/',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/<repo>/diff/<path:branch_to>..<path:branch_from>',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/<namespace>/<repo>/diff/<path:branch_to>..<path:branch_from>/',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/<namespace>/<repo>/diff/<path:branch_to>..<path:branch_from>',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<repo>/diff/<path:branch_to>..<path:branch_from>/',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<repo>/diff/<path:branch_to>..<path:branch_from>',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/diff/'
    '<path:branch_to>..<path:branch_from>/', methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/diff/'
    '<path:branch_to>..<path:branch_from>', methods=('GET', 'POST'))
def new_request_pull(
        repo, branch_to, branch_from, username=None, namespace=None):
    """ Create a pull request with the changes from the fork into the project.
    """
    branch_to = flask.request.values.get('branch_to', branch_to)
    project_to = flask.request.values.get('project_to')

    repo = flask.g.repo

    parent = repo
    if repo.parent:
        parent = repo.parent

    repo_obj = flask.g.repo_obj

    if not project_to:
        parentpath = get_parent_repo_path(repo)
        orig_repo = pygit2.Repository(parentpath)
    else:
        p_namespace = None
        p_username = None
        p_name = None
        project_to = project_to.rstrip('/')
        if project_to.startswith('fork/'):
            tmp = project_to.split('fork/')[1]
            p_username, left = tmp.split('/', 1)
        else:
            left = project_to

        if '/' in left:
            p_namespace, p_name = left.split('/', 1)
        else:
            p_name = left
        parent = pagure.lib.get_authorized_project(
            flask.g.session,
            p_name,
            user=p_username,
            namespace=p_namespace
        )
        if parent:
            family = [
                p.url_path for p in
                pagure.lib.get_project_family(flask.g.session, repo)
            ]
            if parent.url_path not in family:
                flask.abort(
                    400,
                    '%s is not part of %s\'s family' % (
                        project_to, repo.url_path))
            orig_repo = pygit2.Repository(os.path.join(
                pagure_config['GIT_FOLDER'], parent.path))
        else:
            flask.abort(404, 'No project found for %s' % project_to)

    if not parent.settings.get('pull_requests', True):
        flask.abort(404, 'No pull-request allowed on this project')

    if parent.settings.get(
            'Enforce_signed-off_commits_in_pull-request', False):
        flask.flash(
            'This project enforces the Signed-off-by statement on all '
            'commits')

    try:
        diff, diff_commits, orig_commit = pagure.lib.git.get_diff_info(
            repo_obj, orig_repo, branch_from, branch_to)
    except pagure.exceptions.PagureException as err:
        flask.abort(400, str(err))

    repo_committer = flask.g.repo_committer

    form = pagure.forms.RequestPullForm()
    if form.validate_on_submit() and repo_committer:
        try:
            if parent.settings.get(
                    'Enforce_signed-off_commits_in_pull-request', False):
                for commit in diff_commits:
                    if 'signed-off-by' not in commit.message.lower():
                        raise pagure.exceptions.PagureException(
                            'This repo enforces that all commits are '
                            'signed off by their author. ')

            if orig_commit:
                orig_commit = orig_commit.oid.hex

            initial_comment = form.initial_comment.data.strip() or None
            commit_start = commit_stop = None
            if diff_commits:
                commit_stop = diff_commits[0].oid.hex
                commit_start = diff_commits[-1].oid.hex
            request = pagure.lib.new_pull_request(
                flask.g.session,
                repo_to=parent,
                branch_to=branch_to,
                branch_from=branch_from,
                repo_from=repo,
                title=form.title.data,
                initial_comment=initial_comment,
                user=flask.g.fas_user.username,
                requestfolder=pagure_config['REQUESTS_FOLDER'],
                commit_start=commit_start,
                commit_stop=commit_stop,
            )

            try:
                flask.g.session.commit()
            except SQLAlchemyError as err:  # pragma: no cover
                flask.g.session.rollback()
                _log.exception(err)
                flask.flash(
                    'Could not register this pull-request in the database',
                    'error')

            if not parent.is_fork:
                url = flask.url_for(
                    'ui_ns.request_pull', requestid=request.id,
                    username=None, repo=parent.name, namespace=namespace)
            else:
                url = flask.url_for(
                    'ui_ns.request_pull', requestid=request.id,
                    username=parent.user.user, repo=parent.name,
                    namespace=namespace)

            return flask.redirect(url)
        except pagure.exceptions.PagureException as err:  # pragma: no cover
            # There could be a PagureException thrown if the flask.g.fas_user
            # wasn't in the DB but then it shouldn't be recognized as a
            # repo admin and thus, if we ever are here, we are in trouble.
            flask.flash(str(err), 'error')
        except SQLAlchemyError as err:  # pragma: no cover
            flask.g.session.rollback()
            flask.flash(str(err), 'error')

    if not flask.g.repo_committer:
        form = None

    # if the pull request we are creating only has one commit,
    # we automatically fill out the form fields for the PR with
    # the commit title and bodytext
    if len(diff_commits) == 1 and form:
        form.title.data = diff_commits[0].message.strip().split('\n')[0]
        form.initial_comment.data = diff_commits[0].message.partition('\n')[2]

    # Get the contributing templates from the requests git repo
    contributing = None
    requestrepopath = _get_parent_request_repo_path(repo)
    if os.path.exists(requestrepopath):
        requestrepo = pygit2.Repository(requestrepopath)
        if not requestrepo.is_empty and not requestrepo.head_is_unborn:
            commit = requestrepo[requestrepo.head.target]
            contributing = __get_file_in_tree(
                requestrepo, commit.tree, ['templates', 'contributing.md'],
                bail_on_tree=True)
            if contributing:
                contributing, _ = pagure.doc_utils.convert_readme(
                    contributing.data, 'md')

    flask.g.branches = sorted(orig_repo.listall_branches())

    return flask.render_template(
        'pull_request.html',
        select='requests',
        repo=repo,
        username=username,
        orig_repo=orig_repo,
        parent_branches=sorted(orig_repo.listall_branches()),
        diff_commits=diff_commits,
        diff=diff,
        form=form,
        branch_to=branch_to,
        branch_from=branch_from,
        contributing=contributing,
        parent=parent,
        project_to=project_to,
    )


@UI_NS.route('/<repo>/diff/remote/', methods=('GET', 'POST'))
@UI_NS.route('/<repo>/diff/remote', methods=('GET', 'POST'))
@UI_NS.route('/<namespace>/<repo>/diff/remote/', methods=('GET', 'POST'))
@UI_NS.route('/<namespace>/<repo>/diff/remote', methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<repo>/diff/remote/', methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<repo>/diff/remote', methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/diff/remote/',
    methods=('GET', 'POST'))
@UI_NS.route(
    '/fork/<username>/<namespace>/<repo>/diff/remote',
    methods=('GET', 'POST'))
@login_required
def new_remote_request_pull(repo, username=None, namespace=None):
    """ Create a pull request with the changes from a remote fork into the
        project.
    """
    confirm = flask.request.values.get('confirm', False)

    repo = flask.g.repo

    if not repo.settings.get('pull_requests', True):
        flask.abort(404, 'No pull-request allowed on this project')

    if repo.settings.get(
            'Enforce_signed-off_commits_in_pull-request', False):
        flask.flash(
            'This project enforces the Signed-off-by statement on all '
            'commits')

    orig_repo = flask.g.repo_obj

    form = pagure.forms.RemoteRequestPullForm()
    if form.validate_on_submit():
        taskid = flask.request.values.get('taskid')
        if taskid:
            result = pagure.lib.tasks.get_result(taskid)
            if not result.ready:
                return pagure.utils.wait_for_task_post(
                    taskid, form, 'ui_ns.new_remote_request_pull',
                    repo=repo.name, username=username, namespace=namespace)
            # Make sure to collect any exceptions resulting from the task
            try:
                result.get(timeout=0)
            except Exception as err:
                flask.abort(500, err)

        branch_from = form.branch_from.data.strip()
        branch_to = form.branch_to.data.strip()
        remote_git = form.git_repo.data.strip()

        repopath = pagure.utils.get_remote_repo_path(remote_git, branch_from)
        if not repopath:
            taskid = pagure.lib.tasks.pull_remote_repo.delay(
                remote_git, branch_from)
            return pagure.utils.wait_for_task_post(
                taskid, form, 'ui_ns.new_remote_request_pull',
                repo=repo.name, username=username, namespace=namespace,
                initial=True)

        repo_obj = pygit2.Repository(repopath)

        try:
            diff, diff_commits, orig_commit = pagure.lib.git.get_diff_info(
                repo_obj, orig_repo, branch_from, branch_to)
        except pagure.exceptions.PagureException as err:
            flask.flash('%s' % err, 'error')
            return flask.redirect(flask.url_for(
                'ui_ns.view_repo', username=username, repo=repo.name,
                namespace=namespace))

        if not confirm:
            flask.g.branches = sorted(orig_repo.listall_branches())
            return flask.render_template(
                'pull_request.html',
                select='requests',
                repo=repo,
                username=username,
                orig_repo=orig_repo,
                diff_commits=diff_commits,
                diff=diff,
                form=form,
                branch_to=branch_to,
                branch_from=branch_from,
                remote_git=remote_git,
                parent=repo,
            )

        try:
            if repo.settings.get(
                    'Enforce_signed-off_commits_in_pull-request', False):
                for commit in diff_commits:
                    if 'signed-off-by' not in commit.message.lower():
                        raise pagure.exceptions.PagureException(
                            'This repo enforces that all commits are '
                            'signed off by their author. ')

            if orig_commit:
                orig_commit = orig_commit.oid.hex

            parent = repo
            if repo.parent:
                parent = repo.parent

            request = pagure.lib.new_pull_request(
                flask.g.session,
                repo_to=parent,
                branch_to=branch_to,
                branch_from=branch_from,
                repo_from=None,
                remote_git=remote_git,
                title=form.title.data,
                user=flask.g.fas_user.username,
                requestfolder=pagure_config['REQUESTS_FOLDER'],
            )

            if form.initial_comment.data.strip() != '':
                pagure.lib.add_pull_request_comment(
                    flask.g.session,
                    request=request,
                    commit=None,
                    tree_id=None,
                    filename=None,
                    row=None,
                    comment=form.initial_comment.data.strip(),
                    user=flask.g.fas_user.username,
                    requestfolder=pagure_config['REQUESTS_FOLDER'],
                )

            try:
                flask.g.session.commit()
                flask.flash('Request created')
            except SQLAlchemyError as err:  # pragma: no cover
                flask.g.session.rollback()
                _log.exception(err)
                flask.flash(
                    'Could not register this pull-request in '
                    'the database', 'error')

            if not parent.is_fork:
                url = flask.url_for(
                    'ui_ns.request_pull', requestid=request.id,
                    username=None, repo=parent.name,
                    namespace=namespace)
            else:
                url = flask.url_for(
                    'ui_ns.request_pull', requestid=request.id,
                    username=parent.user, repo=parent.name,
                    namespace=namespace)

            return flask.redirect(url)
        except pagure.exceptions.PagureException as err:  # pragma: no cover
            # There could be a PagureException thrown if the
            # flask.g.fas_user wasn't in the DB but then it shouldn't
            # be recognized as a repo admin and thus, if we ever are
            # here, we are in trouble.
            flask.flash(str(err), 'error')
        except SQLAlchemyError as err:  # pragma: no cover
            flask.g.session.rollback()
            flask.flash(str(err), 'error')

    flask.g.branches = sorted(orig_repo.listall_branches())

    return flask.render_template(
        'remote_pull_request.html',
        select='requests',
        repo=repo,
        username=username,
        form=form,
        branch_to=orig_repo.head.shorthand,
    )


@UI_NS.route(
    '/fork_edit/<repo>/edit/<path:branchname>/f/<path:filename>',
    methods=['POST'])
@UI_NS.route(
    '/fork_edit/<namespace>/<repo>/edit/<path:branchname>/f/<path:filename>',
    methods=['POST'])
@UI_NS.route(
    '/fork_edit/fork/<username>/<repo>/edit/<path:branchname>/'
    'f/<path:filename>', methods=['POST'])
@UI_NS.route(
    '/fork_edit/fork/<username>/<namespace>/<repo>/edit/<path:branchname>/'
    'f/<path:filename>', methods=['POST'])
@login_required
def fork_edit_file(
        repo, branchname, filename, username=None, namespace=None):
    """ Fork the project specified and open the specific file to edit
    """
    repo = flask.g.repo

    form = pagure.forms.ConfirmationForm()
    if not form.validate_on_submit():
        flask.abort(400)

    if pagure.lib._get_project(
            flask.g.session, repo.name, user=flask.g.fas_user.username,
            case=pagure_config.get('CASE_SENSITIVE', False)):
        flask.flash('You had already forked this project')
        return flask.redirect(flask.url_for(
            'ui_ns.edit_file',
            username=flask.g.fas_user.username,
            namespace=namespace,
            repo=repo.name,
            branchname=branchname,
            filename=filename
        ))

    try:
        task = pagure.lib.fork_project(
            session=flask.g.session,
            repo=repo,
            gitfolder=pagure_config['GIT_FOLDER'],
            docfolder=pagure_config['DOCS_FOLDER'],
            ticketfolder=pagure_config['TICKETS_FOLDER'],
            requestfolder=pagure_config['REQUESTS_FOLDER'],
            user=flask.g.fas_user.username,
            editbranch=branchname,
            editfile=filename)

        flask.g.session.commit()
        return pagure.utils.wait_for_task(task)
    except pagure.exceptions.PagureException as err:
        flask.flash(str(err), 'error')
    except SQLAlchemyError as err:  # pragma: no cover
        flask.g.session.rollback()
        flask.flash(str(err), 'error')

    return flask.redirect(flask.url_for(
        'ui_ns.view_repo', repo=repo.name, username=username,
        namespace=namespace))