code:
# -*- coding: utf-8 -*-

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

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

"""

import flask
import os
import datetime

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.forms
from pagure import (APP, SESSION, LOG, login_required, is_repo_admin,
                    __get_file_in_tree)

# pylint: disable=E1101

def _get_parent_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(APP.config['GIT_FOLDER'], repo.parent.path)
    else:
        parentpath = os.path.join(APP.config['GIT_FOLDER'], repo.path)

    return parentpath

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(APP.config['REQUESTS_FOLDER'], repo.parent.path)
    else:
        parentpath = os.path.join(APP.config['REQUESTS_FOLDER'], repo.path)

    return parentpath

def _get_pr_info(repo_obj, orig_repo, branch_from, branch_to):
    ''' Return the info needed to see a diff or make a Pull-Request between
    the two specified repo.
    '''
    frombranch = repo_obj.lookup_branch(branch_from)
    if not frombranch and not repo_obj.is_empty:
        flask.abort(
            400,
            'Branch %s does not exist' % branch_from)

    branch = orig_repo.lookup_branch(branch_to)
    if not branch and not orig_repo.is_empty:
        flask.abort(
            400,
            'Branch %s could not be found in the target repo' % branch_to)

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

    diff_commits = []
    diff = None
    if not repo_obj.is_empty and not orig_repo.is_empty:
        orig_commit = orig_repo[
            orig_repo.lookup_branch(branch_to).get_object().hex]

        master_commits = [
            commit.oid.hex
            for commit in orig_repo.walk(
                orig_commit.oid.hex, pygit2.GIT_SORT_TIME)
        ]

        repo_commit = repo_obj[commitid]

        for commit in repo_obj.walk(
                repo_commit.oid.hex, pygit2.GIT_SORT_TIME):
            if commit.oid.hex in master_commits:
                break
            diff_commits.append(commit)

        if diff_commits:
            first_commit = repo_obj[diff_commits[-1].oid.hex]
            diff = repo_obj.diff(
                repo_obj.revparse_single(first_commit.parents[0].oid.hex),
                repo_obj.revparse_single(diff_commits[0].oid.hex)
            )

    elif orig_repo.is_empty and not repo_obj.is_empty:
        orig_commit = None
        if 'master' in repo_obj.listall_branches():
            repo_commit = repo_obj[repo_obj.head.target]
        else:
            branch = repo_obj.lookup_branch(branch_from)
            repo_commit = branch.get_object()

        for commit in repo_obj.walk(
                repo_commit.oid.hex, pygit2.GIT_SORT_TIME):
            diff_commits.append(commit)

        diff = repo_commit.tree.diff_to_tree(swap=True)
    else:
        raise pagure.exceptions.PagureException(
            'Fork is empty, there are no commits to request pulling'
        )

    return(diff, diff_commits, orig_commit)

@APP.route('/<repo:repo>/pull-requests/')
@APP.route('/<repo:repo>/pull-requests')
@APP.route('/fork/<username>/<repo:repo>/pull-requests/')
@APP.route('/fork/<username>/<repo:repo>/pull-requests')
def request_pulls(repo, username=None):
    """ Request pulling 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 = pagure.lib.get_project(SESSION, repo, user=username)

    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')

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

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

    reponame = pagure.get_repo_path(repo)
    repo_obj = pygit2.Repository(reponame)
    if not repo_obj.is_empty and not repo_obj.head_is_unborn:
        head = repo_obj.head.shorthand
    else:
        head = 'master'

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

@APP.route('/<repo:repo>/pull-request/<int:requestid>/')
@APP.route('/<repo:repo>/pull-request/<int:requestid>')
@APP.route('/fork/<username>/<repo:repo>/pull-request/<int:requestid>/')
@APP.route('/fork/<username>/<repo:repo>/pull-request/<int:requestid>')
def request_pull(repo, requestid, username=None):
    """ Request pulling the changes from the fork into the project.
    """

    repo = pagure.lib.get_project(SESSION, repo, user=username)

    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(
        SESSION, project_id=repo.id, requestid=requestid)

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

    if request.remote:
        repopath = pagure.get_remote_repo_path(
            request.remote_git, request.branch_from)
        parentpath = pagure.get_repo_path(request.project)
    else:
        repo_from = request.project_from
        repopath = pagure.get_repo_path(repo_from)
        parentpath = _get_parent_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(
                SESSION, request, repo_obj, orig_repo,
                requestfolder=APP.config['REQUESTS_FOLDER'])
        except pagure.exceptions.PagureException as err:
            flask.flash(err.message, 'error')
            return flask.redirect(flask.url_for(
                'view_repo', username=username, repo=repo.name))
        except SQLAlchemyError as err:  # pragma: no cover
            SESSION.rollback()
            APP.logger.exception(err)
            flask.flash(
                'Could not update this pull-request in the database',
                'error')

    if diff:
        diff.find_similar()

    form = pagure.forms.ConfirmationForm()

    return flask.render_template(
        'pull_request.html',
        select='requests',
        requestid=requestid,
        repo=repo,
        username=username,
        repo_obj=repo_obj,
        pull_request=request,
        repo_admin=is_repo_admin(request.project),
        diff_commits=diff_commits,
        diff=diff,
        mergeform=form,
    )

@APP.route('/<repo:repo>/pull-request/<int:requestid>.patch')
@APP.route('/fork/<username>/<repo:repo>/pull-request/<int:requestid>.patch')
def request_pull_patch(repo, requestid, username=None):
    """ Returns the commits from the specified pull-request as patches.
    """
    repo = pagure.lib.get_project(SESSION, repo, user=username)

    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(
        SESSION, project_id=repo.id, requestid=requestid)

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

    if request.remote:
        repopath = pagure.get_remote_repo_path(
            request.remote_git, request.branch_from)
        parentpath = pagure.get_repo_path(request.project)
    else:
        repo_from = request.project_from
        repopath = pagure.get_repo_path(repo_from)
        parentpath = _get_parent_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(
                SESSION, request, repo_obj, orig_repo,
                requestfolder=APP.config['REQUESTS_FOLDER'],
                with_diff=False)[0]
        except pagure.exceptions.PagureException as err:
            flask.flash(err.message, 'error')
            return flask.redirect(flask.url_for(
                'view_repo', username=username, repo=repo.name))
        except SQLAlchemyError as err:  # pragma: no cover
            SESSION.rollback()
            APP.logger.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)

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

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

    repo = pagure.lib.get_project(SESSION, repo, user=username)

    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(
        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 is_repo_admin(repo) \
            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()
        SESSION.add(request)
        try:
            SESSION.commit()
            flask.flash('Request pull edited!')
        except SQLAlchemyError as err:  # pragma: no cover
            SESSION.rollback()
            APP.logger.exception(err)
            flask.flash(
                'Could not edit this pull-request in the database',
                'error')
        return flask.redirect(flask.url_for(
            'request_pull', username=username,
            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,
    )

@APP.route('/<repo:repo>/pull-request/<int:requestid>/comment',
           methods=['POST'])
@APP.route('/<repo:repo>/pull-request/<int:requestid>/comment/<commit>/'
           '<path:filename>/<row>', methods=('GET', 'POST'))
@APP.route('/fork/<username>/<repo:repo>/pull-request/<int:requestid>/comment',
           methods=['POST'])
@APP.route('/fork/<username>/<repo: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):
    """ Add a comment to a commit in a pull-request.
    """
    repo = pagure.lib.get_project(SESSION, repo, user=username)

    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(
        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(
                SESSION,
                request=request,
                commit=commit,
                tree_id=tree_id,
                filename=filename,
                row=row,
                comment=comment,
                user=flask.g.fas_user.username,
                requestfolder=APP.config['REQUESTS_FOLDER'],
            )
            SESSION.commit()
            if not is_js:
                flask.flash(message)
        except SQLAlchemyError as err:  # pragma: no cover
            SESSION.rollback()
            APP.logger.exception(err)
            flask.flash(str(err), 'error')
            if is_js:
                return 'error'

        if is_js:
            return 'ok'
        return flask.redirect(flask.url_for(
            'request_pull', username=username,
            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,
    )

@APP.route('/<repo:repo>/pull-request/<int:requestid>/comment/drop',
           methods=['POST'])
@APP.route('/fork/<username>/<repo:repo>/pull-request/<int:requestid>/comment/drop',
           methods=['POST'])
@login_required
def pull_request_drop_comment(repo, requestid, username=None):
    """ Delete a comment of a pull-request.
    """
    repo = pagure.lib.get_project(SESSION, repo, user=username)

    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(
        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(
                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 is_repo_admin(repo):
                flask.abort(
                    403,
                    'You are not allowed to remove this comment from '
                    'this issue')

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

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

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

    project = pagure.lib.get_project(SESSION, repo, user=username)

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

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

    request = pagure.lib.search_pull_requests(
        SESSION, project_id=project.id, requestid=requestid)

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

    comment = pagure.lib.get_request_comment(
        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 is_repo_admin(project):
        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(
                SESSION,
                parent=request,
                comment=comment,
                user=flask.g.fas_user.username,
                updated_comment=updated_comment,
                folder=APP.config['REQUESTS_FOLDER'],
            )
            SESSION.commit()
            if not is_js:
                flask.flash(message)
        except SQLAlchemyError, err:  # pragma: no cover
            SESSION.rollback()
            LOG.error(err)
            if is_js:
                return 'error'
            flask.flash(
                'Could not edit the comment: %s' % commentid, 'error')

        if is_js:
            return 'ok'
        return flask.redirect(flask.url_for(
            'request_pull', username=username,
            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,
        repo_admin=is_repo_admin(project),
    )

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

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

    repo = pagure.lib.get_project(SESSION, repo, user=username)

    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(
        SESSION, project_id=repo.id, requestid=requestid)

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

    if not is_repo_admin(repo):
        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(
                'request_pull', username=username,
                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(
                'request_pull', username=username,
                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(
            'request_pull', username=username,
            repo=repo.name, requestid=requestid))

    try:
        message = pagure.lib.git.merge_pull_request(
            SESSION, request, flask.g.fas_user.username,
            APP.config['REQUESTS_FOLDER'])
        flask.flash(message)
    except pygit2.GitError as err:
        flask.flash(str(err.message), 'error')
        return flask.redirect(flask.url_for(
            'request_pull', repo=repo.name, requestid=requestid,
            username=username))
    except pagure.exceptions.PagureException as err:
        flask.flash(str(err), 'error')
        return flask.redirect(flask.url_for(
            'request_pull', repo=repo.name, requestid=requestid,
            username=username))

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

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

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

        repo_obj = pagure.lib.get_project(SESSION, repo, user=username)

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

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

        request = pagure.lib.search_pull_requests(
            SESSION, project_id=repo_obj.id, requestid=requestid)

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

        if not is_repo_admin(repo_obj) \
                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(
            SESSION, request, flask.g.fas_user.username,
            requestfolder=APP.config['REQUESTS_FOLDER'],
            merged=False)
        try:
            SESSION.commit()
            flask.flash('Request pull canceled!')
        except SQLAlchemyError as err:  # pragma: no cover
            SESSION.rollback()
            APP.logger.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('view_repo', repo=repo))

@APP.route('/<repo:repo>/pull-request/<int:requestid>/assign', methods=['POST'])
@APP.route('/fork/<username>/<repo:repo>/pull-request/<int:requestid>/assign',
           methods=['POST'])
@login_required
def set_assignee_requests(repo, requestid, username=None):
    ''' Assign a pull-request. '''
    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

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

    request = pagure.lib.search_pull_requests(
        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 is_repo_admin(repo):
        flask.abort(403, 'You are not allowed to assign this pull-request')

    form = pagure.forms.ConfirmationForm()
    if form.validate_on_submit():
        try:
            # Assign or update assignee of the ticket
            message = pagure.lib.add_pull_request_assignee(
                SESSION,
                request=request,
                assignee=flask.request.form.get('user', '').strip() or None,
                user=flask.g.fas_user.username,
                requestfolder=APP.config['REQUESTS_FOLDER'],)
            if message:
                SESSION.commit()
                flask.flash(message)
        except pagure.exceptions.PagureException as err:
            SESSION.rollback()
            flask.flash(err.message, 'error')
        except SQLAlchemyError as err:  # pragma: no cover
            SESSION.rollback()
            APP.logger.exception(err)
            flask.flash(str(err), 'error')

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

# Specific actions

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

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

    if repo is None:
        flask.abort(404)

    if pagure.lib.get_project(
            SESSION, repo.name, user=flask.g.fas_user.username):
        flask.flash('You had already forked this project')
        return flask.redirect(flask.url_for(
            'view_repo', repo=repo.name, username=flask.g.fas_user.username
        ))

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

        SESSION.commit()
        pagure.lib.git.generate_gitolite_acls()
        flask.flash(message)
        return flask.redirect(
            flask.url_for(
                'view_repo',
                username=flask.g.fas_user.username,
                repo=repo.name)
        )
    except pagure.exceptions.PagureException as err:
        flask.flash(str(err), 'error')
    except SQLAlchemyError as err:  # pragma: no cover
        SESSION.rollback()
        flask.flash(str(err), 'error')

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

@APP.route('/<repo:repo>/diff/<path:branch_to>..<path:branch_from>/',
           methods=('GET', 'POST'))
@APP.route('/<repo:repo>/diff/<path:branch_to>..<path:branch_from>',
           methods=('GET', 'POST'))
@APP.route(
    '/fork/<username>/<repo:repo>/diff/<path:branch_to>..<path:branch_from>/',
    methods=('GET', 'POST'))
@APP.route(
    '/fork/<username>/<repo:repo>/diff/<path:branch_to>..<path:branch_from>',
    methods=('GET', 'POST'))
def new_request_pull(repo, branch_to, branch_from, username=None):
    """ Request pulling the changes from the fork into the project.
    """
    repo = pagure.lib.get_project(SESSION, repo, user=username)
    branch_to = flask.request.values.get('branch_to', branch_to)

    if not repo:
        flask.abort(404)

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

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

    repopath = pagure.get_repo_path(repo)
    repo_obj = pygit2.Repository(repopath)

    parentpath = _get_parent_repo_path(repo)
    orig_repo = pygit2.Repository(parentpath)

    try:
        diff, diff_commits, orig_commit = _get_pr_info(
            repo_obj, orig_repo, branch_from, branch_to)
    except pagure.exceptions.PagureException as err:
        flask.flash(err.message, 'error')
        return flask.redirect(flask.url_for(
            'view_repo', username=username, repo=repo.name))

    repo_admin = is_repo_admin(repo)

    form = pagure.forms.RequestPullForm()
    if form.validate_on_submit() and repo_admin:
        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

            initial_comment = form.initial_comment.data.strip() or None
            request = pagure.lib.new_pull_request(
                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=APP.config['REQUESTS_FOLDER'],
            )

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

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

            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
            SESSION.rollback()
            flask.flash(str(err), 'error')

    if not is_repo_admin(repo):
        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, safe = pagure.doc_utils.convert_readme(
                    contributing.data, 'md')
                output_type = 'markup'

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

@APP.route('/<repo:repo>/diff/remote/', methods=('GET', 'POST'))
@APP.route('/<repo:repo>/diff/remote', methods=('GET', 'POST'))
@APP.route(
    '/fork/<username>/<repo:repo>/diff/remote/', methods=('GET', 'POST'))
@APP.route(
    '/fork/<username>/<repo:repo>/diff/remote', methods=('GET', 'POST'))
@login_required
def new_remote_request_pull(repo, username=None):
    """ Request pulling the changes from a remote fork into the project.
    """
    repo = pagure.lib.get_project(SESSION, repo, user=username)
    confirm = flask.request.values.get('confirm', False)

    if not repo:
        flask.abort(404)

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

    parentpath = pagure.get_repo_path(repo)
    orig_repo = pygit2.Repository(parentpath)

    repo_admin = is_repo_admin(repo)

    form = pagure.forms.RemoteRequestPullForm()
    if form.validate_on_submit():
        branch_from = form.branch_from.data.strip()
        branch_to = form.branch_to.data.strip()
        remote_git = form.git_repo.data.strip()

        repopath = pagure.get_remote_repo_path(remote_git, branch_from)
        repo_obj = pygit2.Repository(repopath)

        try:
            diff, diff_commits, orig_commit = _get_pr_info(
                repo_obj, orig_repo, branch_from, branch_to)
        except pagure.exceptions.PagureException as err:
            flask.flash(err.message, 'error')
            return flask.redirect(flask.url_for(
                'view_repo', username=username, repo=repo.name))

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

        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(
                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=APP.config['REQUESTS_FOLDER'],
            )

            if form.initial_comment.data.strip() != '':
                pagure.lib.add_pull_request_comment(
                    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=APP.config['REQUESTS_FOLDER'],
                )

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

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

            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
            SESSION.rollback()
            flask.flash(str(err), 'error')

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

@APP.route(
    '/fork_edit/<repo:repo>/edit/<path:branchname>/f/<path:filename>',
    methods=['POST'])
@APP.route(
    '/fork_edit/fork/<username>/<repo:repo>/edit/<path:branchname>/f/<path:filename>',
    methods=['POST'])
@login_required
def fork_edit_file(repo, branchname, filename, username=None):
    """ Fork the project specified and open the specific file to edit
    """
    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

    if repo is None:
        flask.abort(404)

    if pagure.lib.get_project(
            SESSION, repo.name, user=flask.g.fas_user.username):
        flask.flash('You had already forked this project')
        return flask.redirect(flask.url_for(
            'edit_file',
            username=flask.g.fas_user.username,
            repo=repo.name,
            branchname=branchname,
            filename=filename
        ))

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

        SESSION.commit()
        pagure.lib.git.generate_gitolite_acls()
        flask.flash(message)
        return flask.redirect(flask.url_for(
            'edit_file',
            username=flask.g.fas_user.username,
            repo=repo.name,
            branchname=branchname,
            filename=filename
        ))
    except pagure.exceptions.PagureException as err:
        flask.flash(str(err), 'error')
    except SQLAlchemyError as err:  # pragma: no cover
        SESSION.rollback()
        flask.flash(str(err), 'error')

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


code:
# -*- coding: utf-8 -*-

"""
 (c) 2015 - Copyright Red Hat Inc

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

"""

import flask

from sqlalchemy.exc import SQLAlchemyError

import pagure
import pagure.forms
import pagure.lib
import pagure.lib.git

# pylint: disable=E1101

@pagure.APP.route('/groups/')
@pagure.APP.route('/groups')
def group_lists():
    ''' List all the groups associated with all the projects. '''
    if not pagure.APP.config.get('ENABLE_USER_MNGT', True):
        flask.abort(404)

    group_type = 'user'
    if pagure.is_admin():
        group_type = None
    groups = pagure.lib.search_groups(pagure.SESSION, group_type=group_type)

    group_types = ['user']
    if pagure.is_admin():
        group_types = [
            grp.group_type
            for grp in pagure.lib.get_group_types(pagure.SESSION)
        ]
        # Make sure the admin type is always the last one
        group_types.remove('admin')
        group_types.append('admin')

    form = pagure.forms.NewGroupForm(group_types=group_types)

    return flask.render_template(
        'group_list.html',
        groups=groups,
        form=form,
    )

@pagure.APP.route('/group/<group>/', methods=['GET', 'POST'])
@pagure.APP.route('/group/<group>', methods=['GET', 'POST'])
def view_group(group):
    ''' Displays information about this group. '''
    if not pagure.APP.config.get('ENABLE_USER_MNGT', True):
        flask.abort(404)

    group_type = 'user'
    if pagure.is_admin():
        group_type = None
    group = pagure.lib.search_groups(
        pagure.SESSION, group_name=group, group_type=group_type)

    if not group:
        flask.abort(404, 'Group not found')

    # Add new user to the group if asked
    form = pagure.forms.AddUserForm()
    if pagure.authenticated() and form.validate_on_submit() \
        and pagure.APP.config.get('ENABLE_GROUP_MNGT', False):

        username = form.user.data

        try:
            msg = pagure.lib.add_user_to_group(
                pagure.SESSION,
                username=username,
                group=group,
                user=flask.g.fas_user.username,
                is_admin=pagure.is_admin(),
            )
            pagure.SESSION.commit()
            pagure.lib.git.generate_gitolite_acls()
            flask.flash(msg)
        except pagure.exceptions.PagureException as err:
            pagure.SESSION.rollback()
            flask.flash(err.message, 'error')
            return flask.redirect(
                flask.url_for('.view_group', group=group.group_name))
        except SQLAlchemyError as err:  # pragma: no cover
            pagure.SESSION.rollback()
            flask.flash(
                'Could not add user `%s` to group `%s`.' % (
                    username, group.group_name),
                'error')
            pagure.APP.logger.debug(
                'Could not add user `%s` to group `%s`.' % (
                    username, group.group_name))
            pagure.APP.logger.exception(err)

    member = False
    if pagure.authenticated():
        member = pagure.lib.is_group_member(
            pagure.SESSION, flask.g.fas_user.username, group.group_name)

    return flask.render_template(
        'group_info.html',
        group=group,
        form=form,
        member=member,
    )

@pagure.APP.route('/group/<group>/<user>/delete', methods=['POST'])
@pagure.login_required
def group_user_delete(user, group):
    """ Delete an user from a certain group
    """
    if not pagure.APP.config.get('ENABLE_USER_MNGT', True):
        flask.abort(404)

    if not pagure.APP.config.get('ENABLE_GROUP_MNGT', False):
        flask.abort(404)

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

        try:
            pagure.lib.delete_user_of_group(
                pagure.SESSION,
                username=user,
                groupname=group,
                user=flask.g.fas_user.username,
                is_admin=pagure.is_admin()
            )
            pagure.SESSION.commit()
            pagure.lib.git.generate_gitolite_acls()
            flask.flash(
                'User `%s` removed from the group `%s`' % (user, group))
        except pagure.exceptions.PagureException as err:
            pagure.SESSION.rollback()
            flask.flash(err.message, 'error')
            return flask.redirect(
                flask.url_for('.view_group', group=group))
        except SQLAlchemyError as err:  # pragma: no cover
            pagure.SESSION.rollback()
            flask.flash(
                'Could not remove user `%s` from the group `%s`.' % (
                    user.user, group),
                'error')
            pagure.APP.logger.debug(
                'Could not remove user `%s` from the group `%s`.' % (
                    user.user, group))
            pagure.APP.logger.exception(err)

    return flask.redirect(flask.url_for('.view_group', group=group))

@pagure.APP.route('/group/<group>/delete', methods=['POST'])
@pagure.login_required
def group_delete(group):
    """ Delete a certain group
    """
    if not pagure.APP.config.get('ENABLE_USER_MNGT', True):
        flask.abort(404)

    if not pagure.APP.config.get('ENABLE_GROUP_MNGT', False):
        flask.abort(404)

    form = pagure.forms.ConfirmationForm()
    if form.validate_on_submit():
        group_obj = pagure.lib.search_groups(
            pagure.SESSION, group_name=group)

        if not group_obj:
            flask.flash('No group `%s` found' % group, 'error')
            return flask.redirect(flask.url_for('.group_lists'))

        user = pagure.lib.search_user(
            pagure.SESSION, username=flask.g.fas_user.username)
        if not user:
            flask.abort(404, 'User not found')

        if group not in user.groups:
            flask.flash(
                'You are not allowed to delete the group %s' % group, 'error')
            return flask.redirect(flask.url_for('.group_lists'))

        pagure.SESSION.delete(group_obj)

        pagure.SESSION.commit()
        pagure.lib.git.generate_gitolite_acls()
        flask.flash(
            'Group `%s` has been deleted' % (group))

    return flask.redirect(flask.url_for('.group_lists'))

@pagure.APP.route('/group/add/', methods=['GET', 'POST'])
@pagure.APP.route('/group/add', methods=['GET', 'POST'])
@pagure.login_required
def add_group():
    """ Endpoint to create groups
    """
    if not pagure.APP.config.get('ENABLE_USER_MNGT', True):
        flask.abort(404)

    if not pagure.APP.config.get('ENABLE_GROUP_MNGT', False):
        flask.abort(404)

    user = pagure.lib.search_user(
        pagure.SESSION, username=flask.g.fas_user.username)
    if not user:  # pragma: no cover
        return flask.abort(403)

    group_types = ['user']
    if pagure.is_admin():
        group_types = [
            grp.group_type
            for grp in pagure.lib.get_group_types(pagure.SESSION)
        ]
        # Make sure the admin type is always the last one
        group_types.remove('admin')
        group_types.append('admin')

    form = pagure.forms.NewGroupForm(group_types=group_types)

    if not pagure.is_admin():
        form.group_type.data = 'user'

    if form.validate_on_submit():

        try:
            group_name = form.group_name.data
            msg = pagure.lib.add_group(
                session=pagure.SESSION,
                group_name=group_name,
                group_type=form.group_type.data,
                user=flask.g.fas_user.username,
                is_admin=pagure.is_admin(),
                blacklist=pagure.APP.config['BLACKLISTED_GROUPS'],
            )
            pagure.SESSION.commit()
            flask.flash('Group `%s` created.' % group_name)
            flask.flash(msg)
            return flask.redirect(flask.url_for('.group_lists'))
        except pagure.exceptions.PagureException as err:
            pagure.SESSION.rollback()
            flask.flash(err.message, 'error')
        except SQLAlchemyError as err:  # pragma: no cover
            pagure.SESSION.rollback()
            flask.flash('Could not create group.')
            pagure.APP.logger.debug('Could not create group.')
            pagure.APP.logger.exception(err)

    return flask.render_template(
        'add_group.html',
        form=form,
    )

repo_name: bill/auger

license: mit


code:
# -*- coding: utf-8 -*-

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

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

"""

import flask
import os
from collections import defaultdict

import pygit2
from sqlalchemy.exc import SQLAlchemyError

import chardet
import kitchen.text.converters as ktc
import mimetypes

import pagure.doc_utils
import pagure.lib
import pagure.forms
from pagure import (APP, SESSION, LOG, __get_file_in_tree,
                    login_required, is_repo_admin, authenticated)

# pylint: disable=E1101

# URLs

@APP.route('/<repo:repo>/issue/<int:issueid>/update/', methods=['GET', 'POST'])
@APP.route('/<repo:repo>/issue/<int:issueid>/update', methods=['GET', 'POST'])
@APP.route('/fork/<username>/<repo:repo>/issue/<int:issueid>/update/',
           methods=['GET', 'POST'])
@APP.route('/fork/<username>/<repo:repo>/issue/<int:issueid>/update',
           methods=['GET', 'POST'])
@login_required
def update_issue(repo, issueid, username=None):
    ''' Add a comment to an issue. '''
    is_js = flask.request.args.get('js', False)

    repo = pagure.lib.get_project(SESSION, repo, user=username)

    if flask.request.method == 'GET':
        if not is_js:
            flask.flash('Invalid method: GET', 'error')
        return flask.redirect(flask.url_for(
            'view_issue', username=username, repo=repo.name, issueid=issueid))

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

    if not repo.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid)

    if issue is None or issue.project != repo:
        flask.abort(404, 'Issue not found')

    if issue.private and not is_repo_admin(repo) \
            and (not authenticated() or
                 not issue.user.user == flask.g.fas_user.username):
        flask.abort(
            403, 'This issue is private and you are not allowed to view it')

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

    status = pagure.lib.get_issue_statuses(SESSION)
    form = pagure.forms.UpdateIssueForm(
        status=status, priorities=repo.priorities)

    if form.validate_on_submit():
        repo_admin = is_repo_admin(repo)

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

            comment = pagure.lib.get_issue_comment(
                SESSION, issue.uid, commentid)
            if comment is None or comment.issue.project != repo:
                flask.abort(404, 'Comment not found')

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

            SESSION.delete(comment)
            try:
                SESSION.commit()
                if not is_js:
                    flask.flash('Comment removed')
            except SQLAlchemyError as err:  # pragma: no cover
                is_js = False
                SESSION.rollback()
                LOG.error(err)
                if not is_js:
                    flask.flash(
                        'Could not remove the comment: %s' % commentid,
                        'error')
            if is_js:
                return 'ok'
            else:
                return flask.redirect(flask.url_for(
                    'view_issue', username=username, repo=repo.name,
                    issueid=issueid))

        comment = form.comment.data
        depends = []
        for depend in form.depends.data.split(','):
            if depend.strip():
                try:
                    depends.append(int(depend.strip()))
                except ValueError:
                    pass

        blocks = []
        for block in form.blocks.data.split(','):
            if block.strip():
                try:
                    blocks.append(int(block.strip()))
                except ValueError:
                    pass

        assignee = form.assignee.data
        new_status = form.status.data
        new_priority = None
        try:
            new_priority = int(form.priority.data)
        except:
            pass
        tags = [
            tag.strip()
            for tag in form.tag.data.split(',')
            if tag.strip()]

        try:

            # New comment
            if comment:
                message = pagure.lib.add_issue_comment(
                    SESSION,
                    issue=issue,
                    comment=comment,
                    user=flask.g.fas_user.username,
                    ticketfolder=APP.config['TICKETS_FOLDER'],
                )
                SESSION.commit()
                if message and not is_js:
                    flask.flash(message)

            if repo_admin:
                # Adjust (add/remove) tags
                messages = pagure.lib.update_tags(
                    SESSION, issue, tags,
                    username=flask.g.fas_user.username,
                    ticketfolder=APP.config['TICKETS_FOLDER']
                )
                if not is_js:
                    for message in messages:
                        flask.flash(message)

            # The meta-data can only be changed by admins, which means they
            # will be missing for non-admin and thus reset if we let them
            if repo_admin:
                # Assign or update assignee of the ticket
                message = pagure.lib.add_issue_assignee(
                    SESSION,
                    issue=issue,
                    assignee=assignee or None,
                    user=flask.g.fas_user.username,
                    ticketfolder=APP.config['TICKETS_FOLDER'],
                )
                SESSION.commit()
                if message and not is_js:
                    flask.flash(message)

                # Update status
                if new_status in status:
                    message = pagure.lib.edit_issue(
                        SESSION,
                        issue=issue,
                        status=new_status,
                        private=issue.private,
                        user=flask.g.fas_user.username,
                        ticketfolder=APP.config['TICKETS_FOLDER'],
                    )
                    SESSION.commit()
                    if message:
                        flask.flash(message)

                # Update priority
                if str(new_priority) in repo.priorities:
                    message = pagure.lib.edit_issue(
                        SESSION,
                        issue=issue,
                        priority=new_priority,
                        private=issue.private,
                        user=flask.g.fas_user.username,
                        ticketfolder=APP.config['TICKETS_FOLDER'],
                    )
                    SESSION.commit()
                    if message:
                        flask.flash(message)

            # Update ticket this one depends on
            messages = pagure.lib.update_dependency_issue(
                SESSION, repo, issue, depends,
                username=flask.g.fas_user.username,
                ticketfolder=APP.config['TICKETS_FOLDER'],
            )
            if not is_js:
                for message in messages:
                    flask.flash(message)

            # Update ticket(s) depending on this one
            messages = pagure.lib.update_blocked_issue(
                SESSION, repo, issue, blocks,
                username=flask.g.fas_user.username,
                ticketfolder=APP.config['TICKETS_FOLDER'],
            )
            if not is_js:
                for message in messages:
                    flask.flash(message)

        except pagure.exceptions.PagureException as err:
            is_js = False
            SESSION.rollback()
            if not is_js:
                flask.flash(err.message, 'error')
        except SQLAlchemyError as err:  # pragma: no cover
            is_js = False
            SESSION.rollback()
            APP.logger.exception(err)
            if not is_js:
                flask.flash(str(err), 'error')

    if is_js:
        return 'ok'
    else:
        return flask.redirect(flask.url_for(
            'view_issue', username=username, repo=repo.name, issueid=issueid))

@APP.route('/<repo:repo>/tag/<tag>/edit/', methods=('GET', 'POST'))
@APP.route('/<repo:repo>/tag/<tag>/edit', methods=('GET', 'POST'))
@APP.route('/fork/<username>/<repo:repo>/tag/<tag>/edit/', methods=('GET', 'POST'))
@APP.route('/fork/<username>/<repo:repo>/tag/<tag>/edit', methods=('GET', 'POST'))
@login_required
def edit_tag(repo, tag, username=None):
    """ Edit the specified tag associated with the issues of a project.
    """
    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

    if not is_repo_admin(repo):
        flask.abort(
            403,
            'You are not allowed to edit tags associated with the issues of \
            this project')

    if not repo.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    tags = pagure.lib.get_tags_of_project(SESSION, repo)

    if not tags or tag not in [t.tag for t in tags]:
        flask.abort(404, 'Tag %s not found in this project' % tag )

    form = pagure.forms.AddIssueTagForm()
    if form.validate_on_submit():
        new_tag = form.tag.data

        msgs = pagure.lib.edit_issue_tags(
            SESSION, repo, tag, new_tag,
            user=flask.g.fas_user.username,
            ticketfolder=APP.config['TICKETS_FOLDER']
        )

        try:
            SESSION.commit()
            for msg in msgs:
                flask.flash(msg)
        except SQLAlchemyError as err:  # pragma: no cover
            SESSION.rollback()
            LOG.error(err)
            flask.flash('Could not edit tag: %s' % tag, 'error')

        return flask.redirect(flask.url_for(
            '.view_settings', repo=repo.name, username=username))

    return flask.render_template(
        'edit_tag.html',
        form=form,
        username=username,
        repo=repo,
        edit_tag=tag,
    )

@APP.route('/<repo:repo>/droptag/', methods=['POST'])
@APP.route('/fork/<username>/<repo:repo>/droptag/', methods=['POST'])
@login_required
def remove_tag(repo, username=None):
    """ Remove the specified tag, associated with the issues, from the project.
    """
    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

    if not is_repo_admin(repo):
        flask.abort(
            403,
            'You are not allowed to remove tags associated with the issues \
            of this project')

    if not repo.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    form = pagure.forms.AddIssueTagForm()
    if form.validate_on_submit():
        tags = form.tag.data
        tags = [tag.strip() for tag in tags.split(',')]

        msgs = pagure.lib.remove_tags(
            SESSION, repo, tags,
            user=flask.g.fas_user.username,
            ticketfolder=APP.config['TICKETS_FOLDER']
        )

        try:
            SESSION.commit()
            for msg in msgs:
                flask.flash(msg)
        except SQLAlchemyError as err:  # pragma: no cover
            SESSION.rollback()
            LOG.error(err)
            flask.flash(
                'Could not remove tag: %s' % ','.join(tags), 'error')

    return flask.redirect(
        flask.url_for('.view_settings', repo=repo.name, username=username)
    )

@APP.route('/<repo:repo>/issues/')
@APP.route('/<repo:repo>/issues')
@APP.route('/fork/<username>/<repo:repo>/issues/')
@APP.route('/fork/<username>/<repo:repo>/issues')
def view_issues(repo, username=None):
    """ List all issues associated to a repo
    """
    status = flask.request.args.get('status', None)
    priority = flask.request.args.get('priority', None)
    tags = flask.request.args.getlist('tags')
    tags = [tag.strip() for tag in tags if tag.strip()]
    assignee = flask.request.args.get('assignee', None)
    author = flask.request.args.get('author', None)

    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

    if not repo.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    try:
        priority = int(priority)
    except:
        priority = None

    # Hide private tickets
    private = False
    # If user is authenticated, show him/her his/her private tickets
    if authenticated():
        private = flask.g.fas_user.username
    # If user is repo admin, show all tickets included the private ones
    if is_repo_admin(repo):
        private = None

    oth_issues = None
    if status is not None:
        if status.lower() == 'closed':
            issues = pagure.lib.search_issues(
                SESSION,
                repo,
                closed=True,
                tags=tags,
                assignee=assignee,
                author=author,
                private=private,
                priority=priority,
            )
            oth_issues = pagure.lib.search_issues(
                SESSION,
                repo,
                status='Open',
                tags=tags,
                assignee=assignee,
                author=author,
                private=private,
                priority=priority,
                count=True,
            )
        else:
            issues = pagure.lib.search_issues(
                SESSION,
                repo,
                status=status,
                tags=tags,
                assignee=assignee,
                author=author,
                private=private,
                priority=priority,
            )

    else:
        issues = pagure.lib.search_issues(
            SESSION, repo, status='Open', tags=tags, assignee=assignee,
            author=author, private=private, priority=priority)
        oth_issues = pagure.lib.search_issues(
            SESSION, repo, closed=True, tags=tags, assignee=assignee,
            author=author, private=private, priority=priority, count=True)

    tag_list = pagure.lib.get_tags_of_project(SESSION, repo)

    reponame = pagure.get_repo_path(repo)
    repo_obj = pygit2.Repository(reponame)

    return flask.render_template(
        'issues.html',
        select='issues',
        repo=repo,
        username=username,
        tag_list=tag_list,
        status=status,
        issues=issues,
        oth_issues=oth_issues,
        tags=tags,
        assignee=assignee,
        author=author,
        priority=priority,
        repo_admin=is_repo_admin(repo),
        repo_obj=repo_obj,
    )

@APP.route('/<repo:repo>/roadmap/')
@APP.route('/<repo:repo>/roadmap')
@APP.route('/fork/<username>/<repo:repo>/roadmap/')
@APP.route('/fork/<username>/<repo:repo>/roadmap')
def view_roadmap(repo, username=None):
    """ List all issues associated to a repo as roadmap
    """
    status = flask.request.args.get('status', 'Open')
    if status.lower() == 'all':
        status = None
    milestone = flask.request.args.getlist('milestone', None)

    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

    if not repo.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    # Hide private tickets
    private = False
    # If user is authenticated, show him/her his/her private tickets
    if authenticated():
        private = flask.g.fas_user.username
    # If user is repo admin, show all tickets included the private ones
    if is_repo_admin(repo):
        private = None

    milestones = milestone or list(repo.milestones.keys())
    tags = ['roadmap'] + milestones

    issues = pagure.lib.search_issues(
        SESSION,
        repo,
        tags=tags,
        private=private,
    )

    # Change from a list of issues to a dict of milestone/issues
    milestone_issues = defaultdict(list)
    for cnt in range(len(issues)):
        saved = False
        for mlstone in sorted(milestones):
            if mlstone in issues[cnt].tags_text:
                milestone_issues[mlstone].append(issues[cnt])
                saved = True
                break
        if saved:
            continue
        if not milestone:
            milestone_issues['unplanned'].append(issues[cnt])

    if status:
        for key in milestone_issues.keys():
            active = False
            for issue in milestone_issues[key]:
                if issue.status == 'Open':
                    active = True
                    break
            if not active:
                del(milestone_issues[key])

    if milestone:
        for mlstone in milestone:
            if mlstone not in milestone_issues:
                milestone_issues[mlstone] = []

    tag_list = pagure.lib.get_tags_of_project(SESSION, repo)

    reponame = pagure.get_repo_path(repo)
    repo_obj = pygit2.Repository(reponame)
    milestones_ordered = sorted(list(milestone_issues.keys()))
    if 'unplanned' in milestones_ordered:
        index = milestones_ordered.index('unplanned')
        cnt = len(milestones_ordered)
        milestones_ordered.insert(cnt, milestones_ordered.pop(index))

    return flask.render_template(
        'roadmap.html',
        select='issues',
        repo=repo,
        username=username,
        tag_list=tag_list,
        status=status,
        milestones=milestones_ordered,
        issues=milestone_issues,
        tags=milestone,
        repo_admin=is_repo_admin(repo),
        repo_obj=repo_obj,
    )

@APP.route('/<repo:repo>/new_issue/', methods=('GET', 'POST'))
@APP.route('/<repo:repo>/new_issue', methods=('GET', 'POST'))
@APP.route('/fork/<username>/<repo:repo>/new_issue/', methods=('GET', 'POST'))
@APP.route('/fork/<username>/<repo:repo>/new_issue', methods=('GET', 'POST'))
@login_required
def new_issue(repo, username=None):
    """ Create a new issue
    """
    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

    if not repo.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    form = pagure.forms.IssueFormSimplied()
    if form.validate_on_submit():
        title = form.title.data
        content = form.issue_content.data
        private = form.private.data

        try:
            issue = pagure.lib.new_issue(
                SESSION,
                repo=repo,
                title=title,
                content=content,
                private=private or False,
                user=flask.g.fas_user.username,
                ticketfolder=APP.config['TICKETS_FOLDER'],
            )
            SESSION.commit()
            # If there is a file attached, attach it.
            filestream = flask.request.files.get('filestream')
            if filestream and '<!!image>' in issue.content:
                new_filename = pagure.lib.git.add_file_to_git(
                    repo=repo,
                    issue=issue,
                    ticketfolder=APP.config['TICKETS_FOLDER'],
                    user=flask.g.fas_user,
                    filename=filestream.filename,
                    filestream=filestream.stream,
                )
                # Replace the <!!image> tag in the comment with the link
                # to the actual image
                filelocation = flask.url_for(
                    'view_issue_raw_file',
                    repo=repo.name,
                    username=username,
                    filename=new_filename,
                )
                new_filename = new_filename.split('-', 1)[1]
                url = '[![%s](%s)](%s)' % (
                    new_filename, filelocation, filelocation)
                issue.content = issue.content.replace('<!!image>', url)
                SESSION.add(issue)
                SESSION.commit()

            return flask.redirect(flask.url_for(
                '.view_issue', username=username, repo=repo.name,
                issueid=issue.id))
        except pagure.exceptions.PagureException as err:
            flask.flash(str(err), 'error')
        except SQLAlchemyError as err:  # pragma: no cover
            SESSION.rollback()
            flask.flash(str(err), 'error')

    types = None
    default = None
    ticketrepopath = os.path.join(APP.config['TICKETS_FOLDER'], repo.path)
    if os.path.exists(ticketrepopath):
        ticketrepo = pygit2.Repository(ticketrepopath)
        if not ticketrepo.is_empty and not ticketrepo.head_is_unborn:
            commit = ticketrepo[ticketrepo.head.target]
            # Get the different ticket types
            files = __get_file_in_tree(
                ticketrepo, commit.tree, ['templates'],
                bail_on_tree=True)
            if files:
                types = [f.name.rstrip('.md') for f in files]
            # Get the default template
            default_file = __get_file_in_tree(
                ticketrepo, commit.tree, ['templates', 'default.md'],
                bail_on_tree=True)
            if default_file:
                default, _ = pagure.doc_utils.convert_readme(
                        default_file.data, 'md')

    return flask.render_template(
        'new_issue.html',
        select='issues',
        form=form,
        repo=repo,
        username=username,
        repo_admin=is_repo_admin(repo),
        types=types,
        default=default,
    )

@APP.route('/<repo:repo>/issue/<int:issueid>/')
@APP.route('/<repo:repo>/issue/<int:issueid>')
@APP.route('/fork/<username>/<repo:repo>/issue/<int:issueid>/')
@APP.route('/fork/<username>/<repo:repo>/issue/<int:issueid>')
def view_issue(repo, issueid, username=None):
    """ List all issues associated to a repo
    """

    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

    if not repo.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid)

    if issue is None or issue.project != repo:
        flask.abort(404, 'Issue not found')

    if issue.private and not is_repo_admin(repo) \
            and (not authenticated() or
                 not issue.user.user == flask.g.fas_user.username):
        flask.abort(
            403, 'This issue is private and you are not allowed to view it')

    reponame = pagure.get_repo_path(repo)
    repo_obj = pygit2.Repository(reponame)

    status = pagure.lib.get_issue_statuses(SESSION)

    form = pagure.forms.UpdateIssueForm(
        status=status, priorities=repo.priorities)
    form.status.data = issue.status
    form.priority.data = str(issue.priority)
    tag_list = pagure.lib.get_tags_of_project(SESSION, repo)
    return flask.render_template(
        'issue.html',
        select='issues',
        repo=repo,
        username=username,
        repo_obj=repo_obj,
        tag_list=tag_list,
        issue=issue,
        issueid=issueid,
        form=form,
        repo_admin=is_repo_admin(repo),
    )

@APP.route('/<repo:repo>/issue/<int:issueid>/drop', methods=['POST'])
@APP.route('/fork/<username>/<repo:repo>/issue/<int:issueid>/drop',
           methods=['POST'])
def delete_issue(repo, issueid, username=None):
    """ Delete the specified issue
    """

    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

    if not repo.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid)

    if issue is None or issue.project != repo:
        flask.abort(404, 'Issue not found')

    if not is_repo_admin(repo):
        flask.abort(
            403,
            'You are not allowed to remove tickets of this project')

    form = pagure.forms.ConfirmationForm()
    if form.validate_on_submit():
        try:
            pagure.lib.drop_issue(
                SESSION, issue,
                user=flask.g.fas_user.username,
                ticketfolder=APP.config['TICKETS_FOLDER'],
            )
            SESSION.commit()
            flask.flash('Issue deleted')
            return flask.redirect(flask.url_for(
                'view_issues', username=username, repo=repo.name))
        except SQLAlchemyError as err:  # pragma: no cover
            SESSION.rollback()
            APP.logger.exception(err)
            flask.flash('Could not delete the issue', 'error')

    return flask.redirect(flask.url_for(
        'view_issue', username=username, repo=repo.name, issueid=issueid))

@APP.route('/<repo:repo>/issue/<int:issueid>/edit/', methods=('GET', 'POST'))
@APP.route('/<repo:repo>/issue/<int:issueid>/edit', methods=('GET', 'POST'))
@APP.route('/fork/<username>/<repo:repo>/issue/<int:issueid>/edit/',
           methods=('GET', 'POST'))
@APP.route('/fork/<username>/<repo:repo>/issue/<int:issueid>/edit',
           methods=('GET', 'POST'))
@login_required
def edit_issue(repo, issueid, username=None):
    """ Edit the specified issue
    """
    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

    if not repo.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid)

    if issue is None or issue.project != repo:
        flask.abort(404, 'Issue not found')

    if not (is_repo_admin(repo)
            or flask.g.fas_user.username == issue.user.username):
        flask.abort(
            403, 'You are not allowed to edit issues for this project')

    status = pagure.lib.get_issue_statuses(SESSION)
    form = pagure.forms.IssueForm(status=status)
    if form.validate_on_submit():
        title = form.title.data
        content = form.issue_content.data
        status = form.status.data
        private = form.private.data

        try:
            message = pagure.lib.edit_issue(
                SESSION,
                issue=issue,
                title=title,
                content=content,
                status=status,
                user=flask.g.fas_user.username,
                ticketfolder=APP.config['TICKETS_FOLDER'],
                private=private,
            )
            SESSION.commit()

            # If there is a file attached, attach it.
            filestream = flask.request.files.get('filestream')
            if filestream and '<!!image>' in issue.content:
                new_filename = pagure.lib.git.add_file_to_git(
                    repo=repo,
                    issue=issue,
                    ticketfolder=APP.config['TICKETS_FOLDER'],
                    user=flask.g.fas_user,
                    filename=filestream.filename,
                    filestream=filestream.stream,
                )
                # Replace the <!!image> tag in the comment with the link
                # to the actual image
                filelocation = flask.url_for(
                    'view_issue_raw_file',
                    repo=repo.name,
                    username=username,
                    filename=new_filename,
                )
                new_filename = new_filename.split('-', 1)[1]
                url = '[![%s](%s)](%s)' % (
                    new_filename, filelocation, filelocation)
                issue.content = issue.content.replace('<!!image>', url)
                SESSION.add(issue)
                SESSION.commit()
            flask.flash(message)
            url = flask.url_for(
                'view_issue', username=username,
                repo=repo.name, issueid=issueid)
            return flask.redirect(url)
        except pagure.exceptions.PagureException as err:
            flask.flash(str(err), 'error')
        except SQLAlchemyError as err:  # pragma: no cover
            SESSION.rollback()
            flask.flash(str(err), 'error')
    elif flask.request.method == 'GET':
        form.title.data = issue.title
        form.issue_content.data = issue.content
        form.status.data = issue.status
        form.private.data = issue.private

    return flask.render_template(
        'new_issue.html',
        select='issues',
        type='edit',
        form=form,
        repo=repo,
        username=username,
        issue=issue,
        issueid=issueid,
        repo_admin=is_repo_admin(repo),
    )

@APP.route('/<repo:repo>/issue/<int:issueid>/upload', methods=['POST'])
@APP.route('/fork/<username>/<repo:repo>/issue/<int:issueid>/upload',
           methods=['POST'])
@login_required
def upload_issue(repo, issueid, username=None):
    ''' Upload a file to a ticket.
    '''
    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

    if not repo.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid)

    if issue is None or issue.project != repo:
        flask.abort(404, 'Issue not found')

    form = pagure.forms.UploadFileForm()
    # pylint: disable=E1101
    if form.validate_on_submit():
        filestream = flask.request.files['filestream']
        new_filename = pagure.lib.git.add_file_to_git(
            repo=repo,
            issue=issue,
            ticketfolder=APP.config['TICKETS_FOLDER'],
            user=flask.g.fas_user,
            filename=filestream.filename,
            filestream=filestream.stream,
        )
        return flask.jsonify({
            'output': 'ok',
            'filename': new_filename.split('-', 1)[1],
            'filelocation': flask.url_for(
                'view_issue_raw_file',
                repo=repo.name,
                username=username,
                filename=new_filename,
            )
        })
    else:
        return flask.jsonify({'output': 'notok'})

@APP.route('/<repo:repo>/issue/raw/<path:filename>')
@APP.route('/fork/<username>/<repo:repo>/issue/raw/<path:filename>')
def view_issue_raw_file(repo, filename=None, username=None):
    """ Displays the raw content of a file of a commit for the specified
    ticket repo.
    """
    repo = pagure.lib.get_project(SESSION, repo, user=username)

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

    if not repo.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    reponame = os.path.join(APP.config['TICKETS_FOLDER'], repo.path)

    repo_obj = pygit2.Repository(reponame)

    if repo_obj.is_empty:
        flask.abort(404, 'Empty repo cannot have a file')

    branch = repo_obj.lookup_branch('master')
    commit = branch.get_object()

    mimetype = None
    encoding = None

    content = __get_file_in_tree(
        repo_obj, commit.tree, filename.split('/'), bail_on_tree=True)
    if not content or isinstance(content, pygit2.Tree):
        flask.abort(404, 'File not found')

    mimetype, encoding = mimetypes.guess_type(filename)
    data = repo_obj[content.oid].data

    if not data:
        flask.abort(404, 'No content found')

    if not mimetype and data[:2] == '#!':
        mimetype = 'text/plain'

    headers = {}
    if not mimetype:
        if '\0' in data:
            mimetype = 'application/octet-stream'
        else:
            mimetype = 'text/plain'
    elif 'html' in mimetype:
        mimetype = 'application/octet-stream'
        headers['Content-Disposition'] = 'attachment'

    if mimetype.startswith('text/') and not encoding:
        encoding = chardet.detect(ktc.to_bytes(data))['encoding']

    headers['Content-Type'] = mimetype
    if encoding:
        headers['Content-Encoding'] = encoding

    return (data, 200, headers)

@APP.route('/<repo:repo>/issue/<int:issueid>/comment/<int:commentid>/edit',
           methods=('GET', 'POST'))
@APP.route('/fork/<username>/<repo:repo>/issue/<int:issueid>/comment'
           '/<int:commentid>/edit', methods=('GET', 'POST'))
@login_required
def edit_comment_issue(repo, issueid, commentid, username=None):
    """Edit comment of an issue
    """
    is_js = flask.request.args.get('js', False)

    project = pagure.lib.get_project(SESSION, repo, user=username)

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

    if not project.settings.get('issue_tracker', True):
        flask.abort(404, 'No issue tracker found for this project')

    issue = pagure.lib.search_issues(SESSION, project, issueid=issueid)

    if issue is None or issue.project != project:
        flask.abort(404, 'Issue not found')

    comment = pagure.lib.get_issue_comment(
        SESSION, issue.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 is_repo_admin(project):
        flask.abort(403, 'You are not allowed to edit this comment')

    form = pagure.forms.EditCommentForm()

    if form.validate_on_submit():

        updated_comment = form.update_comment.data
        try:
            message = pagure.lib.edit_comment(
                SESSION,
                parent=issue,
                comment=comment,
                user=flask.g.fas_user.username,
                updated_comment=updated_comment,
                folder=APP.config['TICKETS_FOLDER'],
            )
            SESSION.commit()
            if not is_js:
                flask.flash(message)
        except SQLAlchemyError, err:  # pragma: no cover
            SESSION.rollback()
            LOG.error(err)
            if is_js:
                return 'error'
            flask.flash(
                'Could not edit the comment: %s' % commentid, 'error')

        if is_js:
            return 'ok'

        return flask.redirect(flask.url_for(
            'view_issue', username=username,
            repo=project.name, issueid=issueid))

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

    return flask.render_template(
        'comment_update.html',
        select='issues',
        requestid=issueid,
        repo=project,
        username=username,
        form=form,
        comment=comment,
        is_js=is_js,
        repo_admin=is_repo_admin(project),
    )