diff --git a/pagure/hooks/files/pagure_force_commit_hook.py b/pagure/hooks/files/pagure_force_commit_hook.py new file mode 100755 index 0000000..a1bacd4 --- /dev/null +++ b/pagure/hooks/files/pagure_force_commit_hook.py @@ -0,0 +1,82 @@ +#! /usr/bin/env python2 + + +"""Pagure specific hook to add comment on issues if the commits fixes or +relates to an issue. +""" + +import os +import sys + +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_pre_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('Block non fast-forward pushes') + dbobj = plugin.db_object() + # Get the list of branches + branches = [ + branch.strip() + for branch in repo.pagure_force_commit_hook[0].branches.split(',') + if repo.pagure_force_commit_hook] + + # Remove empty branches + branches = [ + branch.strip() + for branch in branches + if branch] + + 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 refname in branches: + 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 "Deletion is forbidden" + sys.exit(1) + elif pagure.lib.git.is_forced_push(oldrev, newrev, abspath): + print "Non fast-forward push are forbidden" + sys.exit(1) + + +def main(args): + run_as_pre_receive_hook() + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/pagure/hooks/pagure_force_commit.py b/pagure/hooks/pagure_force_commit.py new file mode 100644 index 0000000..09b8930 --- /dev/null +++ b/pagure/hooks/pagure_force_commit.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2016 - Copyright Red Hat Inc + + Authors: + Pierre-Yves Chibon + +""" + +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'), + 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 + + ''' + 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') + hook_file = os.path.join(hook_files, 'pagure_force_commit_hook.py') + + for repopath in repopaths: + # Init the git repo in case + pygit2.Repository(repopath) + + # Install the hook itself + hook_path = os.path.join( + repopath, 'hooks', 'pre-receive.pagureforcecommit') + if not os.path.exists(hook_path): + os.symlink(hook_file, hook_path) + + @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) + ) + + for repopath in repopaths: + hook_path = os.path.join( + repopath, 'hooks', 'pre-receive.pagureforcecommit') + if os.path.exists(hook_path): + os.unlink(hook_path)