Blame progit/ui/issues.py

Pierre-Yves Chibon 47950c
#-*- coding: utf-8 -*-
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
"""
Pierre-Yves Chibon 47950c
 (c) 2014 - Copyright Red Hat Inc
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
 Authors:
Pierre-Yves Chibon 47950c
   Pierre-Yves Chibon <pingou@pingoured.fr></pingou@pingoured.fr>
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
"""
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
import flask
Pierre-Yves Chibon 47950c
import os
Pierre-Yves Chibon 47950c
from math import ceil
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
import pygit2
Pierre-Yves Chibon 47950c
from sqlalchemy.exc import SQLAlchemyError
Pierre-Yves Chibon 47950c
from pygments import highlight
Pierre-Yves Chibon 47950c
from pygments.lexers import guess_lexer
Pierre-Yves Chibon 47950c
from pygments.lexers.text import DiffLexer
Pierre-Yves Chibon 47950c
from pygments.formatters import HtmlFormatter
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
import progit.doc_utils
Pierre-Yves Chibon 47950c
import progit.lib
Pierre-Yves Chibon 47950c
import progit.forms
Pierre-Yves Chibon 0dbb10
from progit import (APP, SESSION, LOG, __get_file_in_tree, cla_required,
Pierre-Yves Chibon c3d937
                    is_repo_admin, authenticated)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 556a8e
## URLs
Pierre-Yves Chibon 556a8e
Pierre-Yves Chibon 32cbdc
@APP.route('/<repo>/issue/<int:issueid>/add', methods=('GET', 'POST'))</int:issueid></repo>
Pierre-Yves Chibon 32cbdc
@APP.route('/fork/<username>/<repo>/issue/<int:issueid>/add',</int:issueid></repo></username>
Pierre-Yves Chibon 556a8e
           methods=('GET', 'POST'))
Pierre-Yves Chibon 556a8e
def add_comment_issue(repo, issueid, username=None):
Pierre-Yves Chibon 556a8e
    ''' Add a comment to an issue. '''
Pierre-Yves Chibon 47950c
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    if repo is None:
Pierre-Yves Chibon 47950c
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 556a8e
    if not repo.issue_tracker:
Pierre-Yves Chibon 556a8e
        flask.abort(404, 'No issue tracker found for this project')
Pierre-Yves Chibon 556a8e
Pierre-Yves Chibon 32e73c
    issue = progit.lib.get_issue(SESSION, repo.id, issueid)
Pierre-Yves Chibon 556a8e
Pierre-Yves Chibon 1d6527
    if issue is None or issue.project != repo:
Pierre-Yves Chibon 556a8e
        flask.abort(404, 'Issue not found')
Pierre-Yves Chibon 556a8e
Pierre-Yves Chibon 556a8e
    form = progit.forms.AddIssueCommentForm()
Pierre-Yves Chibon 47950c
    if form.validate_on_submit():
Pierre-Yves Chibon 556a8e
        comment = form.comment.data
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
        try:
Pierre-Yves Chibon 556a8e
            message = progit.lib.add_issue_comment(
Pierre-Yves Chibon 47950c
                SESSION,
Pierre-Yves Chibon 556a8e
                issue=issue,
Pierre-Yves Chibon 556a8e
                comment=comment,
Pierre-Yves Chibon 47950c
                user=flask.g.fas_user.username,
Pierre-Yves Chibon a34835
                ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon 47950c
            )
Pierre-Yves Chibon 47950c
            SESSION.commit()
Pierre-Yves Chibon 47950c
            flask.flash(message)
Pierre-Yves Chibon 47950c
        except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon 47950c
            SESSION.rollback()
Pierre-Yves Chibon 47950c
            flask.flash(str(err), 'error')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 556a8e
    return flask.redirect(flask.url_for(
Pierre-Yves Chibon 6bf823
        'view_issue', username=username, repo=repo.name, issueid=issueid))
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon edadad
@APP.route('/<repo>/issue/<int:issueid>/tag', methods=('GET', 'POST'))</int:issueid></repo>
Pierre-Yves Chibon edadad
@APP.route('/fork/<username>/<repo>/issue/<int:issueid>/tag',</int:issueid></repo></username>
Pierre-Yves Chibon edadad
           methods=('GET', 'POST'))
Pierre-Yves Chibon e9a982
@APP.route('/<repo>/issue/<int:issueid>/tag/<chrome>', methods=('GET', 'POST'))</chrome></int:issueid></repo>
Pierre-Yves Chibon e9a982
@APP.route('/fork/<username>/<repo>/issue/<int:issueid>/tag/<chrome>',</chrome></int:issueid></repo></username>
Pierre-Yves Chibon e9a982
           methods=('GET', 'POST'))
Pierre-Yves Chibon 8f169d
@cla_required
Pierre-Yves Chibon e9a982
def add_tag_issue(repo, issueid, username=None, chrome=True):
Pierre-Yves Chibon edadad
    ''' Add a tag to an issue. '''
Pierre-Yves Chibon edadad
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon edadad
Pierre-Yves Chibon edadad
    if repo is None:
Pierre-Yves Chibon edadad
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon edadad
Pierre-Yves Chibon edadad
    if not repo.issue_tracker:
Pierre-Yves Chibon edadad
        flask.abort(404, 'No issue tracker found for this project')
Pierre-Yves Chibon edadad
Pierre-Yves Chibon edadad
    issue = progit.lib.get_issue(SESSION, repo.id, issueid)
Pierre-Yves Chibon edadad
Pierre-Yves Chibon edadad
    if issue is None or issue.project != repo:
Pierre-Yves Chibon edadad
        flask.abort(404, 'Issue not found')
Pierre-Yves Chibon edadad
Pierre-Yves Chibon edadad
    form = progit.forms.AddIssueTagForm()
Pierre-Yves Chibon e9a982
    cat = None
Pierre-Yves Chibon edadad
    if form.validate_on_submit():
Pierre-Yves Chibon 3df87b
        tags = form.tag.data
Pierre-Yves Chibon 3df87b
Pierre-Yves Chibon 3df87b
        for tag in tags.split(','):
Pierre-Yves Chibon 37487a
            tag = tag.strip()
Pierre-Yves Chibon 3df87b
            try:
Pierre-Yves Chibon 3df87b
                message = progit.lib.add_issue_tag(
Pierre-Yves Chibon 3df87b
                    SESSION,
Pierre-Yves Chibon 3df87b
                    issue=issue,
Pierre-Yves Chibon 37487a
                    tag=tag,
Pierre-Yves Chibon 3df87b
                    user=flask.g.fas_user.username,
Pierre-Yves Chibon 3df87b
                    ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon 3df87b
                )
Pierre-Yves Chibon ea8d98
                SESSION.commit()
Pierre-Yves Chibon 37487a
                msg = 'Added tag: %s' % tag
Pierre-Yves Chibon ea8d98
                flask.flash(msg)
Pierre-Yves Chibon 3df87b
            except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon 3df87b
                SESSION.rollback()
Pierre-Yves Chibon 37487a
                LOG.error(err)
Pierre-Yves Chibon 37487a
                flask.flash('Could not add tag: %s' % tag, 'error')
Pierre-Yves Chibon edadad
Pierre-Yves Chibon e9a982
    if not chrome:
Pierre-Yves Chibon e9a982
        if cat is not None:
Pierre-Yves Chibon e9a982
            output = {'output': 'notok', 'message': msg}
Pierre-Yves Chibon e9a982
            httpcode = 500
Pierre-Yves Chibon e9a982
        else:
Pierre-Yves Chibon e9a982
            output = {'output': 'ok', 'message': msg}
Pierre-Yves Chibon e9a982
            httpcode = 200
Pierre-Yves Chibon e9a982
Pierre-Yves Chibon e9a982
        jsonout = flask.jsonify(output)
Pierre-Yves Chibon e9a982
        jsonout.status_code = httpcode
Pierre-Yves Chibon e9a982
        return jsonout
Pierre-Yves Chibon e9a982
Pierre-Yves Chibon e9a982
    else:
Pierre-Yves Chibon e9a982
        return flask.redirect(flask.url_for(
Pierre-Yves Chibon e9a982
            'view_issue', username=username, repo=repo.name, issueid=issueid))
Pierre-Yves Chibon edadad
Pierre-Yves Chibon edadad
Pierre-Yves Chibon 556a8e
@APP.route('/<repo>/issues')</repo>
Pierre-Yves Chibon 556a8e
@APP.route('/fork/<username>/<repo>/issues')</repo></username>
Pierre-Yves Chibon 556a8e
def view_issues(repo, username=None):
Pierre-Yves Chibon 556a8e
    """ List all issues associated to a repo
Pierre-Yves Chibon 47950c
    """
Pierre-Yves Chibon 556a8e
    status = flask.request.args.get('status', None)
Pierre-Yves Chibon a7173e
    tags = flask.request.args.getlist('tags', None)
Pierre-Yves Chibon ee7151
    tags = [tag for tag in tags if tag]
Pierre-Yves Chibon 556a8e
Pierre-Yves Chibon 47950c
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    if repo is None:
Pierre-Yves Chibon 47950c
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    if not repo.issue_tracker:
Pierre-Yves Chibon 47950c
        flask.abort(404, 'No issue tracker found for this project')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 556a8e
    if status is not None:
Pierre-Yves Chibon 556a8e
        if status.lower() == 'closed':
Pierre-Yves Chibon a7173e
            issues = progit.lib.get_issues(
Pierre-Yves Chibon a7173e
                SESSION, repo, closed=True, tags=tags)
Pierre-Yves Chibon 556a8e
        else:
Pierre-Yves Chibon a7173e
            issues = progit.lib.get_issues(
Pierre-Yves Chibon a7173e
                SESSION, repo, status=status, tags=tags)
Pierre-Yves Chibon 556a8e
    else:
Pierre-Yves Chibon a7173e
        issues = progit.lib.get_issues(
Pierre-Yves Chibon a7173e
            SESSION, repo, status='Open', tags=tags)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 15f6e9
    tag_list = progit.lib.get_tags_of_project(SESSION, repo)
Pierre-Yves Chibon 15f6e9
Pierre-Yves Chibon 556a8e
    return flask.render_template(
Pierre-Yves Chibon 556a8e
        'issues.html',
Pierre-Yves Chibon 556a8e
        select='issues',
Pierre-Yves Chibon 556a8e
        repo=repo,
Pierre-Yves Chibon 556a8e
        username=username,
Pierre-Yves Chibon 556a8e
        status=status,
Pierre-Yves Chibon 556a8e
        issues=issues,
Pierre-Yves Chibon 15f6e9
        tags=tags,
Pierre-Yves Chibon 15f6e9
        tag_list=tag_list,
Pierre-Yves Chibon 556a8e
    )
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 556a8e
Pierre-Yves Chibon 556a8e
@APP.route('/<repo>/new_issue', methods=('GET', 'POST'))</repo>
Pierre-Yves Chibon 556a8e
@APP.route('/fork/<username>/<repo>/new_issue', methods=('GET', 'POST'))</repo></username>
Pierre-Yves Chibon 556a8e
@cla_required
Pierre-Yves Chibon 556a8e
def new_issue(repo, username=None):
Pierre-Yves Chibon 556a8e
    """ Create a new issue
Pierre-Yves Chibon 556a8e
    """
Pierre-Yves Chibon 556a8e
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon 556a8e
Pierre-Yves Chibon 556a8e
    if repo is None:
Pierre-Yves Chibon 556a8e
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon 556a8e
Pierre-Yves Chibon c5a9a2
    status = progit.lib.get_issue_statuses(SESSION)
Pierre-Yves Chibon c5a9a2
    form = progit.forms.IssueForm(status=status)
Pierre-Yves Chibon 47950c
    if form.validate_on_submit():
Pierre-Yves Chibon 47950c
        title = form.title.data
Pierre-Yves Chibon c5a9a2
        content = form.issue_content.data
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
        try:
Pierre-Yves Chibon 556a8e
            message = progit.lib.new_issue(
Pierre-Yves Chibon 47950c
                SESSION,
Pierre-Yves Chibon 556a8e
                repo=repo,
Pierre-Yves Chibon 47950c
                title=title,
Pierre-Yves Chibon 47950c
                content=content,
Pierre-Yves Chibon 556a8e
                user=flask.g.fas_user.username,
Pierre-Yves Chibon a34835
                ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon 47950c
            )
Pierre-Yves Chibon 47950c
            SESSION.commit()
Pierre-Yves Chibon 47950c
            flask.flash(message)
Pierre-Yves Chibon 556a8e
            return flask.redirect(flask.url_for(
Pierre-Yves Chibon a3258a
                'view_issues', username=username, repo=repo.name))
Pierre-Yves Chibon 47950c
        except progit.exceptions.ProgitException, err:
Pierre-Yves Chibon 47950c
            flask.flash(str(err), 'error')
Pierre-Yves Chibon 47950c
        except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon 47950c
            SESSION.rollback()
Pierre-Yves Chibon 47950c
            flask.flash(str(err), 'error')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    return flask.render_template(
Pierre-Yves Chibon 47950c
        'new_issue.html',
Pierre-Yves Chibon 47950c
        select='issues',
Pierre-Yves Chibon 47950c
        form=form,
Pierre-Yves Chibon 47950c
        repo=repo,
Pierre-Yves Chibon 47950c
        username=username,
Pierre-Yves Chibon 47950c
    )
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 32cbdc
@APP.route('/<repo>/issue/<int:issueid>', methods=('GET', 'POST'))</int:issueid></repo>
Pierre-Yves Chibon 32cbdc
@APP.route('/fork/<username>/<repo>/issue/<int:issueid>',</int:issueid></repo></username>
Pierre-Yves Chibon 556a8e
           methods=('GET', 'POST'))
Pierre-Yves Chibon 556a8e
def view_issue(repo, issueid, username=None):
Pierre-Yves Chibon 47950c
    """ List all issues associated to a repo
Pierre-Yves Chibon 47950c
    """
Pierre-Yves Chibon 556a8e
Pierre-Yves Chibon 47950c
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    if repo is None:
Pierre-Yves Chibon 47950c
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    if not repo.issue_tracker:
Pierre-Yves Chibon 47950c
        flask.abort(404, 'No issue tracker found for this project')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 32e73c
    issue = progit.lib.get_issue(SESSION, repo.id, issueid)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 362d6b
    if issue is None or issue.project != repo:
Pierre-Yves Chibon 47950c
        flask.abort(404, 'Issue not found')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    status = progit.lib.get_issue_statuses(SESSION)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 307d54
    form_comment = progit.forms.AddIssueCommentForm()
Pierre-Yves Chibon 169392
    form = form_tag = None
Pierre-Yves Chibon c3d937
    if authenticated() and is_repo_admin(repo):
Pierre-Yves Chibon c3d937
        form = progit.forms.UpdateIssueStatusForm(status=status)
Pierre-Yves Chibon 169392
        form_tag = progit.forms.AddIssueTagForm()
Pierre-Yves Chibon c3d937
Pierre-Yves Chibon c3d937
        if form.validate_on_submit():
Pierre-Yves Chibon c3d937
            try:
Pierre-Yves Chibon c3d937
                message = progit.lib.edit_issue(
Pierre-Yves Chibon c3d937
                    SESSION,
Pierre-Yves Chibon c3d937
                    issue=issue,
Pierre-Yves Chibon c3d937
                    status=form.status.data,
Pierre-Yves Chibon a34835
                    ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon c3d937
                )
Pierre-Yves Chibon c3d937
                SESSION.commit()
Pierre-Yves Chibon c3d937
                flask.flash(message)
Pierre-Yves Chibon 449928
                url = flask.url_for(
Pierre-Yves Chibon 329bf7
                    'view_issue', username=username, repo=repo.name,
Pierre-Yves Chibon 329bf7
                    issueid=issueid)
Pierre-Yves Chibon c3d937
                return flask.redirect(url)
Pierre-Yves Chibon c3d937
            except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon c3d937
                SESSION.rollback()
Pierre-Yves Chibon c3d937
                flask.flash(str(err), 'error')
Pierre-Yves Chibon c3d937
        elif flask.request.method == 'GET':
Pierre-Yves Chibon c3d937
            form.status.data = issue.status
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    return flask.render_template(
Pierre-Yves Chibon 47950c
        'issue.html',
Pierre-Yves Chibon 47950c
        select='issues',
Pierre-Yves Chibon 47950c
        repo=repo,
Pierre-Yves Chibon 47950c
        username=username,
Pierre-Yves Chibon 47950c
        issue=issue,
Pierre-Yves Chibon c156b6
        issueid=issueid,
Pierre-Yves Chibon 47950c
        form=form,
Pierre-Yves Chibon 307d54
        form_comment=form_comment,
Pierre-Yves Chibon 169392
        form_tag=form_tag,
Pierre-Yves Chibon 47950c
    )
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 32cbdc
@APP.route('/<repo>/issue/<int:issueid>/edit', methods=('GET', 'POST'))</int:issueid></repo>
Pierre-Yves Chibon 32cbdc
@APP.route('/fork/<username>/<repo>/issue/<int:issueid>/edit',</int:issueid></repo></username>
Pierre-Yves Chibon f9a5d2
           methods=('GET', 'POST'))
Pierre-Yves Chibon 556a8e
@cla_required
Pierre-Yves Chibon 556a8e
def edit_issue(repo, issueid, username=None):
Pierre-Yves Chibon 556a8e
    """ Edit the specified issue
Pierre-Yves Chibon 556a8e
    """
Pierre-Yves Chibon f9a5d2
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon f9a5d2
Pierre-Yves Chibon f9a5d2
    if repo is None:
Pierre-Yves Chibon f9a5d2
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon f9a5d2
Pierre-Yves Chibon f9a5d2
    if not repo.issue_tracker:
Pierre-Yves Chibon f9a5d2
        flask.abort(404, 'No issue tracker found for this project')
Pierre-Yves Chibon f9a5d2
Pierre-Yves Chibon 0dbb10
    if not is_repo_admin(repo):
Pierre-Yves Chibon 0dbb10
        flask.abort(
Pierre-Yves Chibon 0dbb10
            403, 'You are not allowed to edit issues for this project')
Pierre-Yves Chibon 0dbb10
Pierre-Yves Chibon 32e73c
    issue = progit.lib.get_issue(SESSION, repo.id, issueid)
Pierre-Yves Chibon f9a5d2
Pierre-Yves Chibon 1d6527
    if issue is None or issue.project != repo:
Pierre-Yves Chibon f9a5d2
        flask.abort(404, 'Issue not found')
Pierre-Yves Chibon f9a5d2
Pierre-Yves Chibon 556a8e
    status = progit.lib.get_issue_statuses(SESSION)
Pierre-Yves Chibon 556a8e
    form = progit.forms.IssueForm(status=status)
Pierre-Yves Chibon f9a5d2
    if form.validate_on_submit():
Pierre-Yves Chibon 556a8e
        title = form.title.data
Pierre-Yves Chibon 824f5d
        content = form.issue_content.data
Pierre-Yves Chibon 556a8e
        status = form.status.data
Pierre-Yves Chibon f9a5d2
Pierre-Yves Chibon f9a5d2
        try:
Pierre-Yves Chibon 556a8e
            message = progit.lib.edit_issue(
Pierre-Yves Chibon f9a5d2
                SESSION,
Pierre-Yves Chibon f9a5d2
                issue=issue,
Pierre-Yves Chibon 556a8e
                title=title,
Pierre-Yves Chibon 556a8e
                content=content,
Pierre-Yves Chibon 556a8e
                status=status,
Pierre-Yves Chibon a34835
                ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon f9a5d2
            )
Pierre-Yves Chibon f9a5d2
            SESSION.commit()
Pierre-Yves Chibon f9a5d2
            flask.flash(message)
Pierre-Yves Chibon 556a8e
            url = flask.url_for(
Pierre-Yves Chibon 556a8e
                'view_issue', username=username,
Pierre-Yves Chibon 6bf823
                repo=repo.name, issueid=issueid)
Pierre-Yves Chibon 556a8e
            return flask.redirect(url)
Pierre-Yves Chibon 556a8e
        except progit.exceptions.ProgitException, err:
Pierre-Yves Chibon 556a8e
            flask.flash(str(err), 'error')
Pierre-Yves Chibon f9a5d2
        except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon f9a5d2
            SESSION.rollback()
Pierre-Yves Chibon f9a5d2
            flask.flash(str(err), 'error')
Pierre-Yves Chibon 556a8e
    elif flask.request.method == 'GET':
Pierre-Yves Chibon 556a8e
        form.title.data = issue.title
Pierre-Yves Chibon 824f5d
        form.issue_content.data = issue.content
Pierre-Yves Chibon 556a8e
        form.status.data = issue.status
Pierre-Yves Chibon f9a5d2
Pierre-Yves Chibon 556a8e
    return flask.render_template(
Pierre-Yves Chibon 556a8e
        'new_issue.html',
Pierre-Yves Chibon 556a8e
        select='issues',
Pierre-Yves Chibon 556a8e
        type='edit',
Pierre-Yves Chibon 556a8e
        form=form,
Pierre-Yves Chibon 556a8e
        repo=repo,
Pierre-Yves Chibon 556a8e
        username=username,
Pierre-Yves Chibon 556a8e
        issue=issue,
Pierre-Yves Chibon 32cbdc
        issueid=issueid,
Pierre-Yves Chibon 556a8e
    )