code:
#! /usr/bin/env python2

"""Pagure specific hook to update pull-requests stored in the database
based on the information pushed in the requests git repository.
"""

import json
import os
import re
import sys
import subprocess

# We need to access the database
if 'PAGURE_CONFIG' not in os.environ \
        and os.path.exists('/etc/pagure/pagure.cfg'):
    os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg'

import pagure.lib.git

abspath = os.path.abspath(os.environ['GIT_DIR'])

def get_files_to_load(new_commits_list):

    print 'Files changed by new commits:\n'
    file_list = []
    new_commits_list.reverse()
    for commit in new_commits_list:
        filenames = pagure.lib.git.read_git_lines(
            ['diff-tree', '--no-commit-id', '--name-only', '-r', commit],
            abspath)
        for line in filenames:
            if line.strip():
                file_list.append(line.strip())

    return file_list

def run_as_post_receive_hook():

    file_list = set()
    for line in sys.stdin:
        if pagure.APP.config.get('HOOK_DEBUG', False):
            print line
        (oldrev, newrev, refname) = line.strip().split(' ', 2)

        if pagure.APP.config.get('HOOK_DEBUG', False):
            print '  -- Old rev'
            print oldrev
            print '  -- New rev'
            print newrev
            print '  -- Ref name'
            print refname

        if set(newrev) == set(['0']):
            print "Deleting a reference/branch, so we won't run the "\
                "pagure hook"
            return

        tmp = set(get_files_to_load(
            pagure.lib.git.get_revs_between(oldrev, newrev, abspath, refname)))
        file_list = file_list.union(tmp)

    reponame = pagure.lib.git.get_repo_name(abspath)
    username = pagure.lib.git.get_username(abspath)
    print 'repo:', reponame, username

    for filename in file_list:
        print 'To load: %s' % filename
        json_data = None
        data = ''.join(
            pagure.lib.git.read_git_lines(
                ['show', 'HEAD:%s' % filename], abspath))
        if data:
            try:
                json_data = json.loads(data)
            except:
                pass
        if json_data:
            pagure.lib.git.update_request_from_git(
                pagure.SESSION,
                reponame=reponame,
                username=username,
                request_uid=filename,
                json_data=json_data,
                gitfolder=pagure.APP.config['GIT_FOLDER'],
                docfolder=pagure.APP.config['DOCS_FOLDER'],
                ticketfolder=pagure.APP.config['TICKETS_FOLDER'],
                requestfolder=pagure.APP.config['REQUESTS_FOLDER'],
            )

def main(args):
    run_as_post_receive_hook()

if __name__ == '__main__':
    main(sys.argv[1:])


code:
#! /usr/bin/env python2

"""Pagure specific hook to update tickets stored in the database based on
the information pushed in the tickets git repository.
"""

import json
import os
import re
import sys
import subprocess

# We need to access the database
if 'PAGURE_CONFIG' not in os.environ \
        and os.path.exists('/etc/pagure/pagure.cfg'):
    os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg'

import pagure
import pagure.lib.git

abspath = os.path.abspath(os.environ['GIT_DIR'])

def get_files_to_load(new_commits_list):

    print 'Files changed by new commits:\n'
    file_list = []
    new_commits_list.reverse()
    for commit in new_commits_list:
        filenames = pagure.lib.git.read_git_lines(
            ['diff-tree', '--no-commit-id', '--name-only', '-r', commit],
            abspath)
        for line in filenames:
            if line.strip():
                file_list.append(line.strip())

    return file_list

def run_as_post_receive_hook():

    file_list = set()
    for line in sys.stdin:
        if pagure.APP.config.get('HOOK_DEBUG', False):
            print line
        (oldrev, newrev, refname) = line.strip().split(' ', 2)

        if pagure.APP.config.get('HOOK_DEBUG', False):
            print '  -- Old rev'
            print oldrev
            print '  -- New rev'
            print newrev
            print '  -- Ref name'
            print refname

        if set(newrev) == set(['0']):
            print "Deleting a reference/branch, so we won't run the "\
                "pagure hook"
            return

        tmp = set(get_files_to_load(
            pagure.lib.git.get_revs_between(oldrev, newrev, abspath, refname)))
        file_list = file_list.union(tmp)

    reponame = pagure.lib.git.get_repo_name(abspath)
    username = pagure.lib.git.get_username(abspath)
    print 'repo:', reponame, username

    for filename in file_list:
        print 'To load: %s' % filename
        json_data = None
        data = ''.join(
            pagure.lib.git.read_git_lines(
                ['show', 'HEAD:%s' % filename], abspath))
        if data:
            try:
                json_data = json.loads(data)
            except:
                pass
        if json_data:
            pagure.lib.git.update_ticket_from_git(
                pagure.SESSION,
                reponame=reponame,
                username=username,
                issue_uid=filename,
                json_data=json_data)

def main(args):
    run_as_post_receive_hook()

if __name__ == '__main__':
    main(sys.argv[1:])

repo_name: bill/auger

license: mit


code:
#!/bin/bash
#
# author: orefalo

hookname=`basename $0`

FILE=`mktemp`
trap 'rm -f $FILE' EXIT
cat - > $FILE

for hook in $GIT_DIR/hooks/$hookname.*
do
    if test -x "$hook"; then
        cat $FILE | $hook "$@"
        status=$?

        if test $status -ne 0; then
            echo Hook $hook failed with error code $status
            exit $status
        fi
    fi
done

path: pagure/hooks/files/post-receive


path: pagure/hooks/files/pre-receive


code:
#! /usr/bin/env python2

"""Pagure specific hook to trigger a build on a readthedocs.org project.
"""

import os
import sys

import requests

from sqlalchemy.exc import SQLAlchemyError

if 'PAGURE_CONFIG' not in os.environ \
        and os.path.exists('/etc/pagure/pagure.cfg'):
    os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg'

import pagure
import pagure.exceptions
import pagure.lib.link
import pagure.ui.plugins

abspath = os.path.abspath(os.environ['GIT_DIR'])

def run_as_post_receive_hook():
    reponame = pagure.lib.git.get_repo_name(abspath)
    username = pagure.lib.git.get_username(abspath)
    if pagure.APP.config.get('HOOK_DEBUG', False):
        print 'repo:', reponame
        print 'user:', username

    repo = pagure.lib.get_project(pagure.SESSION, reponame, user=username)
    if not repo:
        print 'Unknown repo %s of username: %s' % (reponame, username)
        sys.exit(1)

    plugin = pagure.ui.plugins.get_plugin('Read the Doc')
    dbobj = plugin.db_object()
    # Get the list of branches
    branches = [
        branch.strip()
        for branch in repo.rtd_hook[0].branches.split(',')
        if repo.rtd_hook]

    # Remove empty branches
    branches = [
        branch.strip()
        for branch in branches
        if branch]

    url = 'http://readthedocs.org/build/%s' % (
        repo.rtd_hook[0].project_name.strip()
    )

    for line in sys.stdin:
        if pagure.APP.config.get('HOOK_DEBUG', False):
            print line
        (oldrev, newrev, refname) = line.strip().split(' ', 2)

        refname = refname.replace('refs/heads/', '')
        if branches:
            if refname in branches:
                print 'Starting RTD build for %s' % (
                    repo.rtd_hook[0].project_name.strip())
                requests.post(url)
        else:
            print 'Starting RTD build for %s' % (
                repo.rtd_hook[0].project_name.strip())
            requests.post(url)

def main(args):
    run_as_post_receive_hook()

if __name__ == '__main__':
    main(sys.argv[1:])


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

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

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

"""

import os

import sqlalchemy as sa
import pygit2
import wtforms
from flask.ext import wtf
from sqlalchemy.orm import relation
from sqlalchemy.orm import backref

from pagure.hooks import BaseHook, RequiredIf
from pagure.lib.model import BASE, Project
from pagure import get_repo_path

class IrcTable(BASE):
    """ Stores information about the irc hook deployed on a project.

    Table -- hook_irc
    """

    __tablename__ = 'hook_irc'

    id = sa.Column(sa.Integer, primary_key=True)
    project_id = sa.Column(
        sa.Integer,
        sa.ForeignKey(
            'projects.id', onupdate='CASCADE', ondelete='CASCADE'),
        nullable=False,
        unique=True,
        index=True)

    server = sa.Column(sa.Text, nullable=False)
    port = sa.Column(sa.Text, nullable=False)
    room = sa.Column(sa.Text, nullable=False)
    nick = sa.Column(sa.Text, nullable=True, default=None)
    nick_pass = sa.Column(sa.Text, nullable=True, default=None)
    active = sa.Column(sa.Boolean, nullable=False, default=False)
    join = sa.Column(sa.Boolean, nullable=False, default=True)
    ssl = sa.Column(sa.Boolean, nullable=False, default=True)

    project = relation(
        'Project', remote_side=[Project.id],
        backref=backref(
            'irc_hook', cascade="delete, delete-orphan",
            single_parent=True)
    )

class IrcForm(wtf.Form):
    ''' Form to configure the irc hook. '''
    server = wtforms.TextField(
        'Server *',
        [RequiredIf('active')]
    )
    port = wtforms.TextField(
        'Port *',
        [RequiredIf('active')]
    )
    room = wtforms.TextField(
        'Room *',
        [RequiredIf('active')]
    )
    nick = wtforms.TextField(
        'Nick',
        [wtforms.validators.Optional()]
    )
    nick_pass = wtforms.TextField(
        'Nickserv Password',
        [wtforms.validators.Optional()]
    )

    active = wtforms.BooleanField(
        'Active',
        [wtforms.validators.Optional()]
    )
    join = wtforms.BooleanField(
        'Message Without Join',
        [wtforms.validators.Optional()]
    )
    ssl = wtforms.BooleanField(
        'Use SSL',
        [wtforms.validators.Optional()]
    )

class Hook(BaseHook):
    ''' IRC hooks. '''

    name = 'IRC'
    description = 'This hook sends message to the mention channel regarding'\
        ' the changes made by the pushes to the git repository.'
    form = IrcForm
    db_object = IrcTable
    backref = 'irc_hook'
    form_fields = [
        'server', 'port', 'room', 'nick', 'nick_pass', 'active', 'join',
        'ssl'
    ]

    @classmethod
    def install(cls, project, dbobj):
        ''' Method called to install the hook for a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [get_repo_path(project)]

        repo_obj = pygit2.Repository(repopaths[0])

        # Configure the hook
        # repo_obj.config.set_multivar()

        # Install the hook itself
        # cls.base_install(repopaths, dbobj, 'irc', 'git_irc.py')

    @classmethod
    def remove(cls, project):
        ''' Method called to remove the hook of a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [get_repo_path(project)]

        # cls.base_remove(repopaths, 'irc')


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

import os
import uuid

import sqlalchemy as sa
import pygit2

from wtforms import validators, TextField, BooleanField
from flask.ext import wtf
from sqlalchemy.orm import relation, backref
from sqlalchemy.ext.declarative import declarative_base

from pagure.hooks import BaseHook, RequiredIf
from pagure.lib.model import BASE, Project
from pagure import get_repo_path
from pagure import APP, SESSION

class PagureCI(BASE):

    __tablename__ = 'hook_pagure_ci'

    id = sa.Column(sa.Integer, primary_key=True)
    project_id = sa.Column(
        sa.Integer,
        sa.ForeignKey('projects.id', onupdate='CASCADE',ondelete='CASCADE'),
        nullable=False,
        unique=False,
        index=True)
    pagure_ci_token = sa.Column(sa.String(32), nullable=True, unique=True,
                            index=True)

    active = sa.Column(sa.Boolean, nullable=False, default=False)
    display_name = sa.Column(sa.String(64), nullable=False, default='Jenkins')
    pagure_name = sa.Column(sa.String(255))

    jenkins_name = sa.Column(sa.String(255))
    jenkins_url = sa.Column(sa.String(255), nullable=False,
                            default='http://jenkins.fedorainfracloud.org/')
    jenkins_token = sa.Column(sa.String(64))

    project = relation(
        'Project',
        foreign_keys=[project_id],
        remote_side=[Project.id],
        backref=backref(
            'hook_pagure_ci', cascade="delete, delete-orphan",
            single_parent=True)
    )

    def __init__(self):
        self.pagure_ci_token = uuid.uuid4().hex

class ConfigNotFound(Exception):
    pass

class Service(object):
    PAGURE = PagureCI.pagure_name
    JENKINS = PagureCI.jenkins_name

def get_configs(project_name, service):
    """Returns all configurations with given name on a service.

    :raises ConfigNotFound: when no configuration matches
    """
    cfg = SESSION.query(PagureCI).filter(
        service == project_name).all()
    if not cfg:
        raise ConfigNotFound(project_name)
    return cfg

class JenkinsForm(wtf.Form):

    '''Form to configure Jenkins hook'''

    pagure_name = TextField('Name of project in Pagure',
                            [validators.Required(),
                             validators.Length(max=255)])

    jenkins_name = TextField('Name of project in Jenkins',
                             [validators.Required(),
                              validators.Length(max=255)])

    jenkins_url = TextField('Jenkins URL',
                            [validators.Required(),
                             validators.Length(max=255)],
                            default='http://jenkins.fedorainfracloud.org/')

    jenkins_token = TextField('Jenkins token',
                              [validators.Required()])

    active = BooleanField('Active', [validators.Optional()])

class PagureCiHook(BaseHook):
    ''' Jenkins hooks. '''

    name = 'Pagure CI'
    description = 'This hook help to set up CI for the project'\
        ' the changes made by the pushes to the git repository.'
    form = JenkinsForm
    db_object = PagureCI
    backref = 'hook_pagure_ci'
    form_fields = [
        'pagure_name', 'jenkins_name',
        'jenkins_url', 'jenkins_token', 'active'
    ]

    @classmethod
    def set_up(cls, project):
        ''' Install the generic post-receive hook that allow us to call
        multiple post-receive hooks as set per plugin.
        '''
        pass

    @classmethod
    def install(cls, project, dbobj):
        ''' Method called to install the hook for a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        pass

    @classmethod
    def remove(cls, project):
        ''' Method called to remove the hook of a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        pass


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

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

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

"""

import os

import sqlalchemy as sa
import pygit2
import wtforms
from flask.ext import wtf
from sqlalchemy.orm import relation
from sqlalchemy.orm import backref

from pagure.hooks import BaseHook, RequiredIf
from pagure.lib.model import BASE, Project
from pagure import get_repo_path

class MailTable(BASE):
    """ Stores information about the irc hook deployed on a project.

    Table -- hook_mail
    """

    __tablename__ = 'hook_mail'

    id = sa.Column(sa.Integer, primary_key=True)
    project_id = sa.Column(
        sa.Integer,
        sa.ForeignKey(
            'projects.id', onupdate='CASCADE', ondelete='CASCADE'),
        nullable=False,
        unique=True,
        index=True)

    mail_to = sa.Column(sa.Text, nullable=False)
    active = sa.Column(sa.Boolean, nullable=False, default=False)

    project = relation(
        'Project', remote_side=[Project.id],
        backref=backref(
            'mail_hook', cascade="delete, delete-orphan",
            single_parent=True)
    )

class MailForm(wtf.Form):
    ''' Form to configure the mail hook. '''
    mail_to = wtforms.TextField(
        'Mail to',
        [RequiredIf('active')]
    )
    active = wtforms.BooleanField(
        'Active',
        [wtforms.validators.Optional()]
    )

class Mail(BaseHook):
    ''' Mail hooks. '''

    name = 'Mail'
    description = 'Generate notification emails for pushes to a git repository. '\
        'This hook sends emails describing changes introduced by pushes to a git repository.'
    form = MailForm
    db_object = MailTable
    backref = 'mail_hook'
    form_fields = ['mail_to', 'active']

    @classmethod
    def install(cls, project, dbobj):
        ''' Method called to install the hook for a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [get_repo_path(project)]
        repo_obj = pygit2.Repository(repopaths[0])

        # Configure the hook
        repo_obj.config.set_multivar(
            'multimailhook.mailingList',
            '',
            dbobj.mail_to
        )
        repo_obj.config.set_multivar(
            'multimailhook.environment', '', 'gitolite')

        # Install the hook itself
        cls.base_install(repopaths, dbobj, 'mail', 'git_multimail.py')

    @classmethod
    def remove(cls, project):
        ''' Method called to remove the hook of a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [get_repo_path(project)]
        cls.base_remove(repopaths, 'mail')


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

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

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

"""

import os

import sqlalchemy as sa
import pygit2
import wtforms
from flask.ext import wtf
from sqlalchemy.orm import relation
from sqlalchemy.orm import backref

from pagure.hooks import BaseHook, RequiredIf
from pagure.lib.model import BASE, Project
from pagure import APP, get_repo_path

class PagureForceCommitTable(BASE):
    """ Stores information about the pagure hook deployed on a project.

    Table -- hook_pagure_force_commit
    """

    __tablename__ = 'hook_pagure_force_commit'

    id = sa.Column(sa.Integer, primary_key=True)
    project_id = sa.Column(
        sa.Integer,
        sa.ForeignKey(
            'projects.id', onupdate='CASCADE', ondelete='CASCADE'),
        nullable=False,
        unique=True,
        index=True)

    branches = sa.Column(sa.Text, nullable=False)

    active = sa.Column(sa.Boolean, nullable=False, default=False)

    project = relation(
        'Project', foreign_keys=[project_id], remote_side=[Project.id],
        backref=backref(
            'pagure_force_commit_hook', cascade="delete, delete-orphan",
            single_parent=True)
    )

class PagureForceCommitForm(wtf.Form):
    ''' Form to configure the pagure hook. '''
    branches = wtforms.TextField(
        'Branches',
        [RequiredIf('active')]
    )

    active = wtforms.BooleanField(
        'Active',
        [wtforms.validators.Optional()]
    )

class PagureForceCommitHook(BaseHook):
    ''' PagurPagureForceCommit hook. '''

    name = 'Block non fast-forward pushes'
    description = 'Using this hook you can block any non-fast-forward '\
        'commit forced pushed to one or more branches'
    form = PagureForceCommitForm
    db_object = PagureForceCommitTable
    backref = 'pagure_force_commit_hook'
    form_fields = ['branches', 'active']
    hook_type = 'pre-receive'

    @classmethod
    def install(cls, project, dbobj):
        ''' Method called to install the hook for a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        # Init the git repo in case
        repopaths = [get_repo_path(project)]
        pygit2.Repository(repopaths[0])

        cls.base_install(repopaths, dbobj, 'pagureforcecommit',
                         'pagure_force_commit_hook.py')

    @classmethod
    def remove(cls, project):
        ''' Method called to remove the hook of a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [get_repo_path(project)]
        cls.base_remove(repopaths, 'pagureforcecommit')


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

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

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

"""

import os

import sqlalchemy as sa
import pygit2
import wtforms
from flask.ext import wtf
from sqlalchemy.orm import relation
from sqlalchemy.orm import backref

from pagure.hooks import BaseHook
from pagure.lib.model import BASE, Project
from pagure import APP, get_repo_path

class PagureTable(BASE):
    """ Stores information about the pagure hook deployed on a project.

    Table -- hook_pagure
    """

    __tablename__ = 'hook_pagure'

    id = sa.Column(sa.Integer, primary_key=True)
    project_id = sa.Column(
        sa.Integer,
        sa.ForeignKey(
            'projects.id', onupdate='CASCADE', ondelete='CASCADE'),
        nullable=False,
        unique=True,
        index=True)

    active = sa.Column(sa.Boolean, nullable=False, default=False)

    project = relation(
        'Project', remote_side=[Project.id],
        backref=backref(
            'pagure_hook', cascade="delete, delete-orphan",
            single_parent=True)
    )

class PagureForm(wtf.Form):
    ''' Form to configure the pagure hook. '''
    active = wtforms.BooleanField(
        'Active',
        [wtforms.validators.Optional()]
    )

DESCRIPTION = '''
Pagure specific hook to add a comment to issues or pull requests if the pushed
commits fix them
or relate to them. This is determined based on the commit message.

To reference an issue/PR you need to use one of recognized keywords followed by
a reference to the issue or PR, separated by whitespace and and optional colon.
Such references can be either:

 * The issue/PR number preceded by the `#` symbol
 * The full URL of the issue or PR

If using the full URL, it is possible to reference issues in other projects.

The recognized keywords are:

 * fix/fixed/fixes
 * relate/related/relates
 * merge/merges/merged
 * close/closes/closed

Examples:

 * Fixes #21
 * related: https://pagure.io/myproject/issue/32
 * this commit merges #74
 * Merged: https://pagure.io/myproject/pull-request/74

Capitalization does not matter; neither does the colon between keyword and
number.

'''

class PagureHook(BaseHook):
    ''' Pagure hook. '''

    name = 'Pagure'
    description = DESCRIPTION
    form = PagureForm
    db_object = PagureTable
    backref = 'pagure_hook'
    form_fields = ['active']

    @classmethod
    def install(cls, project, dbobj):
        ''' Method called to install the hook for a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [get_repo_path(project)]
        for folder in [
                APP.config.get('DOCS_FOLDER'),
                APP.config.get('REQUESTS_FOLDER')]:
            repopaths.append(
                os.path.join(folder, project.path)
            )

        cls.base_install(repopaths, dbobj, 'pagure', 'pagure_hook.py')

    @classmethod
    def remove(cls, project):
        ''' Method called to remove the hook of a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [get_repo_path(project)]
        for folder in [
                APP.config.get('DOCS_FOLDER'),
                APP.config.get('REQUESTS_FOLDER')]:
            repopaths.append(
                os.path.join(folder, project.path)
            )

        cls.base_remove(repopaths, 'pagure')


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

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

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

"""

import os

import flask
import sqlalchemy as sa
import pygit2
import wtforms
from flask.ext import wtf
from sqlalchemy.orm import relation
from sqlalchemy.orm import backref

from pagure.hooks import BaseHook
from pagure.lib.model import BASE, Project
from pagure import APP

class PagureRequestsTable(BASE):
    """ Stores information about the pagure requests hook deployed on a
    project.

    Table -- hook_pagure_requests
    """

    __tablename__ = 'hook_pagure_requests'

    id = sa.Column(sa.Integer, primary_key=True)
    project_id = sa.Column(
        sa.Integer,
        sa.ForeignKey(
            'projects.id', onupdate='CASCADE', ondelete='CASCADE'),
        nullable=False,
        unique=True,
        index=True)

    active = sa.Column(sa.Boolean, nullable=False, default=False)

    project = relation(
        'Project', remote_side=[Project.id],
        backref=backref(
            'pagure_hook_requests', cascade="delete, delete-orphan",
            single_parent=True)
    )

class PagureRequestsForm(wtf.Form):
    ''' Form to configure the pagure hook. '''
    active = wtforms.BooleanField(
        'Active',
        [wtforms.validators.Optional()]
    )

class PagureRequestHook(BaseHook):
    ''' Pagure request hook. '''

    name = 'Pagure requests'
    description = 'Pagure specific hook to update pull-requests stored '\
        'in the database based on the information pushed in the requests git repository.'
    form = PagureRequestsForm
    db_object = PagureRequestsTable
    backref = 'pagure_hook_requests'
    form_fields = ['active']

    @classmethod
    def set_up(cls, project):
        ''' Install the generic post-receive hook that allow us to call
        multiple post-receive hooks as set per plugin.
        '''
        repopath = os.path.join(APP.config['REQUESTS_FOLDER'], project.path)
        if not os.path.exists(repopath):
            flask.abort(404, 'No git repo found')

        hook_files = os.path.join(
            os.path.dirname(os.path.realpath(__file__)), 'files')

        # Make sure the hooks folder exists
        hookfolder = os.path.join(repopath, 'hooks')
        if not os.path.exists(hookfolder):
            os.makedirs(hookfolder)

        # Install the main post-receive file
        postreceive = os.path.join(hookfolder, 'post-receive')
        hook_file = os.path.join(hook_files, 'post-receive')
        if not os.path.exists(postreceive):
            os.symlink(hook_file, postreceive)

    @classmethod
    def install(cls, project, dbobj):
        ''' Method called to install the hook for a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [os.path.join(APP.config['REQUESTS_FOLDER'], project.path)]

        cls.base_install(repopaths, dbobj, 'pagure-requests',
                         'pagure_hook_requests.py')

    @classmethod
    def remove(cls, project):
        ''' Method called to remove the hook of a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [os.path.join(APP.config['REQUESTS_FOLDER'], project.path)]

        cls.base_remove(repopaths, 'pagure-requests')


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

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

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

"""

import os

import flask
import sqlalchemy as sa
import pygit2
import wtforms
from flask.ext import wtf
from sqlalchemy.orm import relation
from sqlalchemy.orm import backref

from pagure.hooks import BaseHook
from pagure.lib.model import BASE, Project
from pagure import APP

class PagureTicketsTable(BASE):
    """ Stores information about the pagure tickets hook deployed on a project.

    Table -- hook_pagure_tickets
    """

    __tablename__ = 'hook_pagure_tickets'

    id = sa.Column(sa.Integer, primary_key=True)
    project_id = sa.Column(
        sa.Integer,
        sa.ForeignKey(
            'projects.id', onupdate='CASCADE', ondelete='CASCADE'),
        nullable=False,
        unique=True,
        index=True)

    active = sa.Column(sa.Boolean, nullable=False, default=False)

    project = relation(
        'Project', remote_side=[Project.id],
        backref=backref(
            'pagure_hook_tickets', cascade="delete, delete-orphan",
            single_parent=True)
    )

class PagureTicketsForm(wtf.Form):
    ''' Form to configure the pagure hook. '''
    active = wtforms.BooleanField(
        'Active',
        [wtforms.validators.Optional()]
    )

class PagureTicketHook(BaseHook):
    ''' Pagure ticket hook. '''

    name = 'Pagure tickets'
    description = 'Pagure specific hook to update tickets stored in the database '\
        'based on the information pushed in the tickets git repository.'
    form = PagureTicketsForm
    db_object = PagureTicketsTable
    backref = 'pagure_hook_tickets'
    form_fields = ['active']

    @classmethod
    def set_up(cls, project):
        ''' Install the generic post-receive hook that allow us to call
        multiple post-receive hooks as set per plugin.
        '''
        repopath = os.path.join(APP.config['TICKETS_FOLDER'], project.path)
        if not os.path.exists(repopath):
            flask.abort(404, 'No git repo found')

        hook_files = os.path.join(
            os.path.dirname(os.path.realpath(__file__)), 'files')

        # Make sure the hooks folder exists
        hookfolder = os.path.join(repopath, 'hooks')
        if not os.path.exists(hookfolder):
            os.makedirs(hookfolder)

        # Install the main post-receive file
        postreceive = os.path.join(hookfolder, 'post-receive')
        hook_file = os.path.join(hook_files, 'post-receive')
        if not os.path.exists(postreceive):
            os.symlink(hook_file, postreceive)

    @classmethod
    def install(cls, project, dbobj):
        ''' Method called to install the hook for a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [os.path.join(APP.config['TICKETS_FOLDER'], project.path)]

        cls.base_install(repopaths, dbobj, 'pagure-ticket',
                         'pagure_hook_tickets.py')

    @classmethod
    def remove(cls, project):
        ''' Method called to remove the hook of a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [os.path.join(APP.config['TICKETS_FOLDER'], project.path)]

        cls.base_remove(repopaths, 'pagure-ticket')


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

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

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

"""

import os

import sqlalchemy as sa
import pygit2
import wtforms
from flask.ext import wtf
from sqlalchemy.orm import relation
from sqlalchemy.orm import backref

from pagure.hooks import BaseHook, RequiredIf
from pagure.lib.model import BASE, Project
from pagure import APP, get_repo_path

class PagureUnsignedCommitTable(BASE):
    """ Stores information about the pagure hook deployed on a project.

    Table -- hook_pagure_unsigned_commit
    """

    __tablename__ = 'hook_pagure_unsigned_commit'

    id = sa.Column(sa.Integer, primary_key=True)
    project_id = sa.Column(
        sa.Integer,
        sa.ForeignKey(
            'projects.id', onupdate='CASCADE', ondelete='CASCADE'),
        nullable=False,
        unique=True,
        index=True)

    active = sa.Column(sa.Boolean, nullable=False, default=False)

    project = relation(
        'Project', foreign_keys=[project_id], remote_side=[Project.id],
        backref=backref(
            'pagure_unsigned_commit_hook', cascade="delete, delete-orphan",
            single_parent=True)
    )

class PagureUnsignedCommitForm(wtf.Form):
    ''' Form to configure the pagure hook. '''

    active = wtforms.BooleanField(
        'Active',
        [wtforms.validators.Optional()]
    )

class PagureUnsignedCommitHook(BaseHook):
    ''' PagurPagureUnsignedCommit hook. '''

    name = 'Block Un-Signed commits'
    description = 'Using this hook you can block any push with commits '\
        'missing a "Signed-Off-By"'
    form = PagureUnsignedCommitForm
    db_object = PagureUnsignedCommitTable
    backref = 'pagure_unsigned_commit_hook'
    form_fields = ['active']
    hook_type = 'pre-receive'

    @classmethod
    def install(cls, project, dbobj):
        ''' Method called to install the hook for a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [get_repo_path(project)]

        cls.base_install(repopaths, dbobj, 'pagureunsignedcommit',
                         'pagure_block_unsigned.py')

    @classmethod
    def remove(cls, project):
        ''' Method called to remove the hook of a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [get_repo_path(project)]

        cls.base_remove(repopaths, 'pagureunsignedcommit')


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

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

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

"""

import os

import sqlalchemy as sa
import pygit2
import wtforms
from flask.ext import wtf
from sqlalchemy.orm import relation
from sqlalchemy.orm import backref

from pagure.hooks import BaseHook, RequiredIf
from pagure.lib.model import BASE, Project
from pagure import APP, get_repo_path

class RtdTable(BASE):
    """ Stores information about the pagure hook deployed on a project.

    Table -- hook_rtd
    """

    __tablename__ = 'hook_rtd'

    id = sa.Column(sa.Integer, primary_key=True)
    project_id = sa.Column(
        sa.Integer,
        sa.ForeignKey(
            'projects.id', onupdate='CASCADE', ondelete='CASCADE'),
        nullable=False,
        unique=True,
        index=True)

    active = sa.Column(sa.Boolean, nullable=False, default=False)

    project_name = sa.Column(sa.Text, nullable=False)
    branches = sa.Column(sa.Text, nullable=True)

    project = relation(
        'Project', remote_side=[Project.id],
        backref=backref(
            'rtd_hook', cascade="delete, delete-orphan",
            single_parent=True)
    )

class RtdForm(wtf.Form):
    ''' Form to configure the pagure hook. '''
    project_name = wtforms.TextField(
        'Project name on readthedoc.org',
        [RequiredIf('active')]
    )
    branches = wtforms.TextField(
        'Restrict build to these branches only (comma separated)',
        [wtforms.validators.Optional()]
    )

    active = wtforms.BooleanField(
        'Active',
        [wtforms.validators.Optional()]
    )

class RtdHook(BaseHook):
    ''' Read The Doc hook. '''

    name = 'Read the Doc'
    description = 'Kick off a build of the documentation on readthedocs.org.'
    form = RtdForm
    db_object = RtdTable
    backref = 'rtd_hook'
    form_fields = ['active', 'project_name', 'branches']

    @classmethod
    def install(cls, project, dbobj):
        ''' Method called to install the hook for a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [get_repo_path(project)]

        cls.base_install(repopaths, dbobj, 'rtd', 'rtd_hook.py')

    @classmethod
    def remove(cls, project):
        ''' Method called to remove the hook of a project.

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed

        '''
        repopaths = [get_repo_path(project)]

        cls.base_remove(repopaths, 'rtd')


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

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

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

Internal endpoints.

"""

import shutil
import tempfile
import os

import flask
import pygit2

from functools import wraps
from sqlalchemy.exc import SQLAlchemyError

PV = flask.Blueprint('internal_ns', __name__, url_prefix='/pv')

import pagure
import pagure.forms
import pagure.lib
import pagure.lib.git
import pagure.ui.fork
from pagure import is_repo_admin, authenticated

MERGE_OPTIONS = {
    'NO_CHANGE': {
        'short_code': 'No changes',
        'message': 'Nothing to change, git is up to date'
    },
    'FFORWARD': {
        'short_code': 'Ok',
        'message': 'The pull-request can be merged and fast-forwarded'
    },
    'CONFLICTS': {
        'short_code': 'Conflicts',
        'message': 'The pull-request cannot be merged due to conflicts'
    },
    'MERGE': {
        'short_code': 'With merge',
        'message': 'The pull-request can be merged with a merge commit'
    }
}

# pylint: disable=E1101

def localonly(function):
    ''' Decorator used to check if the request is local or not.
    '''
    @wraps(function)
    def decorated_function(*args, **kwargs):
        ''' Wrapped function actually checking if the request is local.
        '''
        ip_allowed = pagure.APP.config.get(
            'IP_ALLOWED_INTERNAL', ['127.0.0.1', 'localhost', '::1'])
        if flask.request.remote_addr not in ip_allowed:
            flask.abort(403)
        else:
            return function(*args, **kwargs)
    return decorated_function

@PV.route('/pull-request/comment/', methods=['PUT'])
@localonly
def pull_request_add_comment():
    """ Add a comment to a pull-request.
    """
    pform = pagure.forms.ProjectCommentForm(csrf_enabled=False)
    if not pform.validate_on_submit():
        flask.abort(400, 'Invalid request')

    objid = pform.objid.data
    useremail = pform.useremail.data

    request = pagure.lib.get_request_by_uid(
        pagure.SESSION,
        request_uid=objid,
    )

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

    form = pagure.forms.AddPullRequestCommentForm(csrf_enabled=False)

    if not form.validate_on_submit():
        flask.abort(400, 'Invalid request')

    commit = form.commit.data or None
    tree_id = form.tree_id.data or None
    filename = form.filename.data or None
    row = form.row.data or None
    comment = form.comment.data

    try:
        message = pagure.lib.add_pull_request_comment(
            pagure.SESSION,
            request=request,
            commit=commit,
            tree_id=tree_id,
            filename=filename,
            row=row,
            comment=comment,
            user=useremail,
            requestfolder=pagure.APP.config['REQUESTS_FOLDER'],
        )
        pagure.SESSION.commit()
    except SQLAlchemyError as err:  # pragma: no cover
        pagure.SESSION.rollback()
        pagure.APP.logger.exception(err)
        flask.abort(500, 'Error when saving the request to the database')

    return flask.jsonify({'message': message})

@PV.route('/ticket/comment/', methods=['PUT'])
@localonly
def ticket_add_comment():
    """ Add a comment to a pull-request.
    """
    pform = pagure.forms.ProjectCommentForm(csrf_enabled=False)
    if not pform.validate_on_submit():
        flask.abort(400, 'Invalid request')

    objid = pform.objid.data
    useremail = pform.useremail.data

    issue = pagure.lib.get_issue_by_uid(
        pagure.SESSION,
        issue_uid=objid
    )

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

    user_obj = pagure.lib.search_user(pagure.SESSION, email=useremail)
    admin = False
    if user_obj:
        admin = user_obj == issue.project.user.user or (
            user_obj in [user.user for user in issue.project.users])

    if issue.private and user_obj and not admin \
            and not issue.user.user == user_obj.username:
        flask.abort(
            403, 'This issue is private and you are not allowed to view it')

    form = pagure.forms.CommentForm(csrf_enabled=False)

    if not form.validate_on_submit():
        flask.abort(400, 'Invalid request')

    comment = form.comment.data

    try:
        message = pagure.lib.add_issue_comment(
            pagure.SESSION,
            issue=issue,
            comment=comment,
            user=useremail,
            ticketfolder=pagure.APP.config['TICKETS_FOLDER'],
            notify=True)
        pagure.SESSION.commit()
    except SQLAlchemyError as err:  # pragma: no cover
        pagure.SESSION.rollback()
        pagure.APP.logger.exception(err)
        flask.abort(500, 'Error when saving the request to the database')

    return flask.jsonify({'message': message})

@PV.route('/pull-request/merge', methods=['POST'])
def mergeable_request_pull():
    """ Returns if the specified pull-request can be merged or not.
    """
    force = flask.request.form.get('force', False)
    if force is not False:
        force = True

    form = pagure.forms.ConfirmationForm()
    if not form.validate_on_submit():
        response = flask.jsonify({
            'code': 'CONFLICTS',
            'message': 'Invalid input submitted',
        })
        response.status_code = 400
        return response

    requestid = flask.request.form.get('requestid')

    request = pagure.lib.get_request_by_uid(
        pagure.SESSION, request_uid=requestid)

    if not request:
        response = flask.jsonify({
            'code': 'CONFLICTS',
            'message': 'Pull-request not found',
        })
        response.status_code = 404
        return response

    if request.merge_status and not force:
        return flask.jsonify({
            'code': request.merge_status,
            'short_code': MERGE_OPTIONS[request.merge_status]['short_code'],
            'message': MERGE_OPTIONS[request.merge_status]['message']})

    try:
        merge_status = pagure.lib.git.merge_pull_request(
            session=pagure.SESSION,
            request=request,
            username=None,
            request_folder=None,
            domerge=False)
    except pygit2.GitError as err:
        response = flask.jsonify({
            'code': 'CONFLICTS', 'message': err.message})
        response.status_code = 409
        return response
    except pagure.exceptions.PagureException as err:
        response = flask.jsonify({
            'code': 'CONFLICTS', 'message': err.message})
        response.status_code = 500
        return response

    return flask.jsonify({
        'code': merge_status,
        'short_code': MERGE_OPTIONS[merge_status]['short_code'],
        'message': MERGE_OPTIONS[merge_status]['message']})

@PV.route('/pull-request/ready', methods=['POST'])
def get_pull_request_ready_branch():
    """ Return the list of branches that have commits not in the main
    branch/repo (thus for which one could open a PR) and the number of
    commits that differ.
    """
    form = pagure.forms.ConfirmationForm()
    if not form.validate_on_submit():
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'Invalid input submitted',
        })
        response.status_code = 400
        return response

    repo = pagure.lib.get_project(
        pagure.SESSION,
        flask.request.form.get('repo', '').strip() or None,
        user=flask.request.form.get('repouser', '').strip() or None)

    if not repo:
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'No repo found with the information provided',
        })
        response.status_code = 404
        return response

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

    branches = {}

    for branchname in repo_obj.listall_branches():
        branch = repo_obj.lookup_branch(branchname)

        diff_commits = []
        if repo.is_fork:
            parentpath = os.path.join(
                pagure.APP.config['GIT_FOLDER'], repo.parent.path)
            if repo.parent.is_fork:
                parentpath = os.path.join(
                    pagure.APP.config['FORK_FOLDER'], repo.parent.path)
        else:
            parentpath = os.path.join(
                pagure.APP.config['GIT_FOLDER'], repo.path)

        orig_repo = pygit2.Repository(parentpath)

        if not repo_obj.is_empty and not orig_repo.is_empty \
                and repo_obj.listall_branches() > 1:

            if not orig_repo.head_is_unborn:
                compare_branch = orig_repo.lookup_branch(
                    orig_repo.head.shorthand)
            else:
                compare_branch = None

            compare_commits = []

            if compare_branch:
                compare_commits = [
                    commit.oid.hex
                    for commit in orig_repo.walk(
                        compare_branch.get_object().hex,
                        pygit2.GIT_SORT_TIME)
                ]

            repo_commit = repo_obj[branch.get_object().hex]

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

        if diff_commits:
            branches[branchname] = diff_commits

    prs = pagure.lib.search_pull_requests(
        pagure.SESSION,
        project_id_from=repo.id,
        status='Open'
    )
    for pr in prs:
        if pr.branch_from in branches:
            del(branches[pr.branch_from])

    return flask.jsonify(
        {
            'code': 'OK',
            'message': branches,
        }
    )

@PV.route('/<repo>/issue/template', methods=['POST'])
@PV.route('/fork/<username>/<repo>/issue/template',
           methods=['POST'])
def get_ticket_template(repo, username=None):
    """ Return the template asked for the specified project
    """

    form = pagure.forms.ConfirmationForm()
    if not form.validate_on_submit():
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'Invalid input submitted',
        })
        response.status_code = 400
        return response

    template = flask.request.args.get('template', None)
    if not template:
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'No template provided',
        })
        response.status_code = 400
        return response

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

    if repo is None:
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'Project not found',
        })
        response.status_code = 404
        return response

    if not repo.settings.get('issue_tracker', True):
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'No issue tracker found for this project',
        })
        response.status_code = 404
        return response

    ticketrepopath = os.path.join(
        pagure.APP.config['TICKETS_FOLDER'], repo.path)
    content = None
    if os.path.exists(ticketrepopath):
        ticketrepo = pygit2.Repository(ticketrepopath)
        if not ticketrepo.is_empty and not ticketrepo.head_is_unborn:
            commit = ticketrepo[ticketrepo.head.target]
            # Get the asked template
            content_file = pagure.__get_file_in_tree(
                ticketrepo, commit.tree, ['templates', '%s.md' % template],
                bail_on_tree=True)
            if content_file:
                content, _ = pagure.doc_utils.convert_readme(
                        content_file.data, 'md')
    if content:
        response = flask.jsonify({
            'code': 'OK',
            'message': content,
        })
    else:
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'No such template found',
        })
        response.status_code = 404
    return response

@PV.route('/branches/commit/', methods=['POST'])
@localonly
def get_branches_of_commit():
    """ Return the list of branches that have the specified commit in
    """
    form = pagure.forms.ConfirmationForm()
    if not form.validate_on_submit():
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'Invalid input submitted',
        })
        response.status_code = 400
        return response

    commit_id = flask.request.form.get('commit_id', '').strip() or None
    if not commit_id:
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'No commit id submitted',
        })
        response.status_code = 400
        return response

    repo = pagure.lib.get_project(
        pagure.SESSION,
        flask.request.form.get('repo', '').strip() or None,
        user=flask.request.form.get('repouser', '').strip() or None)

    if not repo:
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'No repo found with the information provided',
        })
        response.status_code = 404
        return response

    repopath = os.path.join(pagure.APP.config['GIT_FOLDER'], repo.path)

    if not os.path.exists(repopath):
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'No git repo found with the information provided',
        })
        response.status_code = 404
        return response

    repo_obj = pygit2.Repository(repopath)

    try:
        commit_id in repo_obj
    except:
        response = flask.jsonify({
            'code': 'ERROR',
            'message': 'This commit could not be found in this repo',
        })
        response.status_code = 404
        return response

    branches = []
    if not repo_obj.head_is_unborn:
        compare_branch = repo_obj.lookup_branch(
            repo_obj.head.shorthand)
    else:
        compare_branch = None

    for branchname in repo_obj.listall_branches():
        branch = repo_obj.lookup_branch(branchname)

        if not repo_obj.is_empty and len(repo_obj.listall_branches()) > 1:

            merge_commit = None

            if compare_branch:
                merge_commit = repo_obj.merge_base(
                    compare_branch.get_object().hex,
                    branch.get_object().hex
                ).hex

            repo_commit = repo_obj[branch.get_object().hex]

            for commit in repo_obj.walk(
                    repo_commit.oid.hex, pygit2.GIT_SORT_TIME):
                if commit.oid.hex == merge_commit:
                    break
                if commit.oid.hex == commit_id:
                    branches.append(branchname)
                    break

    # If we didn't find the commit in any branch and there is one, then it
    # is in the default branch.
    if not branches and compare_branch:
        branches.append(compare_branch.branch_name)

    return flask.jsonify(
        {
            'code': 'OK',
            'branches': branches,
        }
    )