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

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

 Authors:
   Patrick Uiterwijk <puiterwijk@redhat.com>

"""

import os
import os.path
import shutil

from celery import Celery
from celery.result import AsyncResult

import pygit2
import tempfile
import six

import logging

import pagure
from pagure import APP
import pagure.lib
import pagure.lib.git

_log = logging.getLogger(__name__)


if os.environ.get('PAGURE_BROKER_URL'):
    broker_url = os.environ['PAGURE_BROKER_URL']
else:
    broker_url = 'redis://%s' % APP.config['REDIS_HOST']

conn = Celery('tasks', broker=broker_url, backend=broker_url)
conn.conf.update(APP.config['CELERY_CONFIG'])


def get_result(uuid):
    return AsyncResult(uuid, conn.backend)


def ret(endpoint, **kwargs):
    toret = {'endpoint': endpoint}
    toret.update(kwargs)
    return toret


@conn.task
def generate_gitolite_acls():
    pagure.lib.git._generate_gitolite_acls()


@conn.task
def create_project(username, namespace, name, add_readme,
                   ignore_existing_repo):
    session = pagure.lib.create_session()

    project = pagure.lib._get_project(session, namespace=namespace,
                                      name=name, with_lock=True)
    userobj = pagure.lib.search_user(session, username=username)
    gitrepo = os.path.join(APP.config['GIT_FOLDER'], project.path)

    # Add the readme file if it was asked
    if not add_readme:
        pygit2.init_repository(gitrepo, bare=True)
    else:
        temp_gitrepo_path = tempfile.mkdtemp(prefix='pagure-')
        temp_gitrepo = pygit2.init_repository(temp_gitrepo_path, bare=False)
        author = userobj.fullname or userobj.user
        author_email = userobj.default_email
        if six.PY2:
            author = author.encode('utf-8')
            author_email = author_email.encode('utf-8')
        author = pygit2.Signature(author, author_email)
        content = u"# %s\n\n%s" % (name, project.description)
        readme_file = os.path.join(temp_gitrepo.workdir, "README.md")
        with open(readme_file, 'wb') as stream:
            stream.write(content.encode('utf-8'))
        temp_gitrepo.index.add_all()
        temp_gitrepo.index.write()
        tree = temp_gitrepo.index.write_tree()
        temp_gitrepo.create_commit(
            'HEAD', author, author, 'Added the README', tree, [])
        pygit2.clone_repository(temp_gitrepo_path, gitrepo, bare=True)
        shutil.rmtree(temp_gitrepo_path)

    # Make the repo exportable via apache
    http_clone_file = os.path.join(gitrepo, 'git-daemon-export-ok')
    if not os.path.exists(http_clone_file):
        with open(http_clone_file, 'w') as stream:
            pass

    docrepo = os.path.join(APP.config['DOCS_FOLDER'], project.path)
    if os.path.exists(docrepo):
        if not ignore_existing_repo:
            shutil.rmtree(gitrepo)
            raise pagure.exceptions.RepoExistsException(
                'The docs repo "%s" already exists' % project.path
            )
    else:
        pygit2.init_repository(docrepo, bare=True)

    ticketrepo = os.path.join(APP.config['TICKETS_FOLDER'], project.path)
    if os.path.exists(ticketrepo):
        if not ignore_existing_repo:
            shutil.rmtree(gitrepo)
            shutil.rmtree(docrepo)
            raise pagure.exceptions.RepoExistsException(
                'The tickets repo "%s" already exists' % project.path
            )
    else:
        pygit2.init_repository(
            ticketrepo, bare=True,
            mode=pygit2.C.GIT_REPOSITORY_INIT_SHARED_GROUP)

    requestrepo = os.path.join(APP.config['REQUESTS_FOLDER'], project.path)
    if os.path.exists(requestrepo):
        if not ignore_existing_repo:
            shutil.rmtree(gitrepo)
            shutil.rmtree(docrepo)
            shutil.rmtree(ticketrepo)
            raise pagure.exceptions.RepoExistsException(
                'The requests repo "%s" already exists' % project.path
            )
    else:
        pygit2.init_repository(
            requestrepo, bare=True,
            mode=pygit2.C.GIT_REPOSITORY_INIT_SHARED_GROUP)

    # Install the default hook
    plugin = pagure.lib.plugins.get_plugin('default')
    dbobj = plugin.db_object()
    dbobj.active = True
    dbobj.project_id = project.id
    session.add(dbobj)
    session.flush()
    plugin.set_up(project)
    plugin.install(project, dbobj)
    session.commit()

    session.remove()
    generate_gitolite_acls.delay()
    return ret('view_repo', repo=name, namespace=namespace)


@conn.task
def update_git(name, namespace, user, ticketuid=None, requestuid=None):
    session = pagure.lib.create_session()

    project = pagure.lib._get_project(session, namespace=namespace, name=name,
                                      user=user, with_lock=True)
    if ticketuid is not None:
        obj = pagure.lib.get_issue_by_uid(session, ticketuid)
        folder = APP.config['TICKETS_FOLDER']
    elif requestuid is not None:
        obj = pagure.lib.get_request_by_uid(session, requestuid)
        folder = APP.config['REQUESTS_FOLDER']
    else:
        raise NotImplementedError('No ticket ID or request ID provided')

    if obj is None:
        raise Exception('Unable to find object')

    result = pagure.lib.git._update_git(obj, project, folder)
    session.remove()
    return result


@conn.task
def clean_git(name, namespace, user, ticketuid):
    session = pagure.lib.create_session()

    project = pagure.lib._get_project(session, namespace=namespace, name=name,
                                      user=user, with_lock=True)
    obj = pagure.lib.get_issue_by_uid(session, ticketuid)
    folder = APP.config['TICKETS_FOLDER']

    if obj is None:
        raise Exception('Unable to find object')

    result = pagure.lib.git._clean_git(obj, project, folder)
    session.remove()
    return result


@conn.task
def update_file_in_git(name, namespace, user, branch, branchto, filename,
                       content, message, username, email):
    session = pagure.lib.create_session()

    userobj = pagure.lib.search_user(session, username=username)
    project = pagure.lib._get_project(session, namespace=namespace, name=name,
                                      user=user, with_lock=True)

    pagure.lib.git._update_file_in_git(project, branch, branchto, filename,
                                       content, message, userobj, email)

    session.remove()
    return ret('view_commits', repo=project.name, username=user,
               namespace=namespace, branchname=branchto)


@conn.task
def delete_branch(name, namespace, user, branchname):
    session = pagure.lib.create_session()

    project = pagure.lib._get_project(session, namespace=namespace, name=name,
                                      user=user, with_lock=True)
    repo_obj = pygit2.Repository(pagure.get_repo_path(project))

    try:
        branch = repo_obj.lookup_branch(branchname)
        branch.delete()
    except pygit2.GitError as err:
        _log.exception(err)

    session.remove()
    return ret('view_repo', repo=name, namespace=namespace, username=user)


@conn.task
def fork(name, namespace, user_owner, user_forker, editbranch, editfile):
    session = pagure.lib.create_session()

    repo_from = pagure.lib._get_project(session, namespace=namespace,
                                        name=name, user=user_owner)
    repo_to = pagure.lib._get_project(session, namespace=namespace, name=name,
                                      user=user_forker, with_lock=True)

    reponame = os.path.join(APP.config['GIT_FOLDER'], repo_from.path)
    forkreponame = os.path.join(APP.config['GIT_FOLDER'], repo_to.path)

    frepo = pygit2.clone_repository(reponame, forkreponame, bare=True)
    # Clone all the branches as well
    for branch in frepo.listall_branches(pygit2.GIT_BRANCH_REMOTE):
        branch_obj = frepo.lookup_branch(branch, pygit2.GIT_BRANCH_REMOTE)
        branchname = branch_obj.branch_name.replace(
            branch_obj.remote_name, '', 1)[1:]
        if branchname in frepo.listall_branches(pygit2.GIT_BRANCH_LOCAL):
            continue
        frepo.create_branch(branchname, frepo.get(branch_obj.target.hex))

    # Create the git-daemon-export-ok file on the clone
    http_clone_file = os.path.join(forkreponame, 'git-daemon-export-ok')
    if not os.path.exists(http_clone_file):
        with open(http_clone_file, 'w'):
            pass

    docrepo = os.path.join(APP.config['DOCS_FOLDER'], repo_to.path)
    if os.path.exists(docrepo):
        shutil.rmtree(forkreponame)
        raise pagure.exceptions.RepoExistsException(
            'The docs "%s" already exists' % repo_to.path
        )
    pygit2.init_repository(docrepo, bare=True)

    ticketrepo = os.path.join(APP.config['TICKETS_FOLDER'], repo_to.path)
    if os.path.exists(ticketrepo):
        shutil.rmtree(forkreponame)
        shutil.rmtree(docrepo)
        raise pagure.exceptions.RepoExistsException(
            'The tickets repo "%s" already exists' % repo_to.path
        )
    pygit2.init_repository(
        ticketrepo, bare=True,
        mode=pygit2.C.GIT_REPOSITORY_INIT_SHARED_GROUP)

    requestrepo = os.path.join(APP.config['REQUESTS_FOLDER'], repo_to.path)
    if os.path.exists(requestrepo):
        shutil.rmtree(forkreponame)
        shutil.rmtree(docrepo)
        shutil.rmtree(ticketrepo)
        raise pagure.exceptions.RepoExistsException(
            'The requests repo "%s" already exists' % repo_to.path
        )
    pygit2.init_repository(
        requestrepo, bare=True,
        mode=pygit2.C.GIT_REPOSITORY_INIT_SHARED_GROUP)

    pagure.lib.notify.log(
        repo_to,
        topic='project.forked',
        msg=dict(
            project=repo_to.to_json(public=True),
            agent=user_forker,
        ),
    )

    session.remove()
    generate_gitolite_acls.delay()

    if editfile is None:
        return ret('view_repo', repo=name, namespace=namespace,
                   username=user_forker)
    else:
        return ret('edit_file', repo=name, namespace=namespace,
                   username=user_forker, branchname=editbranch,
                   filename=editfile)


@conn.task
def pull_remote_repo(remote_git, branch_from):
    clonepath = pagure.get_remote_repo_path(remote_git, branch_from,
                                            ignore_non_exist=True)
    pygit2.clone_repository(
        remote_git, clonepath, checkout_branch=branch_from)

    return clonepath


@conn.task
def refresh_pr_cache(name, namespace, user):
    session = pagure.lib.create_session()

    project = pagure.lib._get_project(session, namespace=namespace,
                                      name=name, user=user)

    pagure.lib.reset_status_pull_request(session, project)

    session.remove()


@conn.task
def merge_pull_request(name, namespace, user, requestid, user_merger):
    session = pagure.lib.create_session()

    project = pagure.lib._get_project(session, namespace=namespace,
                                      name=name, user=user, with_lock=True)
    request = pagure.lib.search_pull_requests(
        session, project_id=project.id, requestid=requestid)

    pagure.lib.git.merge_pull_request(
        session, request, user_merger, APP.config['REQUESTS_FOLDER'])

    refresh_pr_cache.delay(name, namespace, user)
    session.remove()
    return ret('view_repo', repo=name, username=user, namespace=namespace)


@conn.task
def add_file_to_git(name, namespace, user, user_attacher, issueuid, filename):
    session = pagure.lib.create_session()

    project = pagure.lib._get_project(session, namespace=namespace,
                                      name=name, user=user)
    issue = pagure.lib.get_issue_by_uid(session, issueuid)

    pagure.lib.git._add_file_to_git(
        project, issue, APP.config['ATTACHMENTS_FOLDER'],
        APP.config['TICKETS_FOLDER'], user_attacher, filename)

    session.remove()