Blame progit/ui/issues.py

Pierre-Yves Chibon 33b534
# -*- coding: utf-8 -*-
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
"""
Pierre-Yves Chibon 8a5345
 (c) 2014-2015 - 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 ccbeb3
import mimetypes
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 e04c77
# URLs
Pierre-Yves Chibon 556a8e
Pierre-Yves Chibon ea16af
@APP.route('/<repo>/issue/<int:issueid>/update', methods=('GET', 'POST'))</int:issueid></repo>
Pierre-Yves Chibon ea16af
@APP.route('/fork/<username>/<repo>/issue/<int:issueid>/update',</int:issueid></repo></username>
Pierre-Yves Chibon 556a8e
           methods=('GET', 'POST'))
Pierre-Yves Chibon ea16af
def update_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 d054da
    issue = progit.lib.search_issues(SESSION, repo, issueid=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 a0c229
    if issue.private and not is_repo_admin(repo) \
Pierre-Yves Chibon ca45e3
            and (
Pierre-Yves Chibon ca45e3
                not authenticated() or
Pierre-Yves Chibon ca45e3
                not issue.user.user == flask.g.fas_user.username):
Pierre-Yves Chibon a0c229
        flask.abort(
Pierre-Yves Chibon a0c229
            403, 'This issue is private and you are not allowed to view it')
Pierre-Yves Chibon a0c229
Pierre-Yves Chibon ea16af
    status = progit.lib.get_issue_statuses(SESSION)
Pierre-Yves Chibon ea16af
    form = progit.forms.UpdateIssueForm(status=status)
Pierre-Yves Chibon ea16af
Pierre-Yves Chibon 47950c
    if form.validate_on_submit():
Pierre-Yves Chibon 556a8e
        comment = form.comment.data
Pierre-Yves Chibon a33982
        try:
Pierre-Yves Chibon a33982
            depends = [
Pierre-Yves Chibon a33982
                int(depend.strip())
Pierre-Yves Chibon a33982
                for depend in form.depends.data.split(',')
Pierre-Yves Chibon a33982
                if depend.strip()]
Pierre-Yves Chibon a33982
        except ValueError:
Pierre-Yves Chibon a33982
            depends = []
Pierre-Yves Chibon a33982
Pierre-Yves Chibon a33982
        try:
Pierre-Yves Chibon a33982
            blocks = [
Pierre-Yves Chibon a33982
                int(block.strip())
Pierre-Yves Chibon a33982
                for block in form.blocks.data.split(',')
Pierre-Yves Chibon a33982
                if block.strip()]
Pierre-Yves Chibon a33982
        except ValueError:
Pierre-Yves Chibon a33982
            blocks = []
Pierre-Yves Chibon a33982
Pierre-Yves Chibon ea16af
        assignee = form.assignee.data
Pierre-Yves Chibon ea16af
        new_status = form.status.data
Pierre-Yves Chibon ea16af
        tags = [
Pierre-Yves Chibon ea16af
            tag.strip()
Pierre-Yves Chibon ea16af
            for tag in form.tag.data.split(',')
Pierre-Yves Chibon ea16af
            if tag.strip()]
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
        try:
Pierre-Yves Chibon a33982
Pierre-Yves Chibon a33982
            # New comment
Pierre-Yves Chibon ea16af
            if comment:
Pierre-Yves Chibon ea16af
                message = progit.lib.add_issue_comment(
Pierre-Yves Chibon 3df87b
                    SESSION,
Pierre-Yves Chibon 3df87b
                    issue=issue,
Pierre-Yves Chibon ea16af
                    comment=comment,
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 07add5
                if message:
Pierre-Yves Chibon 07add5
                    flask.flash(message)
Pierre-Yves Chibon 4a8cb4
Pierre-Yves Chibon a33982
            # Adjust (add/remove) tags
Pierre-Yves Chibon 7e59ab
            toadd = set(tags) - set(issue.tags_text)
Pierre-Yves Chibon 7e59ab
            torm = set(issue.tags_text) - set(tags)
Pierre-Yves Chibon 7e59ab
            for tag in toadd:
Pierre-Yves Chibon 7e59ab
                message = progit.lib.add_issue_tag(
Pierre-Yves Chibon 7e59ab
                        SESSION,
Pierre-Yves Chibon 7e59ab
                        issue=issue,
Pierre-Yves Chibon 7e59ab
                        tag=tag,
Pierre-Yves Chibon 7e59ab
                        user=flask.g.fas_user.username,
Pierre-Yves Chibon 7e59ab
                        ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon 7e59ab
                    )
Pierre-Yves Chibon 7e59ab
                SESSION.commit()
Pierre-Yves Chibon 7e59ab
                if message:
Pierre-Yves Chibon 7e59ab
                    flask.flash(message)
Pierre-Yves Chibon 7e59ab
            if torm:
Pierre-Yves Chibon 7e59ab
                messages = progit.lib.remove_tags_issue(
Pierre-Yves Chibon 7e59ab
                        SESSION,
Pierre-Yves Chibon 7e59ab
                        issue=issue,
Pierre-Yves Chibon 7e59ab
                        tags=torm,
Pierre-Yves Chibon 7e59ab
                        ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon 7e59ab
                    )
Pierre-Yves Chibon 7e59ab
                SESSION.commit()
Pierre-Yves Chibon 7e59ab
                for message in messages:
Pierre-Yves Chibon 7e59ab
                    flask.flash(message)
Pierre-Yves Chibon ea16af
Pierre-Yves Chibon a33982
            # Assign or update assignee of the ticket
Pierre-Yves Chibon 985245
            message = progit.lib.add_issue_assignee(
Pierre-Yves Chibon 985245
                SESSION,
Pierre-Yves Chibon 985245
                issue=issue,
Pierre-Yves Chibon 985245
                assignee=assignee or None,
Pierre-Yves Chibon 985245
                user=flask.g.fas_user.username,
Pierre-Yves Chibon 985245
                ticketfolder=APP.config['TICKETS_FOLDER'],)
Pierre-Yves Chibon 985245
            if message:
Pierre-Yves Chibon 985245
                SESSION.commit()
Pierre-Yves Chibon 985245
                flask.flash(message)
Pierre-Yves Chibon ea16af
Pierre-Yves Chibon a33982
            # Update status
Pierre-Yves Chibon ea16af
            if new_status == 'Fixed' and issue.parents:
Pierre-Yves Chibon ea16af
                for parent in issue.parents:
Pierre-Yves Chibon ea16af
                    if parent.status == 'Open':
Pierre-Yves Chibon ea16af
                        flask.flash(
Pierre-Yves Chibon ea16af
                            'You cannot close a ticket that has ticket '
Pierre-Yves Chibon ea16af
                            'depending that are still open.',
Pierre-Yves Chibon ea16af
                            'error')
Pierre-Yves Chibon ea16af
                        return flask.redirect(flask.url_for(
Pierre-Yves Chibon ea16af
                            'view_issue', repo=repo.name, username=username,
Pierre-Yves Chibon ea16af
                            issueid=issueid))
Pierre-Yves Chibon ea16af
Pierre-Yves Chibon 21f51e
            if new_status in status:
Pierre-Yves Chibon ea16af
                message = progit.lib.edit_issue(
Pierre-Yves Chibon 840cbe
                    SESSION,
Pierre-Yves Chibon ea16af
                    issue=issue,
Pierre-Yves Chibon ea16af
                    status=new_status,
Pierre-Yves Chibon ea16af
                    ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon ea16af
                )
Pierre-Yves Chibon 4a8cb4
                SESSION.commit()
Pierre-Yves Chibon 07add5
                if message:
Pierre-Yves Chibon 07add5
                    flask.flash(message)
Pierre-Yves Chibon a33982
Pierre-Yves Chibon a33982
            # Update ticket this one depends on
Pierre-Yves Chibon a33982
            toadd = set(depends) - set(issue.depends_text)
Pierre-Yves Chibon a33982
            torm = set(issue.depends_text) - set(depends)
Pierre-Yves Chibon a33982
            # Add issue depending
Pierre-Yves Chibon a33982
            for depend in toadd:
Pierre-Yves Chibon a33982
                issue_depend = progit.lib.search_issues(
Pierre-Yves Chibon a33982
                    SESSION, repo, issueid=depend)
Pierre-Yves Chibon a33982
                if issue_depend is None or issue_depend.project != repo:
Pierre-Yves Chibon a33982
                    flask.flash('Issue %s not found' % depend, 'error')
Pierre-Yves Chibon a33982
                    continue
Pierre-Yves Chibon a33982
                if issue_depend.id in issue.depends_text:
Pierre-Yves Chibon a33982
                    continue
Pierre-Yves Chibon a33982
Pierre-Yves Chibon a33982
                message = progit.lib.add_issue_dependency(
Pierre-Yves Chibon a33982
                    SESSION,
Pierre-Yves Chibon a33982
                    issue=issue_depend,
Pierre-Yves Chibon a33982
                    issue_blocked=issue,
Pierre-Yves Chibon a33982
                    user=flask.g.fas_user.username,
Pierre-Yves Chibon a33982
                    ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon a33982
                )
Pierre-Yves Chibon a33982
                SESSION.commit()
Pierre-Yves Chibon a33982
                if message:
Pierre-Yves Chibon a33982
                    flask.flash(message)
Pierre-Yves Chibon a33982
Pierre-Yves Chibon a33982
            # Remove issue depending
Pierre-Yves Chibon a33982
            for depend in torm:
Pierre-Yves Chibon a33982
                issue_depend = progit.lib.search_issues(
Pierre-Yves Chibon a33982
                    SESSION, repo, issueid=depend)
Pierre-Yves Chibon a33982
                if issue_depend is None or issue_depend.project != repo:
Pierre-Yves Chibon a33982
                    flask.flash('Issue %s not found' % depend, 'error')
Pierre-Yves Chibon a33982
                    continue
Pierre-Yves Chibon a33982
                if issue_depend.id not in issue.depends_text:
Pierre-Yves Chibon a33982
                    continue
Pierre-Yves Chibon a33982
Pierre-Yves Chibon a33982
                message = progit.lib.remove_issue_dependency(
Pierre-Yves Chibon a33982
                    SESSION,
Pierre-Yves Chibon a33982
                    issue=issue,
Pierre-Yves Chibon a33982
                    issue_blocked=issue_depend,
Pierre-Yves Chibon a33982
                    user=flask.g.fas_user.username,
Pierre-Yves Chibon a33982
                    ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon a33982
                )
Pierre-Yves Chibon a33982
                SESSION.commit()
Pierre-Yves Chibon a33982
                if message:
Pierre-Yves Chibon a33982
                    flask.flash(message)
Pierre-Yves Chibon a33982
Pierre-Yves Chibon a33982
            # Update ticket(s) depending on this one
Pierre-Yves Chibon a33982
            toadd = set(blocks) - set(issue.blocks_text)
Pierre-Yves Chibon a33982
            torm = set(issue.blocks_text) - set(blocks)
Pierre-Yves Chibon a33982
            # Add issue blocked
Pierre-Yves Chibon a33982
            for block in toadd:
Pierre-Yves Chibon a33982
                issue_block = progit.lib.search_issues(
Pierre-Yves Chibon a33982
                    SESSION, repo, issueid=block)
Pierre-Yves Chibon a33982
                if issue_block is None or issue_block.project != repo:
Pierre-Yves Chibon a33982
                    flask.flash('Issue %s not found' % block, 'error')
Pierre-Yves Chibon a33982
                    continue
Pierre-Yves Chibon a33982
                if issue_block.id in issue.blocks_text:
Pierre-Yves Chibon a33982
                    continue
Pierre-Yves Chibon a33982
Pierre-Yves Chibon a33982
                message = progit.lib.add_issue_dependency(
Pierre-Yves Chibon a33982
                    SESSION,
Pierre-Yves Chibon a33982
                    issue=issue,
Pierre-Yves Chibon a33982
                    issue_blocked=issue_block,
Pierre-Yves Chibon a33982
                    user=flask.g.fas_user.username,
Pierre-Yves Chibon a33982
                    ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon a33982
                )
Pierre-Yves Chibon a33982
                SESSION.commit()
Pierre-Yves Chibon a33982
                if message:
Pierre-Yves Chibon a33982
                    flask.flash(message)
Pierre-Yves Chibon a33982
Pierre-Yves Chibon a33982
            # Remove issue blocked
Pierre-Yves Chibon a33982
            for block in torm:
Pierre-Yves Chibon a33982
                issue_block = progit.lib.search_issues(
Pierre-Yves Chibon a33982
                    SESSION, repo, issueid=block)
Pierre-Yves Chibon a33982
                if issue_block is None or issue_block.project != repo:
Pierre-Yves Chibon a33982
                    flask.flash('Issue %s not found' % block, 'error')
Pierre-Yves Chibon a33982
                    continue
Pierre-Yves Chibon a33982
Pierre-Yves Chibon a33982
                if issue_block.id not in issue.blocks_text:
Pierre-Yves Chibon a33982
                    continue
Pierre-Yves Chibon a33982
Pierre-Yves Chibon a33982
                message = progit.lib.remove_issue_dependency(
Pierre-Yves Chibon a33982
                    SESSION,
Pierre-Yves Chibon a33982
                    issue=issue_block,
Pierre-Yves Chibon a33982
                    issue_blocked=issue,
Pierre-Yves Chibon a33982
                    user=flask.g.fas_user.username,
Pierre-Yves Chibon a33982
                    ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon a33982
                )
Pierre-Yves Chibon a33982
                SESSION.commit()
Pierre-Yves Chibon a33982
                if message:
Pierre-Yves Chibon a33982
                    flask.flash(message)
Pierre-Yves Chibon a33982
Pierre-Yves Chibon c8aec7
        except progit.exceptions.ProgitException, err:
Pierre-Yves Chibon c8aec7
            SESSION.rollback()
Pierre-Yves Chibon c8aec7
            flask.flash(str(err), 'error')
Pierre-Yves Chibon 4a8cb4
        except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon 4a8cb4
            SESSION.rollback()
Pierre-Yves Chibon ea16af
            APP.logger.exception(err)
Pierre-Yves Chibon ea16af
            flask.flash(str(err), 'error')
Pierre-Yves Chibon 4a8cb4
Pierre-Yves Chibon 4a8cb4
    return flask.redirect(flask.url_for(
Pierre-Yves Chibon 4a8cb4
        'view_issue', username=username, repo=repo.name, issueid=issueid))
Pierre-Yves Chibon fccfc5
Pierre-Yves Chibon fccfc5
Pierre-Yves Chibon de0eea
@APP.route('/<repo>/tag/<tag>/edit', methods=('GET', 'POST'))</tag></repo>
Pierre-Yves Chibon de0eea
@APP.route('/fork/<username>/<repo>/tag/<tag>/edit', methods=('GET', 'POST'))</tag></repo></username>
Pierre-Yves Chibon de0eea
@cla_required
Pierre-Yves Chibon de0eea
def edit_tag(repo, tag, username=None):
Pierre-Yves Chibon de0eea
    """ Edit the specified tag of a project.
Pierre-Yves Chibon de0eea
    """
Pierre-Yves Chibon de0eea
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon de0eea
Pierre-Yves Chibon de0eea
    if not repo:
Pierre-Yves Chibon de0eea
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon de0eea
Pierre-Yves Chibon de0eea
    if not is_repo_admin(repo):
Pierre-Yves Chibon de0eea
        flask.abort(
Pierre-Yves Chibon de0eea
            403,
Pierre-Yves Chibon de0eea
            'You are not allowed to add users to this project')
Pierre-Yves Chibon de0eea
Pierre-Yves Chibon de0eea
    form = progit.forms.AddIssueTagForm()
Pierre-Yves Chibon de0eea
    if form.validate_on_submit():
Pierre-Yves Chibon de0eea
        new_tag = form.tag.data
Pierre-Yves Chibon de0eea
Pierre-Yves Chibon de0eea
        msgs = progit.lib.edit_issue_tags(SESSION, repo, tag, new_tag)
Pierre-Yves Chibon de0eea
Pierre-Yves Chibon be12c2
        try:
Pierre-Yves Chibon be12c2
            SESSION.commit()
Pierre-Yves Chibon be12c2
            for msg in msgs:
Pierre-Yves Chibon be12c2
                flask.flash(msg)
Pierre-Yves Chibon be12c2
        except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon be12c2
            SESSION.rollback()
Pierre-Yves Chibon be12c2
            LOG.error(err)
Pierre-Yves Chibon e04c77
            flask.flash('Could not edit tag: %s' % tag, 'error')
Pierre-Yves Chibon de0eea
Pierre-Yves Chibon de0eea
        return flask.redirect(flask.url_for(
Pierre-Yves Chibon de0eea
            '.view_settings', repo=repo.name, username=username)
Pierre-Yves Chibon de0eea
        )
Pierre-Yves Chibon de0eea
Pierre-Yves Chibon de0eea
    return flask.render_template(
Pierre-Yves Chibon de0eea
        'edit_tag.html',
Pierre-Yves Chibon de0eea
        form=form,
Pierre-Yves Chibon de0eea
        username=username,
Pierre-Yves Chibon de0eea
        repo=repo,
Pierre-Yves Chibon de0eea
        tag=tag,
Pierre-Yves Chibon de0eea
    )
Pierre-Yves Chibon de0eea
Pierre-Yves Chibon 048944
Pierre-Yves Chibon 048944
@APP.route('/<repo>/droptag/', methods=['POST'])</repo>
Pierre-Yves Chibon 048944
@APP.route('/fork/<username>/<repo>/droptag/', methods=['POST'])</repo></username>
Pierre-Yves Chibon 048944
@cla_required
Pierre-Yves Chibon 048944
def remove_tag(repo, username=None):
Pierre-Yves Chibon 048944
    """ Remove the specified tag from the project.
Pierre-Yves Chibon 048944
    """
Pierre-Yves Chibon 048944
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon 048944
Pierre-Yves Chibon 048944
    if not repo:
Pierre-Yves Chibon 048944
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon 048944
Pierre-Yves Chibon 048944
    if not is_repo_admin(repo):
Pierre-Yves Chibon 048944
        flask.abort(
Pierre-Yves Chibon 048944
            403,
Pierre-Yves Chibon 048944
            'You are not allowed to change the users for this project')
Pierre-Yves Chibon 048944
Pierre-Yves Chibon 048944
    form = progit.forms.AddIssueTagForm()
Pierre-Yves Chibon 048944
    if form.validate_on_submit():
Pierre-Yves Chibon 048944
        tags = form.tag.data
Pierre-Yves Chibon 048944
        tags = [tag.strip() for tag in tags.split(',')]
Pierre-Yves Chibon 048944
Pierre-Yves Chibon 5ea1e2
        msgs = progit.lib.remove_tags(SESSION, repo, tags)
Pierre-Yves Chibon 048944
Pierre-Yves Chibon 7aa682
        try:
Pierre-Yves Chibon 7aa682
            SESSION.commit()
Pierre-Yves Chibon 7aa682
            for msg in msgs:
Pierre-Yves Chibon 7aa682
                flask.flash(msg)
Pierre-Yves Chibon 7aa682
        except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon 7aa682
            SESSION.rollback()
Pierre-Yves Chibon 7aa682
            LOG.error(err)
Pierre-Yves Chibon 7aa682
            flask.flash(
Pierre-Yves Chibon 7aa682
                'Could not remove tag: %s' % ','.join(tags), 'error')
Pierre-Yves Chibon 048944
Pierre-Yves Chibon 048944
    return flask.redirect(
Pierre-Yves Chibon 048944
        flask.url_for('.view_settings', repo=repo.name, username=username)
Pierre-Yves Chibon 048944
    )
Pierre-Yves Chibon 048944
Pierre-Yves Chibon e04c77
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 a8ec77
    tags = flask.request.args.getlist('tags')
Pierre-Yves Chibon a8ec77
    tags = [tag.strip() for tag in tags if tag.strip()]
Pierre-Yves Chibon 1f7038
    assignee = flask.request.args.get('assignee', None)
Pierre-Yves Chibon 2604db
    author = flask.request.args.get('author', None)
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 c8c669
    # Hide private tickets
Pierre-Yves Chibon c8c669
    private = False
Pierre-Yves Chibon c8c669
    # If user is authenticated, show him/her his/her private tickets
Pierre-Yves Chibon c8c669
    if authenticated():
Pierre-Yves Chibon c8c669
        private = flask.g.fas_user.username
Pierre-Yves Chibon c8c669
    # If user is repo admin, show all tickets included the private ones
Pierre-Yves Chibon c8c669
    if is_repo_admin(repo):
Pierre-Yves Chibon c8c669
        private = None
Pierre-Yves Chibon c8c669
Pierre-Yves Chibon 556a8e
    if status is not None:
Pierre-Yves Chibon 556a8e
        if status.lower() == 'closed':
Pierre-Yves Chibon 3b7b2b
            issues = progit.lib.search_issues(
Pierre-Yves Chibon 1f7038
                SESSION,
Pierre-Yves Chibon 1f7038
                repo,
Pierre-Yves Chibon 1f7038
                closed=True,
Pierre-Yves Chibon 1f7038
                tags=tags,
Pierre-Yves Chibon 1f7038
                assignee=assignee,
Pierre-Yves Chibon 2604db
                author=author,
Pierre-Yves Chibon c8c669
                private=private,
Pierre-Yves Chibon 1f7038
            )
Pierre-Yves Chibon 556a8e
        else:
Pierre-Yves Chibon 3b7b2b
            issues = progit.lib.search_issues(
Pierre-Yves Chibon 1f7038
                SESSION,
Pierre-Yves Chibon 1f7038
                repo,
Pierre-Yves Chibon 1f7038
                status=status,
Pierre-Yves Chibon 1f7038
                tags=tags,
Pierre-Yves Chibon 1f7038
                assignee=assignee,
Pierre-Yves Chibon 2604db
                author=author,
Pierre-Yves Chibon c8c669
                private=private,
Pierre-Yves Chibon 1f7038
            )
Pierre-Yves Chibon 556a8e
    else:
Pierre-Yves Chibon 3b7b2b
        issues = progit.lib.search_issues(
Pierre-Yves Chibon 2604db
            SESSION, repo, status='Open', tags=tags, assignee=assignee,
Pierre-Yves Chibon c8c669
            author=author, private=private)
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 a8ec77
        tag_list=tag_list,
Pierre-Yves Chibon 556a8e
        status=status,
Pierre-Yves Chibon 556a8e
        issues=issues,
Pierre-Yves Chibon 15f6e9
        tags=tags,
Pierre-Yves Chibon a8ec77
        assignee=assignee,
Pierre-Yves Chibon a8ec77
        author=author,
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 a5090c
        private = form.private.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 a5090c
                private=private or False,
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 fa46bf
@APP.route('/<repo>/issue/<int:issueid>')</int:issueid></repo>
Pierre-Yves Chibon fa46bf
@APP.route('/fork/<username>/<repo>/issue/<int:issueid>')</int:issueid></repo></username>
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 d054da
    issue = progit.lib.search_issues(SESSION, repo, issueid=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 b9a6f7
    if issue.private and not is_repo_admin(repo) \
Pierre-Yves Chibon ca45e3
            and (
Pierre-Yves Chibon ca45e3
                not authenticated() or
Pierre-Yves Chibon ca45e3
                not issue.user.user == flask.g.fas_user.username):
Pierre-Yves Chibon b9a6f7
        flask.abort(
Pierre-Yves Chibon b9a6f7
            403, 'This issue is private and you are not allowed to view it')
Pierre-Yves Chibon b9a6f7
Pierre-Yves Chibon 47950c
    status = progit.lib.get_issue_statuses(SESSION)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon ea16af
    form = progit.forms.UpdateIssueForm(status=status)
Pierre-Yves Chibon 059d5c
    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 424d2c
        repo_admin=is_repo_admin(repo),
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 d054da
    issue = progit.lib.search_issues(SESSION, repo, issueid=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 be1ede
        private = form.private.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 be1ede
                private=private,
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 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 b93312
        form.private.data = issue.private
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
    )
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
@APP.route('/<repo>/issue/<int:issueid>/upload', methods=['POST'])</int:issueid></repo>
Pierre-Yves Chibon ccbeb3
@APP.route('/fork/<username>/<repo>/issue/<int:issueid>/upload',</int:issueid></repo></username>
Pierre-Yves Chibon ccbeb3
           methods=['POST'])
Pierre-Yves Chibon ccbeb3
@cla_required
Pierre-Yves Chibon ccbeb3
def upload_issue(repo, issueid, username=None):
Pierre-Yves Chibon ccbeb3
    ''' Upload a file to a ticket.
Pierre-Yves Chibon ccbeb3
    '''
Pierre-Yves Chibon ccbeb3
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    if repo is None:
Pierre-Yves Chibon ccbeb3
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    if not repo.issue_tracker:
Pierre-Yves Chibon ccbeb3
        flask.abort(404, 'No issue tracker found for this project')
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    if not is_repo_admin(repo):
Pierre-Yves Chibon ccbeb3
        flask.abort(
Pierre-Yves Chibon ccbeb3
            403, 'You are not allowed to edit issues for this project')
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    issue = progit.lib.search_issues(SESSION, repo, issueid=issueid)
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    if issue is None or issue.project != repo:
Pierre-Yves Chibon ccbeb3
        flask.abort(404, 'Issue not found')
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    form = progit.forms.UploadFileForm()
Pierre-Yves Chibon ccbeb3
    # pylint: disable=E1101
Pierre-Yves Chibon ccbeb3
    if form.validate_on_submit():
Pierre-Yves Chibon ccbeb3
        filestream = flask.request.files['filestream']
Pierre-Yves Chibon ccbeb3
        new_filename = progit.lib.git.add_file_to_git(
Pierre-Yves Chibon ccbeb3
            repo=repo,
Pierre-Yves Chibon ccbeb3
            issue=issue,
Pierre-Yves Chibon ccbeb3
            ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon ccbeb3
            user=flask.g.fas_user,
Pierre-Yves Chibon ccbeb3
            filename=filestream.filename,
Pierre-Yves Chibon ccbeb3
            filestream=filestream.stream,
Pierre-Yves Chibon ccbeb3
        )
Pierre-Yves Chibon ccbeb3
        return flask.jsonify({
Pierre-Yves Chibon ccbeb3
            'output': 'ok',
Pierre-Yves Chibon ccbeb3
            'filename': new_filename.split('-', 1)[1],
Pierre-Yves Chibon ccbeb3
            'filelocation': flask.url_for(
Pierre-Yves Chibon ccbeb3
                'view_issue_raw_file',
Pierre-Yves Chibon ccbeb3
                repo=repo.name,
Pierre-Yves Chibon ccbeb3
                username=username,
Pierre-Yves Chibon ccbeb3
                filename=new_filename,
Pierre-Yves Chibon ccbeb3
            )
Pierre-Yves Chibon ccbeb3
        })
Pierre-Yves Chibon ccbeb3
    else:
Pierre-Yves Chibon ccbeb3
        return flask.jsonify({'output': 'notok'})
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
@APP.route('/<repo>/issue/raw/<path:filename>')</path:filename></repo>
Pierre-Yves Chibon ccbeb3
@APP.route('/fork/<username>/<repo>/issue/raw/<path:filename>')</path:filename></repo></username>
Pierre-Yves Chibon ccbeb3
def view_issue_raw_file(repo, filename=None, username=None):
Pierre-Yves Chibon ccbeb3
    """ Displays the raw content of a file of a commit for the specified
Pierre-Yves Chibon ccbeb3
    ticket repo.
Pierre-Yves Chibon ccbeb3
    """
Pierre-Yves Chibon ccbeb3
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    if not repo:
Pierre-Yves Chibon ccbeb3
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    reponame = os.path.join(APP.config['TICKETS_FOLDER'], repo.path)
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    repo_obj = pygit2.Repository(reponame)
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    if repo_obj.is_empty:
Pierre-Yves Chibon ccbeb3
        flask.abort(404, 'Empty repo cannot have a file')
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    branch = repo_obj.lookup_branch('master')
Pierre-Yves Chibon ccbeb3
    commit = branch.get_object()
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    mimetype = None
Pierre-Yves Chibon ccbeb3
    encoding = None
Pierre-Yves Chibon ccbeb3
    if filename:
Pierre-Yves Chibon ccbeb3
        content = __get_file_in_tree(
Pierre-Yves Chibon ccbeb3
            repo_obj, commit.tree, filename.split('/'))
Pierre-Yves Chibon ccbeb3
        if not content or isinstance(content, pygit2.Tree):
Pierre-Yves Chibon ccbeb3
            flask.abort(404, 'File not found')
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
        mimetype, encoding = mimetypes.guess_type(filename)
Pierre-Yves Chibon ccbeb3
        data = repo_obj[content.oid].data
Pierre-Yves Chibon ccbeb3
    else:
Pierre-Yves Chibon ccbeb3
        if commit.parents:
Pierre-Yves Chibon ccbeb3
            diff = commit.tree.diff_to_tree()
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
            parent = repo_obj.revparse_single('%s^' % identifier)
Pierre-Yves Chibon ccbeb3
            diff = repo_obj.diff(parent, commit)
Pierre-Yves Chibon ccbeb3
        else:
Pierre-Yves Chibon ccbeb3
            # First commit in the repo
Pierre-Yves Chibon ccbeb3
            diff = commit.tree.diff_to_tree(swap=True)
Pierre-Yves Chibon ccbeb3
        data = diff.patch
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    if not mimetype and data[:2] == '#!':
Pierre-Yves Chibon ccbeb3
        mimetype = 'text/plain'
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    if not mimetype:
Pierre-Yves Chibon ccbeb3
        if '\0' in data:
Pierre-Yves Chibon ccbeb3
            mimetype = 'application/octet-stream'
Pierre-Yves Chibon ccbeb3
        else:
Pierre-Yves Chibon ccbeb3
            mimetype = 'text/plain'
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    if mimetype.startswith('text/') and not encoding:
Pierre-Yves Chibon ccbeb3
        encoding = chardet.detect(ktc.to_bytes(data))['encoding']
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    headers = {'Content-Type': mimetype}
Pierre-Yves Chibon ccbeb3
    if encoding:
Pierre-Yves Chibon ccbeb3
        headers['Content-Encoding'] = encoding
Pierre-Yves Chibon ccbeb3
Pierre-Yves Chibon ccbeb3
    return (data, 200, headers)