Blame progit/ui/fork.py

Pierre-Yves Chibon 33b534
# -*- coding: utf-8 -*-
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon fac0b1
"""
Pierre-Yves Chibon fac0b1
 (c) 2014 - Copyright Red Hat Inc
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon fac0b1
 Authors:
Pierre-Yves Chibon fac0b1
   Pierre-Yves Chibon <pingou@pingoured.fr></pingou@pingoured.fr>
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon fac0b1
"""
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon fac0b1
import flask
Pierre-Yves Chibon fac0b1
import os
Pierre-Yves Chibon be7090
import shutil
Pierre-Yves Chibon 41558c
import tempfile
Pierre-Yves Chibon fac0b1
from math import ceil
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon fac0b1
import pygit2
Pierre-Yves Chibon fac0b1
from sqlalchemy.exc import SQLAlchemyError
Pierre-Yves Chibon fac0b1
from pygments import highlight
Pierre-Yves Chibon fac0b1
from pygments.lexers import guess_lexer
Pierre-Yves Chibon fac0b1
from pygments.lexers.text import DiffLexer
Pierre-Yves Chibon fac0b1
from pygments.formatters import HtmlFormatter
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon 6bf4e3
import progit.doc_utils
Pierre-Yves Chibon 80bba3
import progit.lib
Pierre-Yves Chibon 118968
import progit.lib.git
Pierre-Yves Chibon ac8023
import progit.forms
Pierre-Yves Chibon 1e5fe9
from progit import (APP, SESSION, LOG, __get_file_in_tree, cla_required,
Pierre-Yves Chibon 59be40
                    is_repo_admin, generate_gitolite_acls)
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon 8df273
def _get_repo_path(repo):
Pierre-Yves Chibon 8df273
    """ Return the pat of the git repository corresponding to the provided
Pierre-Yves Chibon 8df273
    Repository object from the DB.
Pierre-Yves Chibon 8df273
    """
Pierre-Yves Chibon 8df273
    if repo.is_fork:
Pierre-Yves Chibon 8df273
        repopath = os.path.join(APP.config['FORK_FOLDER'], repo.path)
Pierre-Yves Chibon 8df273
    else:
Pierre-Yves Chibon 8df273
        repopath = os.path.join(APP.config['GIT_FOLDER'], repo.path)
Pierre-Yves Chibon 8df273
    return repopath
Pierre-Yves Chibon 8df273
Pierre-Yves Chibon 8df273
Pierre-Yves Chibon 8df273
def _get_parent_repo_path(repo):
Pierre-Yves Chibon 8df273
    """ Return the path of the parent git repository corresponding to the
Pierre-Yves Chibon 8df273
    provided Repository object from the DB.
Pierre-Yves Chibon 8df273
    """
Pierre-Yves Chibon 8df273
    if repo.parent:
Pierre-Yves Chibon 8df273
        parentpath = os.path.join(APP.config['GIT_FOLDER'], repo.parent.path)
Pierre-Yves Chibon 8df273
    else:
Pierre-Yves Chibon 8df273
        parentpath = os.path.join(APP.config['GIT_FOLDER'], repo.path)
Pierre-Yves Chibon 8df273
    return parentpath
Pierre-Yves Chibon 8df273
Pierre-Yves Chibon 8df273
Pierre-Yves Chibon b31474
@APP.route('/<repo>/request-pulls')</repo>
Pierre-Yves Chibon b31474
@APP.route('/fork/<username>/<repo>/request-pulls')</repo></username>
Pierre-Yves Chibon b31474
def request_pulls(repo, username=None):
Pierre-Yves Chibon b31474
    """ Request pulling the changes from the fork into the project.
Pierre-Yves Chibon 47950c
    """
Pierre-Yves Chibon b31474
    status = flask.request.args.get('status', True)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    if not repo:
Pierre-Yves Chibon 47950c
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon b31474
    if status is False or str(status).lower() == 'closed':
Pierre-Yves Chibon ff8c27
        requests = progit.lib.search_pull_requests(
Pierre-Yves Chibon b31474
            SESSION, project_id=repo.id, status=False)
Pierre-Yves Chibon 47950c
    else:
Pierre-Yves Chibon ff8c27
        requests = progit.lib.search_pull_requests(
Pierre-Yves Chibon b31474
            SESSION, project_id=repo.id, status=status)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon b31474
    return flask.render_template(
Pierre-Yves Chibon b31474
        'requests.html',
Pierre-Yves Chibon b31474
        select='requests',
Pierre-Yves Chibon b31474
        repo=repo,
Pierre-Yves Chibon b31474
        username=username,
Pierre-Yves Chibon b31474
        requests=requests,
Pierre-Yves Chibon b31474
        status=status,
Pierre-Yves Chibon b31474
    )
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon c33710
@APP.route('/<repo>/request-pull/<int:requestid>')</int:requestid></repo>
Pierre-Yves Chibon c33710
@APP.route('/fork/<username>/<repo>/request-pull/<int:requestid>')</int:requestid></repo></username>
Pierre-Yves Chibon b31474
def request_pull(repo, requestid, username=None):
Pierre-Yves Chibon 47950c
    """ Request pulling the changes from the fork into the project.
Pierre-Yves Chibon 47950c
    """
Pierre-Yves Chibon b31474
Pierre-Yves Chibon 47950c
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    if not repo:
Pierre-Yves Chibon 47950c
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon ff8c27
    request = progit.lib.search_pull_requests(
Pierre-Yves Chibon 47950c
        SESSION, project_id=repo.id, requestid=requestid)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    if not request:
Pierre-Yves Chibon 47950c
        flask.abort(404, 'Pull-request not found')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 0784c9
    repo_from = request.repo_from
Pierre-Yves Chibon 8df273
    repopath = _get_repo_path(repo_from)
Pierre-Yves Chibon 47950c
    repo_obj = pygit2.Repository(repopath)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 8df273
    parentpath = _get_parent_repo_path(repo_from)
Pierre-Yves Chibon 8df273
    orig_repo = pygit2.Repository(parentpath)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon e31131
    branch = repo_obj.lookup_branch(request.branch_from)
Pierre-Yves Chibon e31131
    commitid = branch.get_object().hex
Pierre-Yves Chibon e31131
Pierre-Yves Chibon 47950c
    diff_commits = []
Pierre-Yves Chibon e31131
    diff = None
Pierre-Yves Chibon 47950c
    if not repo_obj.is_empty and not orig_repo.is_empty:
Pierre-Yves Chibon 2c9e9f
        orig_commit = orig_repo[
Pierre-Yves Chibon e31131
            orig_repo.lookup_branch(request.branch).get_object().hex]
Pierre-Yves Chibon 2c9e9f
Pierre-Yves Chibon afd10d
        # Closed pull-request
Pierre-Yves Chibon f986ce
        if request.status is False:
Pierre-Yves Chibon f986ce
            commitid = request.commit_stop
Pierre-Yves Chibon afd10d
            for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_TIME):
Pierre-Yves Chibon afd10d
                diff_commits.append(commit)
Pierre-Yves Chibon afd10d
                if commit.oid.hex == request.commit_start:
Pierre-Yves Chibon afd10d
                    break
Pierre-Yves Chibon afd10d
        # Pull-request open
Pierre-Yves Chibon afd10d
        else:
Pierre-Yves Chibon afd10d
            master_commits = [
Pierre-Yves Chibon afd10d
                commit.oid.hex
Pierre-Yves Chibon afd10d
                for commit in orig_repo.walk(
Pierre-Yves Chibon afd10d
                    orig_repo.lookup_branch(request.branch).get_object().hex,
Pierre-Yves Chibon afd10d
                    pygit2.GIT_SORT_TIME)
Pierre-Yves Chibon afd10d
            ]
Pierre-Yves Chibon afd10d
            for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_TIME):
Pierre-Yves Chibon afd10d
                if request.status and commit.oid.hex in master_commits:
Pierre-Yves Chibon afd10d
                    break
Pierre-Yves Chibon afd10d
                diff_commits.append(commit)
Pierre-Yves Chibon afd10d
Pierre-Yves Chibon afd10d
            if request.status:
Pierre-Yves Chibon afd10d
                first_commit = repo_obj[diff_commits[-1].oid.hex]
Pierre-Yves Chibon afd10d
                request.commit_start = first_commit.oid.hex
Pierre-Yves Chibon afd10d
                request.commit_stop = diff_commits[0].oid.hex
Pierre-Yves Chibon afd10d
                SESSION.add(request)
Pierre-Yves Chibon 1a6537
                try:
Pierre-Yves Chibon 1a6537
                    SESSION.commit()
Pierre-Yves Chibon 1a6537
                except SQLAlchemyError as err:
Pierre-Yves Chibon 1a6537
                    SESSION.rollback()
Pierre-Yves Chibon 1a6537
                    APP.logger.exception(err)
Pierre-Yves Chibon 1a6537
                    flask.flash(
Pierre-Yves Chibon 1a6537
                        'Could not update this pull-request in the database',
Pierre-Yves Chibon 1a6537
                        'error')
Pierre-Yves Chibon e31131
Pierre-Yves Chibon e31131
        if diff_commits:
Pierre-Yves Chibon e31131
            first_commit = repo_obj[diff_commits[-1].oid.hex]
Pierre-Yves Chibon e31131
            diff = repo_obj.diff(
Pierre-Yves Chibon e31131
                repo_obj.revparse_single(first_commit.parents[0].oid.hex),
Pierre-Yves Chibon e31131
                repo_obj.revparse_single(diff_commits[0].oid.hex)
Pierre-Yves Chibon e31131
            )
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    elif orig_repo.is_empty:
Pierre-Yves Chibon 47950c
        orig_commit = None
Pierre-Yves Chibon 60052e
        repo_commit = repo_obj[request.stop_id]
Pierre-Yves Chibon 47950c
        diff = repo_commit.tree.diff_to_tree(swap=True)
Pierre-Yves Chibon 47950c
    else:
Pierre-Yves Chibon 47950c
        flask.flash(
Pierre-Yves Chibon 47950c
            'Fork is empty, there are no commits to request pulling',
Pierre-Yves Chibon 47950c
            'error')
Pierre-Yves Chibon 47950c
        return flask.redirect(flask.url_for(
Pierre-Yves Chibon ea8735
            'view_repo', username=username, repo=repo.name))
Pierre-Yves Chibon 47950c
Johan Cwiklinski fd18bd
    form = progit.forms.ConfirmationForm()
Johan Cwiklinski fd18bd
Pierre-Yves Chibon 47950c
    return flask.render_template(
Pierre-Yves Chibon 47950c
        'pull_request.html',
Pierre-Yves Chibon 47950c
        select='requests',
Pierre-Yves Chibon 04ed02
        requestid=requestid,
Pierre-Yves Chibon 47950c
        repo=repo,
Pierre-Yves Chibon 41558c
        username=username,
Pierre-Yves Chibon 87ba70
        pull_request=request,
Pierre-Yves Chibon 2e8ea2
        repo_admin=is_repo_admin(request.repo),
Pierre-Yves Chibon 47950c
        diff_commits=diff_commits,
Pierre-Yves Chibon e31131
        diff=diff,
Johan Cwiklinski fd18bd
        mergeform=form,
Pierre-Yves Chibon 47950c
    )
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon e31131
Pierre-Yves Chibon c33710
@APP.route('/<repo>/request-pull/<int:requestid>.patch')</int:requestid></repo>
Pierre-Yves Chibon c33710
@APP.route('/fork/<username>/<repo>/request-pull/<int:requestid>.patch')</int:requestid></repo></username>
Pierre-Yves Chibon e7de33
def request_pull_patch(repo, requestid, username=None):
Pierre-Yves Chibon e7de33
    """ Returns the commits from the specified pull-request as patches.
Pierre-Yves Chibon e7de33
    """
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon e7de33
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon e7de33
    if not repo:
Pierre-Yves Chibon e7de33
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon ff8c27
    request = progit.lib.search_pull_requests(
Pierre-Yves Chibon e7de33
        SESSION, project_id=repo.id, requestid=requestid)
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon e7de33
    if not request:
Pierre-Yves Chibon e7de33
        flask.abort(404, 'Pull-request not found')
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon 8df273
    repo_from = request.repo_from
Pierre-Yves Chibon 8df273
    repopath = _get_repo_path(repo_from)
Pierre-Yves Chibon e7de33
    repo_obj = pygit2.Repository(repopath)
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon 8df273
    parentpath = _get_parent_repo_path(repo_from)
Pierre-Yves Chibon 8df273
    orig_repo = pygit2.Repository(parentpath)
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon 18e007
    branch = repo_obj.lookup_branch(request.branch_from)
Pierre-Yves Chibon 18e007
    commitid = branch.get_object().hex
Pierre-Yves Chibon 18e007
Pierre-Yves Chibon e7de33
    diff_commits = []
Pierre-Yves Chibon e7de33
    if not repo_obj.is_empty and not orig_repo.is_empty:
Pierre-Yves Chibon e7de33
        orig_commit = orig_repo[
Pierre-Yves Chibon 18e007
            orig_repo.lookup_branch(request.branch).get_object().hex]
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon a8ed59
        # Closed pull-request
Pierre-Yves Chibon a8ed59
        if request.status is False:
Pierre-Yves Chibon a8ed59
            commitid = request.commit_stop
Pierre-Yves Chibon a8ed59
            for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_TIME):
Pierre-Yves Chibon a8ed59
                diff_commits.append(commit)
Pierre-Yves Chibon a8ed59
                if commit.oid.hex == request.commit_start:
Pierre-Yves Chibon a8ed59
                    break
Pierre-Yves Chibon a8ed59
        # Pull-request open
Pierre-Yves Chibon a8ed59
        else:
Pierre-Yves Chibon a8ed59
            master_commits = [
Pierre-Yves Chibon a8ed59
                commit.oid.hex
Pierre-Yves Chibon a8ed59
                for commit in orig_repo.walk(
Pierre-Yves Chibon a8ed59
                    orig_repo.lookup_branch(request.branch).get_object().hex,
Pierre-Yves Chibon a8ed59
                    pygit2.GIT_SORT_TIME)
Pierre-Yves Chibon a8ed59
            ]
Pierre-Yves Chibon a8ed59
            for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_TIME):
Pierre-Yves Chibon a8ed59
                if request.status and commit.oid.hex in master_commits:
Pierre-Yves Chibon a8ed59
                    break
Pierre-Yves Chibon a8ed59
                diff_commits.append(commit)
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon e7de33
    elif orig_repo.is_empty:
Pierre-Yves Chibon e7de33
        orig_commit = None
Pierre-Yves Chibon 60052e
        repo_commit = repo_obj[request.stop_id]
Pierre-Yves Chibon e7de33
        diff = repo_commit.tree.diff_to_tree(swap=True)
Pierre-Yves Chibon e7de33
    else:
Pierre-Yves Chibon e7de33
        flask.flash(
Pierre-Yves Chibon e7de33
            'Fork is empty, there are no commits to request pulling',
Pierre-Yves Chibon e7de33
            'error')
Pierre-Yves Chibon e7de33
        return flask.redirect(flask.url_for(
Pierre-Yves Chibon e7de33
            'view_repo', username=username, repo=repo.name))
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon e7de33
    diff_commits.reverse()
Pierre-Yves Chibon 118968
    patch = progit.lib.git.commit_to_patch(repo_obj, diff_commits)
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon e7de33
    return flask.Response(patch, content_type="text/plain;charset=UTF-8")
Pierre-Yves Chibon e7de33
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon abdb8f
@APP.route('/<repo>/request-pull/<int:requestid>/comment/<commit>/'</commit></int:requestid></repo>
Pierre-Yves Chibon abdb8f
           '<filename>/<row>', methods=('GET', 'POST'))</row></filename>
Pierre-Yves Chibon c33710
@APP.route('/fork/<username>/<repo>/request-pull/<int:requestid>/comment/'</int:requestid></repo></username>
Pierre-Yves Chibon abdb8f
           '<commit>/<filename>/<row>', methods=('GET', 'POST'))</row></filename></commit>
Pierre-Yves Chibon abdb8f
def pull_request_add_comment(repo, requestid, commit, filename, row,
Pierre-Yves Chibon abdb8f
                             username=None):
Pierre-Yves Chibon cb10a1
    """ Add a comment to a commit in a pull-request.
Pierre-Yves Chibon cb10a1
    """
Pierre-Yves Chibon cb10a1
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon cb10a1
Pierre-Yves Chibon cb10a1
    if not repo:
Pierre-Yves Chibon cb10a1
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon cb10a1
Pierre-Yves Chibon ff8c27
    request = progit.lib.search_pull_requests(
Pierre-Yves Chibon cb10a1
        SESSION, project_id=repo.id, requestid=requestid)
Pierre-Yves Chibon cb10a1
    repo = request.repo_from
Pierre-Yves Chibon cb10a1
Pierre-Yves Chibon cb10a1
    if not request:
Pierre-Yves Chibon cb10a1
        flask.abort(404, 'Pull-request not found')
Pierre-Yves Chibon cb10a1
Pierre-Yves Chibon cb10a1
    form = progit.forms.AddPullRequestCommentForm()
Pierre-Yves Chibon cb10a1
    form.commit.data = commit
Pierre-Yves Chibon abdb8f
    form.filename.data = filename
Pierre-Yves Chibon cb10a1
    form.requestid.data = requestid
Pierre-Yves Chibon cb10a1
    form.row.data = row
Pierre-Yves Chibon cb10a1
Pierre-Yves Chibon 248f5e
    if form.validate_on_submit():
Pierre-Yves Chibon 248f5e
        comment = form.comment.data
Pierre-Yves Chibon 248f5e
Pierre-Yves Chibon 248f5e
        try:
Pierre-Yves Chibon 248f5e
            message = progit.lib.add_pull_request_comment(
Pierre-Yves Chibon 248f5e
                SESSION,
Pierre-Yves Chibon 248f5e
                request=request,
Pierre-Yves Chibon 248f5e
                commit=commit,
Pierre-Yves Chibon abdb8f
                filename=filename,
Pierre-Yves Chibon 248f5e
                row=row,
Pierre-Yves Chibon 248f5e
                comment=comment,
Pierre-Yves Chibon 248f5e
                user=flask.g.fas_user.username,
Pierre-Yves Chibon 248f5e
            )
Pierre-Yves Chibon 248f5e
            SESSION.commit()
Pierre-Yves Chibon 248f5e
            flask.flash(message)
Pierre-Yves Chibon 248f5e
        except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon 248f5e
            SESSION.rollback()
Pierre-Yves Chibon 1a6537
            APP.logger.exception(err)
Pierre-Yves Chibon 248f5e
            flask.flash(str(err), 'error')
Pierre-Yves Chibon 248f5e
Pierre-Yves Chibon 248f5e
        return flask.redirect(flask.url_for(
Pierre-Yves Chibon 248f5e
            'request_pull', username=username,
Pierre-Yves Chibon 248f5e
            repo=repo.name, requestid=requestid))
Pierre-Yves Chibon cb10a1
Pierre-Yves Chibon cb10a1
    return flask.render_template(
Pierre-Yves Chibon cb10a1
        'pull_request_comment.html',
Pierre-Yves Chibon cb10a1
        select='requests',
Pierre-Yves Chibon cb10a1
        requestid=requestid,
Pierre-Yves Chibon cb10a1
        repo=repo,
Pierre-Yves Chibon cb10a1
        username=username,
Pierre-Yves Chibon cb10a1
        commit=commit,
Pierre-Yves Chibon abdb8f
        filename=filename,
Pierre-Yves Chibon cb10a1
        row=row,
Pierre-Yves Chibon cb10a1
        form=form,
Pierre-Yves Chibon cb10a1
    )
Pierre-Yves Chibon cb10a1
Pierre-Yves Chibon cb10a1
Pierre-Yves Chibon 57116a
@APP.route('/<repo>/request-pull/<int:requestid>/merge', methods=['POST'])</int:requestid></repo>
Pierre-Yves Chibon 57116a
@APP.route('/fork/<username>/<repo>/request-pull/<int:requestid>/merge',</int:requestid></repo></username>
Pierre-Yves Chibon 0a1731
           methods=['POST'])
Pierre-Yves Chibon b31474
def merge_request_pull(repo, requestid, username=None):
Pierre-Yves Chibon b31474
    """ Request pulling the changes from the fork into the project.
Pierre-Yves Chibon 47950c
    """
Johan Cwiklinski fd18bd
Johan Cwiklinski fd18bd
    form = progit.forms.ConfirmationForm()
Pierre-Yves Chibon 15e950
    if not form.validate_on_submit():
Pierre-Yves Chibon 15e950
        flask.flash('Invalid input submitted', 'error')
Pierre-Yves Chibon 15e950
        return flask.redirect(flask.url_for('view_repo', repo=repo.name))
Johan Cwiklinski fd18bd
Pierre-Yves Chibon 47950c
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
    if not repo:
Pierre-Yves Chibon 47950c
        flask.abort(404, 'Project not found')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon ff8c27
    request = progit.lib.search_pull_requests(
Pierre-Yves Chibon b31474
        SESSION, project_id=repo.id, requestid=requestid)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon b31474
    if not request:
Pierre-Yves Chibon b31474
        flask.abort(404, 'Pull-request not found')
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 4f2df5
    if not is_repo_admin(repo):
Pierre-Yves Chibon 4f2df5
        flask.abort(
Pierre-Yves Chibon 4f2df5
            403,
Pierre-Yves Chibon 4f2df5
            'You are not allowed to merge pull-request for this project')
Pierre-Yves Chibon 4f2df5
Pierre-Yves Chibon b31474
    error_output = flask.url_for(
Pierre-Yves Chibon b31474
        'request_pull', repo=repo.name, requestid=requestid)
Pierre-Yves Chibon b31474
    if username:
Pierre-Yves Chibon b31474
        error_output = flask.url_for(
Pierre-Yves Chibon b31474
            'fork_request_pull',
Pierre-Yves Chibon b31474
            repo=repo.name,
Pierre-Yves Chibon b31474
            requestid=requestid,
Pierre-Yves Chibon b31474
            username=username)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon b31474
    # Get the fork
Pierre-Yves Chibon 2c9e9f
    if request.repo_from.is_fork:
Pierre-Yves Chibon 2c9e9f
        repopath = os.path.join(
Pierre-Yves Chibon 2c9e9f
            APP.config['FORK_FOLDER'], request.repo_from.path)
Pierre-Yves Chibon 2c9e9f
    else:
Pierre-Yves Chibon 2c9e9f
        repopath = os.path.join(
Pierre-Yves Chibon 2c9e9f
            APP.config['GIT_FOLDER'], request.repo_from.path)
Pierre-Yves Chibon b31474
    fork_obj = pygit2.Repository(repopath)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon b31474
    # Get the original repo
Pierre-Yves Chibon b31474
    parentpath = os.path.join(APP.config['GIT_FOLDER'], request.repo.path)
Pierre-Yves Chibon b31474
    orig_repo = pygit2.Repository(parentpath)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon b31474
    # Clone the original repo into a temp folder
Pierre-Yves Chibon b31474
    newpath = tempfile.mkdtemp()
Pierre-Yves Chibon b31474
    new_repo = pygit2.clone_repository(parentpath, newpath)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon a2dd31
    repo_commit = fork_obj[
Pierre-Yves Chibon a2dd31
        fork_obj.lookup_branch(request.branch_from).get_object().hex]
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon b31474
    ori_remote = new_repo.remotes[0]
Pierre-Yves Chibon b31474
    # Add the fork as remote repo
Pierre-Yves Chibon e5d251
    reponame = '%s_%s' % (request.user.user, repo.name)
Pierre-Yves Chibon b31474
    remote = new_repo.create_remote(reponame, repopath)
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon b31474
    # Fetch the commits
Pierre-Yves Chibon b31474
    remote.fetch()
Pierre-Yves Chibon b31474
Pierre-Yves Chibon b31474
    merge = new_repo.merge(repo_commit.oid)
Pierre-Yves Chibon a2dd31
    if merge is None:
Pierre-Yves Chibon a2dd31
        mergecode, prefcode = new_repo.merge_analysis(repo_commit.oid)
Pierre-Yves Chibon a2dd31
Pierre-Yves Chibon a2dd31
    try:
Pierre-Yves Chibon a2dd31
        branch_ref = new_repo.lookup_reference(
Pierre-Yves Chibon a2dd31
            request.branch).resolve()
Pierre-Yves Chibon a2dd31
    except ValueError:
Pierre-Yves Chibon a2dd31
        branch_ref = new_repo.lookup_reference(
Pierre-Yves Chibon a2dd31
            'refs/heads/%s' % request.branch).resolve()
Pierre-Yves Chibon a2dd31
Pierre-Yves Chibon a2dd31
    refname = '%s:%s' % (branch_ref.name, branch_ref.name)
Pierre-Yves Chibon e04c77
    if (
Pierre-Yves Chibon e04c77
            (merge is not None and merge.is_uptodate)
Pierre-Yves Chibon a2dd31
            or
Pierre-Yves Chibon e04c77
            (merge is None and
Pierre-Yves Chibon e04c77
             mergecode & pygit2.GIT_MERGE_ANALYSIS_UP_TO_DATE
Pierre-Yves Chibon e04c77
             )):
Pierre-Yves Chibon d5c14e
        flask.flash('Nothing to do, changes were already merged', 'error')
Pierre-Yves Chibon cf13ff
        progit.lib.close_pull_request(SESSION, request, flask.g.fas_user)
Pierre-Yves Chibon 1a6537
        try:
Pierre-Yves Chibon 1a6537
            SESSION.commit()
Pierre-Yves Chibon 1a6537
        except SQLAlchemyError as err:
Pierre-Yves Chibon 1a6537
            SESSION.rollback()
Pierre-Yves Chibon 1a6537
            APP.logger.exception(err)
Pierre-Yves Chibon 1a6537
            flask.flash('Could not close this pull-request', 'error')
Pierre-Yves Chibon d5c14e
        return flask.redirect(error_output)
Pierre-Yves Chibon e04c77
    elif (
Pierre-Yves Chibon e04c77
            (merge is not None and merge.is_fastforward)
Pierre-Yves Chibon e04c77
            or
Pierre-Yves Chibon e04c77
            (merge is None and
Pierre-Yves Chibon e04c77
             mergecode & pygit2.GIT_MERGE_ANALYSIS_FASTFORWARD
Pierre-Yves Chibon e04c77
             )):
Pierre-Yves Chibon a2dd31
        if merge is not None:
Pierre-Yves Chibon a2dd31
            branch_ref.target = merge.fastforward_oid
Pierre-Yves Chibon a2dd31
            sha = merge.fastforward_oid
Pierre-Yves Chibon a2dd31
        elif merge is None and mergecode is not None:
Pierre-Yves Chibon a2dd31
            print repo_commit.oid
Pierre-Yves Chibon a2dd31
            branch_ref.set_target(repo_commit.oid.hex)
Pierre-Yves Chibon a2dd31
            sha = branch_ref.target
Pierre-Yves Chibon b31474
        ori_remote.push(refname)
Pierre-Yves Chibon b31474
        flask.flash('Changes merged!')
Pierre-Yves Chibon a2dd31
Pierre-Yves Chibon b31474
    else:
Pierre-Yves Chibon 59b3a3
        new_repo.index.write()
Pierre-Yves Chibon 2c9e9f
        try:
Pierre-Yves Chibon 2c9e9f
            tree = new_repo.index.write_tree()
Pierre-Yves Chibon 2c9e9f
        except pygit2.GitError:
Pierre-Yves Chibon be7090
            shutil.rmtree(newpath)
Pierre-Yves Chibon 2c9e9f
            flask.flash('Merge conflicts!', 'error')
Pierre-Yves Chibon 2c9e9f
            return flask.redirect(flask.url_for(
Pierre-Yves Chibon 2c9e9f
                'request_pull',
Pierre-Yves Chibon 2c9e9f
                repo=repo.name,
Pierre-Yves Chibon 2c9e9f
                username=username,
Pierre-Yves Chibon 2c9e9f
                requestid=requestid))
Pierre-Yves Chibon 59b3a3
        head = new_repo.lookup_reference('HEAD').get_object()
Pierre-Yves Chibon 59b3a3
        commit = new_repo[head.oid]
Pierre-Yves Chibon 59b3a3
        sha = new_repo.create_commit(
Pierre-Yves Chibon 59b3a3
            'refs/heads/master',
Pierre-Yves Chibon 59b3a3
            repo_commit.author,
Pierre-Yves Chibon 59b3a3
            repo_commit.committer,
Pierre-Yves Chibon 59b3a3
            'Merge #%s `%s`' % (request.id, request.title),
Pierre-Yves Chibon 59b3a3
            tree,
Pierre-Yves Chibon 59b3a3
            [head.hex, repo_commit.oid.hex])
Pierre-Yves Chibon 59b3a3
        ori_remote.push(refname)
Pierre-Yves Chibon 59b3a3
        flask.flash('Changes merged!')
Pierre-Yves Chibon b31474
Pierre-Yves Chibon b31474
    # Update status
Pierre-Yves Chibon f7606b
    progit.lib.close_pull_request(SESSION, request, flask.g.fas_user)
Pierre-Yves Chibon 1a6537
    try:
Pierre-Yves Chibon 1a6537
        SESSION.commit()
Pierre-Yves Chibon 1a6537
    except SQLAlchemyError as err:
Pierre-Yves Chibon 1a6537
        SESSION.rollback()
Pierre-Yves Chibon 1a6537
        APP.logger.exception(err)
Pierre-Yves Chibon 1a6537
        flask.flash(
Pierre-Yves Chibon 1a6537
            'Could not update this pull-request in the database',
Pierre-Yves Chibon 1a6537
            'error')
Pierre-Yves Chibon be7090
    shutil.rmtree(newpath)
Pierre-Yves Chibon b31474
Pierre-Yves Chibon b31474
    return flask.redirect(flask.url_for('view_repo', repo=repo.name))
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon c33710
@APP.route('/<repo>/request-pull/cancel/<int:requestid>',</int:requestid></repo>
Pierre-Yves Chibon e04c77
           methods=['POST'])
Pierre-Yves Chibon c33710
@APP.route('/fork/<username>/<repo>/request-pull/cancel/<int:requestid>',</int:requestid></repo></username>
Pierre-Yves Chibon e04c77
           methods=['POST'])
Johan Cwiklinski 86d9c4
def cancel_request_pull(repo, requestid, username=None):
Johan Cwiklinski 86d9c4
    """ Cancel request pulling request.
Johan Cwiklinski 86d9c4
    """
Johan Cwiklinski fd18bd
Johan Cwiklinski fd18bd
    form = progit.forms.ConfirmationForm()
Pierre-Yves Chibon 15e950
    if form.validate_on_submit():
Johan Cwiklinski fd18bd
Pierre-Yves Chibon 15e950
        repo = progit.lib.get_project(SESSION, repo, user=username)
Johan Cwiklinski 86d9c4
Pierre-Yves Chibon 15e950
        if not repo:
Pierre-Yves Chibon 15e950
            flask.abort(404, 'Project not found')
Johan Cwiklinski 86d9c4
Pierre-Yves Chibon ff8c27
        request = progit.lib.search_pull_requests(
Pierre-Yves Chibon 15e950
            SESSION, project_id=repo.id, requestid=requestid)
Johan Cwiklinski 86d9c4
Pierre-Yves Chibon 15e950
        if not request:
Pierre-Yves Chibon 15e950
            flask.abort(404, 'Pull-request not found')
Johan Cwiklinski 86d9c4
Pierre-Yves Chibon 15e950
        if not is_repo_admin(repo):
Pierre-Yves Chibon 15e950
            flask.abort(
Pierre-Yves Chibon 15e950
                403,
Pierre-Yves Chibon 15e950
                'You are not allowed to cancel pull-request for this project')
Johan Cwiklinski 86d9c4
Pierre-Yves Chibon f7606b
        progit.lib.close_pull_request(
Pierre-Yves Chibon f7606b
            SESSION, request, flask.g.fas_user, merged=False)
Pierre-Yves Chibon d8fde8
        try:
Pierre-Yves Chibon d8fde8
            SESSION.commit()
Pierre-Yves Chibon d8fde8
            flask.flash('Request pull canceled!')
Pierre-Yves Chibon d8fde8
        except SQLAlchemyError as err:
Pierre-Yves Chibon d8fde8
            SESSION.rollback()
Pierre-Yves Chibon d8fde8
            APP.logger.exception(err)
Pierre-Yves Chibon d8fde8
            flask.flash(
Pierre-Yves Chibon d8fde8
                'Could not update this pull-request in the database',
Pierre-Yves Chibon d8fde8
                'error')
Pierre-Yves Chibon 15e950
    else:
Pierre-Yves Chibon 15e950
        flask.flash('Invalid input submitted', 'error')
Johan Cwiklinski 86d9c4
Johan Cwiklinski 86d9c4
    return flask.redirect(flask.url_for('view_repo', repo=repo.name))
Johan Cwiklinski 86d9c4
Johan Cwiklinski 86d9c4
Pierre-Yves Chibon e04c77
# Specific actions
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon 47950c
Pierre-Yves Chibon e03e84
@APP.route('/do_fork/<repo>')</repo>
Pierre-Yves Chibon e03e84
@APP.route('/do_fork/<username>/<repo>')</repo></username>
Pierre-Yves Chibon d5e3d0
@cla_required
Pierre-Yves Chibon 20ccbb
def fork_project(repo, username=None):
Pierre-Yves Chibon fac0b1
    """ Fork the project specified into the user's namespace
Pierre-Yves Chibon fac0b1
    """
Pierre-Yves Chibon 20ccbb
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon 80bba3
Pierre-Yves Chibon 80bba3
    if repo is None:
Pierre-Yves Chibon fac0b1
        flask.abort(404)
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon 80bba3
    try:
Pierre-Yves Chibon 80bba3
        message = progit.lib.fork_project(
Pierre-Yves Chibon 80bba3
            session=SESSION,
Pierre-Yves Chibon 80bba3
            repo=repo,
Pierre-Yves Chibon e54062
            gitfolder=APP.config['GIT_FOLDER'],
Pierre-Yves Chibon 6160b6
            forkfolder=APP.config['FORK_FOLDER'],
Pierre-Yves Chibon 4b7a7d
            docfolder=APP.config['DOCS_FOLDER'],
Pierre-Yves Chibon a33978
            ticketfolder=APP.config['TICKETS_FOLDER'],
Pierre-Yves Chibon 80bba3
            user=flask.g.fas_user.username)
Pierre-Yves Chibon 80bba3
Pierre-Yves Chibon 80bba3
        SESSION.commit()
Pierre-Yves Chibon 59be40
        generate_gitolite_acls()
Pierre-Yves Chibon 80bba3
        flask.flash(message)
Pierre-Yves Chibon 80bba3
        return flask.redirect(
Pierre-Yves Chibon 792a86
            flask.url_for(
Pierre-Yves Chibon ea8735
                'view_repo',
Pierre-Yves Chibon 792a86
                username=flask.g.fas_user.username,
Pierre-Yves Chibon 792a86
                repo=repo.name)
Pierre-Yves Chibon 80bba3
        )
Pierre-Yves Chibon 80bba3
    except progit.exceptions.ProgitException, err:
Pierre-Yves Chibon 80bba3
        flask.flash(str(err), 'error')
Pierre-Yves Chibon 80bba3
    except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon 80bba3
        SESSION.rollback()
Pierre-Yves Chibon 80bba3
        flask.flash(str(err), 'error')
Pierre-Yves Chibon 80bba3
Pierre-Yves Chibon 792a86
    return flask.redirect(flask.url_for('view_repo', repo=repo.name))
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon fac0b1
Pierre-Yves Chibon 6d60d2
@APP.route('/<repo>/diff/<branch_to>..<branch_from>',</branch_from></branch_to></repo>
Pierre-Yves Chibon 2c9e9f
           methods=('GET', 'POST'))
Pierre-Yves Chibon 6d60d2
@APP.route('/fork/<username>/<repo>/diff/<branch_to>..<branch_from>',</branch_from></branch_to></repo></username>
Pierre-Yves Chibon ac8023
           methods=('GET', 'POST'))
Pierre-Yves Chibon d5e3d0
@cla_required
Pierre-Yves Chibon 6d60d2
def new_request_pull(repo,  branch_to, branch_from, username=None):
Pierre-Yves Chibon 82055c
    """ Request pulling the changes from the fork into the project.
Pierre-Yves Chibon 82055c
    """
Pierre-Yves Chibon 82055c
    repo = progit.lib.get_project(SESSION, repo, user=username)
Pierre-Yves Chibon 82055c
Pierre-Yves Chibon 82055c
    if not repo:
Pierre-Yves Chibon 82055c
        flask.abort(404)
Pierre-Yves Chibon 82055c
Pierre-Yves Chibon 1e5fe9
    if not is_repo_admin(repo):
Pierre-Yves Chibon 1e5fe9
        flask.abort(
Pierre-Yves Chibon 1e5fe9
            403,
Pierre-Yves Chibon 1e5fe9
            'You are not allowed to create pull-requests for this project')
Pierre-Yves Chibon 1e5fe9
Pierre-Yves Chibon 8df273
    repopath = _get_repo_path(repo)
Pierre-Yves Chibon 85755c
    repo_obj = pygit2.Repository(repopath)
Pierre-Yves Chibon 82055c
Pierre-Yves Chibon 8df273
    parentpath = _get_parent_repo_path(repo)
Pierre-Yves Chibon 8df273
    orig_repo = pygit2.Repository(parentpath)
Pierre-Yves Chibon 82055c
Pierre-Yves Chibon 6d60d2
    frombranch = repo_obj.lookup_branch(branch_from)
Pierre-Yves Chibon 24542f
    if not frombranch:
Pierre-Yves Chibon 6d60d2
        flask.abort(
Pierre-Yves Chibon 6d60d2
            400,
Pierre-Yves Chibon 6d60d2
            'Branch %s does not exist' % branch_from)
Pierre-Yves Chibon 24542f
Pierre-Yves Chibon 6d60d2
    branch = orig_repo.lookup_branch(branch_to)
Pierre-Yves Chibon efda13
    if not branch:
Pierre-Yves Chibon 6d60d2
        flask.abort(
Pierre-Yves Chibon 6d60d2
            400,
Pierre-Yves Chibon 6d60d2
            'Branch %s could not be found in the target repo' % branch_to)
Pierre-Yves Chibon efda13
Pierre-Yves Chibon 6d60d2
    branch = repo_obj.lookup_branch(branch_from)
Pierre-Yves Chibon 6d60d2
    commitid = branch.get_object().hex
Pierre-Yves Chibon 82055c
Pierre-Yves Chibon 82055c
    diff_commits = []
Pierre-Yves Chibon 82055c
    if not repo_obj.is_empty and not orig_repo.is_empty:
Pierre-Yves Chibon 2c9e9f
        orig_commit = orig_repo[
Pierre-Yves Chibon 6d60d2
            orig_repo.lookup_branch(branch_to).get_object().hex]
Pierre-Yves Chibon 2c9e9f
Pierre-Yves Chibon 2c9e9f
        master_commits = [
Pierre-Yves Chibon 2c9e9f
            commit.oid.hex
Pierre-Yves Chibon 2c9e9f
            for commit in orig_repo.walk(
Pierre-Yves Chibon 6d60d2
                orig_commit.oid.hex, pygit2.GIT_SORT_TIME)
Pierre-Yves Chibon 2c9e9f
        ]
Pierre-Yves Chibon 2c9e9f
Pierre-Yves Chibon 82055c
        repo_commit = repo_obj[commitid]
Pierre-Yves Chibon 82055c
Pierre-Yves Chibon 2c9e9f
        for commit in repo_obj.walk(
Pierre-Yves Chibon 2c9e9f
                repo_commit.oid.hex, pygit2.GIT_SORT_TIME):
Pierre-Yves Chibon 2c9e9f
            if commit.oid.hex in master_commits:
Pierre-Yves Chibon 82055c
                break
Pierre-Yves Chibon 82055c
            diff_commits.append(commit)
Pierre-Yves Chibon 6d60d2
Pierre-Yves Chibon 6d60d2
        first_commit = repo_obj[diff_commits[-1].oid.hex]
Pierre-Yves Chibon 6d60d2
        diff = repo_obj.diff(
Pierre-Yves Chibon 6d60d2
            repo_obj.revparse_single(first_commit.parents[0].oid.hex),
Pierre-Yves Chibon 6d60d2
            repo_obj.revparse_single(diff_commits[0].oid.hex)
Pierre-Yves Chibon 6d60d2
        )
Pierre-Yves Chibon 82055c
Pierre-Yves Chibon 82055c
    elif orig_repo.is_empty:
Pierre-Yves Chibon ac8023
        orig_commit = None
Pierre-Yves Chibon 85755c
        repo_commit = repo_obj[repo_obj.head.target]
Pierre-Yves Chibon 85755c
        diff = repo_commit.tree.diff_to_tree(swap=True)
Pierre-Yves Chibon 82055c
    else:
Pierre-Yves Chibon 82055c
        flask.flash(
Pierre-Yves Chibon 82055c
            'Fork is empty, there are no commits to request pulling',
Pierre-Yves Chibon 82055c
            'error')
Pierre-Yves Chibon 82055c
        return flask.redirect(flask.url_for(
Pierre-Yves Chibon ea8735
            'view_repo', username=username, repo=repo.name))
Pierre-Yves Chibon 82055c
Pierre-Yves Chibon ac8023
    form = progit.forms.RequestPullForm()
Pierre-Yves Chibon ac8023
    if form.validate_on_submit():
Pierre-Yves Chibon ac8023
        try:
Pierre-Yves Chibon ac8023
            if orig_commit:
Pierre-Yves Chibon ac8023
                orig_commit = orig_commit.oid.hex
Pierre-Yves Chibon 2c9e9f
Pierre-Yves Chibon 2c9e9f
            parent = repo
Pierre-Yves Chibon 2c9e9f
            if repo.parent:
Pierre-Yves Chibon 2c9e9f
                parent = repo.parent
Pierre-Yves Chibon 2c9e9f
Pierre-Yves Chibon ac8023
            message = progit.lib.new_pull_request(
Pierre-Yves Chibon ac8023
                SESSION,
Pierre-Yves Chibon 6d60d2
                repo_to=parent,
Pierre-Yves Chibon 6d60d2
                branch_to=branch_to,
Pierre-Yves Chibon 6d60d2
                branch_from=branch_from,
Pierre-Yves Chibon ac8023
                repo_from=repo,
Pierre-Yves Chibon ac8023
                title=form.title.data,
Pierre-Yves Chibon ac8023
                user=flask.g.fas_user.username,
Pierre-Yves Chibon ac8023
            )
Pierre-Yves Chibon d8fde8
            try:
Pierre-Yves Chibon d8fde8
                SESSION.commit()
Pierre-Yves Chibon d8fde8
                flask.flash(message)
Pierre-Yves Chibon d8fde8
            except SQLAlchemyError as err:
Pierre-Yves Chibon d8fde8
                SESSION.rollback()
Pierre-Yves Chibon d8fde8
                APP.logger.exception(err)
Pierre-Yves Chibon d8fde8
                flask.flash(
Pierre-Yves Chibon d8fde8
                    'Could not register this pull-request in the database',
Pierre-Yves Chibon d8fde8
                    'error')
Pierre-Yves Chibon 8399d1
Pierre-Yves Chibon 2c9e9f
            if not parent.is_fork:
Pierre-Yves Chibon 8399d1
                url = flask.url_for(
Pierre-Yves Chibon 2c9e9f
                    'request_pulls', username=None, repo=parent.name)
Pierre-Yves Chibon 8399d1
            else:
Pierre-Yves Chibon 8399d1
                url = flask.url_for(
Pierre-Yves Chibon 2c9e9f
                    'request_pulls', username=parent.user, repo=parent.name)
Pierre-Yves Chibon 8399d1
Pierre-Yves Chibon 8399d1
            return flask.redirect(url)
Pierre-Yves Chibon ac8023
        except progit.exceptions.ProgitException, err:
Pierre-Yves Chibon ac8023
            flask.flash(str(err), 'error')
Pierre-Yves Chibon ac8023
        except SQLAlchemyError, err:  # pragma: no cover
Pierre-Yves Chibon ac8023
            SESSION.rollback()
Pierre-Yves Chibon ac8023
            flask.flash(str(err), 'error')
Pierre-Yves Chibon ac8023
Pierre-Yves Chibon 82055c
    return flask.render_template(
Pierre-Yves Chibon 82055c
        'pull_request.html',
Pierre-Yves Chibon 0c4c0b
        select='requests',
Pierre-Yves Chibon 82055c
        repo=repo,
Pierre-Yves Chibon 82055c
        username=username,
Pierre-Yves Chibon 82055c
        repo_obj=repo_obj,
Pierre-Yves Chibon 82055c
        orig_repo=orig_repo,
Pierre-Yves Chibon 82055c
        diff_commits=diff_commits,
Pierre-Yves Chibon 6d60d2
        diff=diff,
Pierre-Yves Chibon ac8023
        form=form,
Pierre-Yves Chibon 658f7c
        branches=[
Pierre-Yves Chibon 658f7c
            branch.replace('refs/heads/', '')
Pierre-Yves Chibon 658f7c
            for branch in sorted(orig_repo.listall_references())
Pierre-Yves Chibon 658f7c
        ],
Pierre-Yves Chibon 6d60d2
        branch_to=branch_to,
Pierre-Yves Chibon 6d60d2
        branch_from=branch_from,
Pierre-Yves Chibon 82055c
    )