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

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

 Authors:
   Ralph Bean <rbean@redhat.com>
   Pierre-Yves Chibon <pingou@pingoured.fr>

"""

import docutils
import docutils.core
import docutils.examples
import markupsafe
import markdown
import textwrap

def modify_rst(rst, view_file_url=None):
    """ Downgrade some of our rst directives if docutils is too old. """
    if view_file_url:
        rst = rst.replace(
            '.. image:: ',
            '.. image:: %s' % view_file_url
        )

    # We catch Exception if we want :-p
    # pylint: disable=W0703
    try:
        # The rst features we need were introduced in this version
        minimum = [0, 9]
        version = [int(cpt) for cpt in docutils.__version__.split('.')]

        # If we're at or later than that version, no need to downgrade
        if version >= minimum:
            return rst
    except Exception:  # pragma: no cover
        # If there was some error parsing or comparing versions, run the
        # substitutions just to be safe.
        pass

    # On Fedora this will never work as the docutils version is to recent
    # Otherwise, make code-blocks into just literal blocks.
    substitutions = {  # pragma: no cover
        '.. code-block:: javascript': '::',
    }

    for old, new in substitutions.items():  # pragma: no cover
        rst = rst.replace(old, new)

    return rst  # pragma: no cover

def modify_html(html):
    """ Perform style substitutions where docutils doesn't do what we want.
    """

    substitutions = {
        '<tt class="docutils literal">': '',
        '</tt>': '',
    }
    for old, new in substitutions.items():
        html = html.replace(old, new)

    return html

def convert_doc(rst_string, view_file_url=None):
    """ Utility to load an RST file and turn it into fancy HTML. """
    rst = modify_rst(rst_string, view_file_url)

    overrides = {'report_level': 'quiet'}
    try:
        html = docutils.core.publish_parts(
            source=rst,
            writer_name='html',
            settings_overrides=overrides)
    except:
        return '
%s
' % rst

    else:

        html_string = html['html_body']

        html_string = modify_html(html_string)

        html_string = markupsafe.Markup(html_string)
        return html_string

def convert_readme(content, ext, view_file_url=None):
    ''' Convert the provided content according to the extension of the file
    provided.
    '''
    output = content
    safe = False
    if ext and ext in ['.rst']:
        safe = True
        output = convert_doc(content.decode('utf-8'), view_file_url)
    elif ext and ext in ['.mk', '.md', '.markdown']:
        output = markdown.markdown(content.decode('utf-8'))
        safe = True
    elif not ext or (ext and ext in ['.text', '.txt']):
        safe = True
        output = '
%s
' % content
    return output, safe

def load_doc(endpoint):
    """ Utility to load an RST file and turn it into fancy HTML. """

    rst = unicode(textwrap.dedent(endpoint.__doc__))

    rst = modify_rst(rst)

    api_docs = docutils.examples.html_body(rst)

    api_docs = modify_html(api_docs)

    api_docs = markupsafe.Markup(api_docs)
    return api_docs


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

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

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

"""

import logging
import os

import flask
import pygit2

import pagure.doc_utils
import pagure.exceptions
import pagure.lib
import pagure.forms

# Create the application.
APP = flask.Flask(__name__)

# set up FAS
APP.config.from_object('pagure.default_config')

if 'PAGURE_CONFIG' in os.environ:
    APP.config.from_envvar('PAGURE_CONFIG')

SESSION = pagure.lib.create_session(APP.config['DB_URL'])

if not APP.debug:
    APP.logger.addHandler(pagure.mail_logging.get_mail_handler(
        smtp_server=APP.config.get('SMTP_SERVER', '127.0.0.1'),
        mail_admin=APP.config.get('MAIL_ADMIN', APP.config['EMAIL_ERROR'])
    ))

# Send classic logs into syslog
SHANDLER = logging.StreamHandler()
SHANDLER.setLevel(APP.config.get('log_level', 'INFO'))
APP.logger.addHandler(SHANDLER)

LOG = APP.logger

TMPL_HTML = '''
<!DOCTYPE html>

  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

{content}

'''

def __get_tree(repo_obj, tree, filepath, index=0, extended=False):
    ''' Retrieve the entry corresponding to the provided filename in a
    given tree.
    '''
    filename = filepath[index]
    if isinstance(tree, pygit2.Blob):  # pragma: no cover
        # If we were given a blob, then let's just return it
        return (tree, None, None)

    for element in tree:
        if element.name == filename or \
                (not filename and element.name.startswith('index')):
            # If we have a folder we must go one level deeper
            if element.filemode == 16384:
                if (index + 1) == len(filepath):
                    filepath.append('')
                return __get_tree(
                    repo_obj, repo_obj[element.oid], filepath,
                    index=index + 1, extended=True)
            else:
                return (element, tree, False)

    if filename == '':
        return (None, tree, extended)
    else:
        raise pagure.exceptions.FileNotFoundException(
            'File %s not found' % ('/'.join(filepath),))

def __get_tree_and_content(repo_obj, commit, path):
    ''' Return the tree and the content of the specified file. '''

    (blob_or_tree, tree_obj, extended) = __get_tree(
        repo_obj, commit.tree, path)

    if blob_or_tree is None:
        return (tree_obj, None, False, extended)

    if not repo_obj[blob_or_tree.oid]:
        # Not tested and no idea how to test it, but better safe than sorry
        flask.abort(404, 'File not found')

    if isinstance(blob_or_tree, pygit2.TreeEntry):  # Returned a file
        ext = os.path.splitext(blob_or_tree.name)[1]
        blob_obj = repo_obj[blob_or_tree.oid]
        content, safe = pagure.doc_utils.convert_readme(blob_obj.data, ext)

    tree = sorted(tree_obj, key=lambda x: x.filemode)
    return (tree, content, safe, extended)

@APP.route('/<repo>/')
@APP.route('/<repo>/<path:filename>')
@APP.route('/fork/<username>/<repo>/')
@APP.route('/fork/<username>/<repo>/<path:filename>')
def view_docs(repo, username=None, filename=None):
    """ Display the documentation
    """

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

    if not repo:
        flask.abort(404, 'Project not found')

    if not repo.settings.get('project_documentation', True):
        flask.abort(404, 'This project has documentation disabled')

    reponame = os.path.join(APP.config['DOCS_FOLDER'], repo.path)
    if not os.path.exists(reponame):
        flask.abort(404, 'Documentation not found')

    repo_obj = pygit2.Repository(reponame)

    if not repo_obj.is_empty:
        commit = repo_obj[repo_obj.head.target]
    else:
        flask.abort(404, 'No content found is the repository')
    branchname = 'master'

    content = None
    tree = None
    safe = False
    if not filename:
        path = ['']
    else:
        path = [it for it in filename.split('/') if it]

    if commit:
        try:
            (tree, content, safe, extended) = __get_tree_and_content(
                repo_obj, commit, path)
            if extended:
                filename += '/'
        except pagure.exceptions.FileNotFoundException as err:
            flask.flash(err.message, 'error')

    mimetype = None
    if not filename:
        pass
    elif filename.endswith('.css'):
        mimetype = 'text/css'
    elif filename.endswith('.js'):
        mimetype = 'application/javascript'

    if not content:
        if not tree or not len(tree):
            flask.abort(404, 'No content found is the repository')
        html = '
'
        for el in tree:
            name = el.name
            # Append a trailing '/' to the folders
            if el.filemode == 16384:
                name += '/'
            html += '
{1}
'.format(el.name, name)
        html += '
'
        content = TMPL_HTML.format(content=html)

    return flask.Response(content, mimetype=mimetype)

repo_name: bill/auger

license: mit


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

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

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

"""

class PagureException(Exception):
    ''' Parent class of all the exception for all Pagure specific
    exceptions.
    '''
    pass

class RepoExistsException(PagureException):
    ''' Exception thrown when trying to create a repository that already
    exists.
    '''
    pass

class FileNotFoundException(PagureException):
    ''' Exception thrown when trying to create a repository that already
    exists.
    '''
    pass

class APIError(PagureException):
    ''' Exception raised by the API when something goes wrong. '''

    def __init__(self, status_code, error_code, error=None):
        self.status_code = status_code
        self.error_code = error_code
        self.error = error

class BranchNotFoundException(PagureException):
    ''' Exception thrown when trying to use a branch that could not be
    found in a repository.
    '''
    pass

class PagureEvException(PagureException):
    ''' Exceptions used in the pagure-stream-server.
    '''
    pass

class GitConflictsException(PagureException):
    ''' Exception used when trying to pull on a repo and that leads to
    conflicts.
    '''
    pass

class HookInactiveException(PagureException):
    ''' Exception raised when the hook is inactive. '''
    pass


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

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

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

"""

import re
from flask.ext import wtf
import wtforms
# pylint: disable=R0903,W0232,E1002

STRICT_REGEX = '^[a-zA-Z0-9-_]+$'
TAGS_REGEX = '^[a-zA-Z0-9-_, .]+$'
PROJECT_NAME_REGEX = \
    '^[a-zA-z0-9_][a-zA-Z0-9-_]*(/?[a-zA-z0-9_][a-zA-Z0-9-_]+)?$'

class ProjectFormSimplified(wtf.Form):
    ''' Form to edit the description of a project. '''
    description = wtforms.TextField(
        'Description *',
        [wtforms.validators.Required()]
    )
    url = wtforms.TextField(
        'URL',
        [wtforms.validators.optional()]
    )
    avatar_email = wtforms.TextField(
        'Avatar email',
        [wtforms.validators.optional()]
    )
    tags = wtforms.TextField(
        'Project tags',
        [wtforms.validators.optional()]
    )

class ProjectForm(ProjectFormSimplified):
    ''' Form to create or edit project. '''
    name = wtforms.TextField(
        'Project name *',
        [
            wtforms.validators.Required(),
            wtforms.validators.Regexp(PROJECT_NAME_REGEX, flags=re.IGNORECASE)
        ]
    )
    create_readme = wtforms.BooleanField(
        'Create README',
        [wtforms.validators.optional()],
    )

class IssueFormSimplied(wtf.Form):
    ''' Form to create or edit an issue. '''
    title = wtforms.TextField(
        'Title*',
        [wtforms.validators.Required()]
    )
    issue_content = wtforms.TextAreaField(
        'Content*',
        [wtforms.validators.Required()]
    )
    private = wtforms.BooleanField(
        'Private',
        [wtforms.validators.optional()],
    )

class IssueForm(IssueFormSimplied):
    ''' Form to create or edit an issue. '''
    status = wtforms.SelectField(
        'Status',
        [wtforms.validators.Required()],
        choices=[]
    )

    def __init__(self, *args, **kwargs):
        """ Calls the default constructor with the normal argument but
        uses the list of collection provided to fill the choices of the
        drop-down list.
        """
        super(IssueForm, self).__init__(*args, **kwargs)
        if 'status' in kwargs:
            self.status.choices = [
                (status, status) for status in kwargs['status']
            ]

class RequestPullForm(wtf.Form):
    ''' Form to create a request pull. '''
    title = wtforms.TextField(
        'Title*',
        [wtforms.validators.Required()]
    )
    initial_comment = wtforms.TextAreaField(
        'Initial Comment', [wtforms.validators.Optional()])

class RemoteRequestPullForm(RequestPullForm):
    ''' Form to create a remote request pull. '''
    git_repo = wtforms.TextField(
        'Git repo address*',
        [wtforms.validators.Required()]
    )
    branch_from = wtforms.TextField(
        'Git branch*',
        [wtforms.validators.Required()]
    )
    branch_to = wtforms.TextField(
        'Git branch to merge in*',
        [wtforms.validators.Required()]
    )

class AddIssueTagForm(wtf.Form):
    ''' Form to add a comment to an issue. '''
    tag = wtforms.TextField(
        'tag',
        [
            wtforms.validators.Optional(),
            wtforms.validators.Regexp(TAGS_REGEX, flags=re.IGNORECASE)
        ]
    )

class StatusForm(wtf.Form):
    ''' Form to add/change the status of an issue. '''
    status = wtforms.SelectField(
        'Status',
        [wtforms.validators.Required()],
        choices=[]
    )

    def __init__(self, *args, **kwargs):
        """ Calls the default constructor with the normal argument but
        uses the list of collection provided to fill the choices of the
        drop-down list.
        """
        super(StatusForm, self).__init__(*args, **kwargs)
        if 'status' in kwargs:
            self.status.choices = [
                (status, status) for status in kwargs['status']
            ]

class NewTokenForm(wtf.Form):
    ''' Form to add/change the status of an issue. '''
    acls = wtforms.SelectMultipleField(
        'ACLs',
        [wtforms.validators.Required()],
        choices=[]
    )

    def __init__(self, *args, **kwargs):
        """ Calls the default constructor with the normal argument but
        uses the list of collection provided to fill the choices of the
        drop-down list.
        """
        super(NewTokenForm, self).__init__(*args, **kwargs)
        if 'acls' in kwargs:
            self.acls.choices = [
                (acl.name, acl.name) for acl in kwargs['acls']
            ]

class UpdateIssueForm(wtf.Form):
    ''' Form to add a comment to an issue. '''
    tag = wtforms.TextField(
        'tag',
        [
            wtforms.validators.Optional(),
            wtforms.validators.Regexp(TAGS_REGEX, flags=re.IGNORECASE)
        ]
    )
    depends = wtforms.TextField(
        'dependency issue', [wtforms.validators.Optional()]
    )
    blocks = wtforms.TextField(
        'blocked issue', [wtforms.validators.Optional()]
    )
    comment = wtforms.TextAreaField(
        'Comment', [wtforms.validators.Optional()]
    )
    assignee = wtforms.TextAreaField(
        'Assigned to', [wtforms.validators.Optional()]
    )
    status = wtforms.SelectField(
        'Status',
        [wtforms.validators.Optional()],
        choices=[]
    )
    priority = wtforms.SelectField(
        'Priority',
        [wtforms.validators.Optional()],
        choices=[]
    )

    def __init__(self, *args, **kwargs):
        """ Calls the default constructor with the normal argument but
        uses the list of collection provided to fill the choices of the
        drop-down list.
        """
        super(UpdateIssueForm, self).__init__(*args, **kwargs)
        if 'status' in kwargs:
            self.status.choices = [
                (status, status) for status in kwargs['status']
            ]

        self.priority.choices = []
        if 'priorities' in kwargs:
            for key in sorted(kwargs['priorities']):
                self.priority.choices.append(
                    (key, kwargs['priorities'][key])
                )

class AddPullRequestCommentForm(wtf.Form):
    ''' Form to add a comment to a pull-request. '''
    commit = wtforms.HiddenField('commit identifier')
    filename = wtforms.HiddenField('file changed')
    row = wtforms.HiddenField('row')
    requestid = wtforms.HiddenField('requestid')
    tree_id = wtforms.HiddenField('treeid')
    comment = wtforms.TextAreaField(
        'Comment*',
        [wtforms.validators.Required()]
    )

class AddPullRequestFlagForm(wtf.Form):
    ''' Form to add a flag to a pull-request. '''
    username = wtforms.TextField(
        'Username', [wtforms.validators.Required()])
    percent = wtforms.TextField(
        'Percentage of completion', [wtforms.validators.Required()])
    comment = wtforms.TextAreaField(
        'Comment', [wtforms.validators.Required()])
    url = wtforms.TextField(
        'URL', [wtforms.validators.Required()])
    uid = wtforms.TextField(
        'UID', [wtforms.validators.optional()])

class UserSettingsForm(wtf.Form):
    ''' Form to create or edit project. '''
    ssh_key = wtforms.TextAreaField(
        'Public SSH key *',
        [wtforms.validators.Required()]
    )

class AddUserForm(wtf.Form):
    ''' Form to add a user to a project. '''
    user = wtforms.TextField(
        'Username *',
        [wtforms.validators.Required()]
    )

class AssignIssueForm(wtf.Form):
    ''' Form to assign an user to an issue. '''
    assignee = wtforms.TextField(
        'Assignee *',
        [wtforms.validators.Required()]
    )

class AddGroupForm(wtf.Form):
    ''' Form to add a group to a project. '''
    group = wtforms.TextField(
        'Group *',
        [
            wtforms.validators.Required(),
            wtforms.validators.Regexp(STRICT_REGEX, flags=re.IGNORECASE)
        ]
    )

class ConfirmationForm(wtf.Form):
    ''' Simple form used just for CSRF protection. '''
    pass

class UploadFileForm(wtf.Form):
    ''' Form to upload a file. '''
    filestream = wtforms.FileField(
        'File',
        [wtforms.validators.Required()])

class UserEmailForm(wtf.Form):
    ''' Form to edit the description of a project. '''
    email = wtforms.TextField(
        'email', [wtforms.validators.Required()]
    )

    def __init__(self, *args, **kwargs):
        super(UserEmailForm, self).__init__(*args, **kwargs)
        if 'emails' in kwargs:
            if kwargs['emails']:
                self.email.validators.append(
                    wtforms.validators.NoneOf(kwargs['emails'])
                )
        else:
            self.email.validators = [wtforms.validators.Required()]

class ProjectCommentForm(wtf.Form):
    ''' Form to represent project. '''
    objid = wtforms.TextField(
        'Ticket/Request id',
        [wtforms.validators.Required()]
    )
    useremail = wtforms.TextField(
        'Email',
        [wtforms.validators.Required()]
    )

class CommentForm(wtf.Form):
    ''' Form to upload a file. '''
    comment = wtforms.FileField(
        'Comment',
        [wtforms.validators.Required()])

class NewGroupForm(wtf.Form):
    """ Form to ask for a password change. """
    group_name = wtforms.TextField(
        'Group name  *',
        [
            wtforms.validators.Required(),
            wtforms.validators.Length(max=16),
            wtforms.validators.Regexp(STRICT_REGEX, flags=re.IGNORECASE)
        ]
    )
    group_type = wtforms.SelectField(
        'Group type',
        [wtforms.validators.Required()],
        choices=[]
    )

    def __init__(self, *args, **kwargs):
        """ Calls the default constructor with the normal argument but
        uses the list of collection provided to fill the choices of the
        drop-down list.
        """
        super(NewGroupForm, self).__init__(*args, **kwargs)
        if 'group_types' in kwargs:
            self.group_type.choices = [
                (grptype, grptype) for grptype in kwargs['group_types']
            ]

class EditFileForm(wtf.Form):
    """ Form used to edit a file. """
    content = wtforms.TextAreaField(
        'content', [wtforms.validators.Required()])
    commit_title = wtforms.TextField(
        'Title', [wtforms.validators.Required()])
    commit_message = wtforms.TextAreaField(
        'Commit message', [wtforms.validators.optional()])
    email = wtforms.SelectField(
        'Email', [wtforms.validators.Required()],
        choices=[]
    )
    branch = wtforms.TextField(
        'Branch', [wtforms.validators.Required()])

    def __init__(self, *args, **kwargs):
        """ Calls the default constructor with the normal argument but
        uses the list of collection provided to fill the choices of the
        drop-down list.
        """
        super(EditFileForm, self).__init__(*args, **kwargs)
        if 'emails' in kwargs:
            self.email.choices = [
                (email.email, email.email) for email in kwargs['emails']
            ]

class DefaultBranchForm(wtf.Form):
    """Form to change the default branh for a repository"""
    branches = wtforms.SelectField(
        'default_branch',
        [wtforms.validators.Required()],
        choices=[]
    )

    def __init__(self, *args, **kwargs):
        """ Calls the default constructor with the normal argument but
        uses the list of collection provided to fill the choices of the
        drop-down list.
        """
        super(DefaultBranchForm, self).__init__(*args, **kwargs)
        if 'branches' in kwargs:
            self.branches.choices = [
                (branch, branch) for branch in kwargs['branches']
            ]

class EditCommentForm(wtf.Form):
    """ Form to verify that comment is not empty
    """
    update_comment = wtforms.TextAreaField(
        'Comment*',
        [wtforms.validators.Required()]
    )

class ForkRepoForm(wtf.Form):
    ''' Form to fork a project in the API. '''
    repo = wtforms.TextField(
        'The project name *',
        [wtforms.validators.Required()]
    )
    username = wtforms.TextField(
        'User who forked the project',
        [wtforms.validators.optional()])


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

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

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

"""

import os
import shutil
import wtforms

from pagure.exceptions import FileNotFoundException
from pagure import APP, get_repo_path

class RequiredIf(wtforms.validators.Required):
    """ Wtforms validator setting a field as required if another field
    has a value.
    """

    def __init__(self, fields, *args, **kwargs):
        if isinstance(fields, basestring):
            fields = [fields]
        self.fields = fields
        super(RequiredIf, self).__init__(*args, **kwargs)

    def __call__(self, form, field):
        for fieldname in self.fields:
            nfield = form._fields.get(fieldname)
            if nfield is None:
                raise Exception(
                    'no field named "%s" in form' % fieldname)
            if bool(nfield.data):
                super(RequiredIf, self).__call__(form, field)

class BaseHook(object):
    ''' Base class for pagure's hooks. '''

    name = None
    form = None
    description = None
    hook_type = 'post-receive'

    @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.
        '''
        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)
            )

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

        for repopath in repopaths:
            # 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, cls.hook_type)
            if not os.path.exists(postreceive):
                os.symlink(os.path.join(hook_files, cls.hook_type),
                           postreceive)

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

        :arg project: a ``pagure.model.Project`` object to which the hook
            should be installed
        :arg dbobj: the DB object the hook uses to store the settings
            information.

        '''
        for repopath in repopaths:
            if not os.path.exists(repopath):
                raise FileNotFoundException('Repo %s not found' % repopath)

            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 hook itself
            hook_file = os.path.join(repopath, 'hooks', cls.hook_type + '.'
                                     + hook_name)

            if not os.path.exists(hook_file):
                os.symlink(
                    os.path.join(hook_files, filein),
                    hook_file
                )

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

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

        '''
        for repopath in repopaths:
            if not os.path.exists(repopath):
                raise FileNotFoundException('Repo %s not found' % repopath)

            hook_path = os.path.join(repopath, 'hooks', cls.hook_type + '.'
                                     + hook_name)
            if os.path.exists(hook_path):
                os.unlink(hook_path)


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

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

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

"""

import os

import sqlalchemy as sa
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 get_repo_path

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

    Table -- hook_fedmsg
    """

    __tablename__ = 'hook_fedmsg'

    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(
            'fedmsg_hook', cascade="delete, delete-orphan",
            single_parent=True)
    )

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

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

    name = 'Fedmsg'
    description = 'This hook pushes the commit messages'\
        ' to the Fedora bus to be consumed by other applications.'
    form = FedmsgForm
    db_object = FedmsgTable
    backref = 'fedmsg_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)]
        cls.base_install(repopaths, dbobj, 'fedmsg', 'fedmsg_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, 'fedmsg')


code:
#!/usr/bin/env python

import getpass
import os
import subprocess
import sys

from collections import defaultdict

import fedmsg
import fedmsg.config

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'])

print "Emitting a message to the fedmsg bus."
config = fedmsg.config.load_config([], None)
config['active'] = True
config['endpoints']['relay_inbound'] = config['relay_inbound']
fedmsg.init(name='relay_inbound', **config)

seen = []

# Read in all the rev information git-receive-pack hands us.
for line in sys.stdin.readlines():
    (oldrev, newrev, refname) = line.strip().split(' ', 2)

    forced = False
    if set(newrev) == set(['0']):
        print "Deleting a reference/branch, so we won't run the "\
            "pagure hook"
        break
    elif set(oldrev) == set(['0']):
        print "New reference/branch"
        oldrev = '^%s' % oldrev
    elif pagure.lib.git.is_forced_push(oldrev, newrev, abspath):
        forced = True
        base = pagure.lib.git.get_base_revision(oldrev, newrev, abspath)
        if base:
            oldrev = base[0]

    revs = pagure.lib.git.get_revs_between(
        oldrev, newrev, abspath, refname, forced=forced)
    project_name = pagure.lib.git.get_repo_name(abspath)
    username = pagure.lib.git.get_username(abspath)
    project = pagure.lib.get_project(pagure.SESSION, project_name, username)
    if not project:
        project = project_name

    auths = set()
    for rev in revs:
        email = pagure.lib.git.get_author_email(rev, abspath)
        name = pagure.lib.git.get_author(rev, abspath)
        author = pagure.lib.search_user(pagure.SESSION, email=email) or name
        auths.add(author)

    authors = []
    for author in auths:
        if isinstance(author, basestring):
            author = author
        else:
            author = author.to_json(public=True)
        authors.append(author)

    if revs:
        revs.reverse()
        print "* Publishing information for %i commits" % len(revs)
        pagure.lib.notify.log(
            project=project,
            topic="git.receive",
            msg=dict(
                total_commits=len(revs),
                start_commit=revs[0],
                end_commit=revs[-1],
                branch=refname,
                forced=forced,
                authors=list(authors),
                agent=os.environ['GL_USER'],
                repo=project.to_json(public=True)
                if not isinstance(project, basestring) else project,
            ),
        )