diff --git a/progit/hooks/files/progit_hook_requests.py b/progit/hooks/files/progit_hook_requests.py new file mode 100644 index 0000000..c0c5e07 --- /dev/null +++ b/progit/hooks/files/progit_hook_requests.py @@ -0,0 +1,146 @@ +#! /usr/bin/env python2 + + +"""Progit 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 'PROGIT_CONFIG' not in os.environ \ + and os.path.exists('/etc/progit/progit.cfg'): + print 'Using configuration file `/etc/progit/progit.cfg`' + os.environ['PROGIT_CONFIG'] = '/etc/progit/progit.cfg' + +sys.path.insert(0, os.path.expanduser('~/repos/gitrepo/progit')) + +import progit.lib.git + +def read_git_output(args, input=None, keepends=False, **kw): + """Read the output of a Git command.""" + + return read_output(['git'] + args, input=input, keepends=keepends, **kw) + + +def read_git_lines(args, keepends=False, **kw): + """Return the lines output by Git command. + + Return as single lines, with newlines stripped off.""" + + return read_git_output(args, keepends=True, **kw).splitlines(keepends) + + +def read_output(cmd, input=None, keepends=False, **kw): + if input: + stdin = subprocess.PIPE + else: + stdin = None + p = subprocess.Popen( + cmd, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kw + ) + (out, err) = p.communicate(input) + retcode = p.wait() + if retcode: + print 'ERROR: %s =-- %s' % (cmd, retcode) + if not keepends: + out = out.rstrip('\n\r') + return out + + +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 = read_git_lines( + ['diff-tree', '--no-commit-id', '--name-only', '-r', commit], + keepends=False) + for line in filenames: + if line.strip(): + file_list.append(line.strip()) + + return file_list + + +def get_commits_id(fromrev, torev): + ''' Retrieve the list commit between two revisions and return the list + of their identifier. + ''' + cmd = ['rev-list', '%s...%s' % (torev, fromrev)] + if set(fromrev) == set('0'): + cmd = ['rev-list', '%s' % torev] + output = read_git_lines(cmd) + return output + + +def get_repo_name(): + ''' Return the name of the git repo based on its path. + ''' + repo = os.path.basename(os.getcwd()).split('.git')[0] + return repo + + +def get_username(): + ''' Return the username of the git repo based on its path. + ''' + username = None + repo = os.path.abspath(os.path.join(os.getcwd(), '..')) + if progit.APP.config['REQUESTS_FOLDER'] in repo: + username = repo.split(progit.APP.config['REQUESTS_FOLDER'])[1] + return username + + +def run_as_post_receive_hook(): + + file_list = set() + for line in sys.stdin: + print line + (oldrev, newrev, refname) = line.strip().split(' ', 2) + + print ' -- Old rev' + print oldrev + print ' -- New rev' + print newrev + print ' -- Ref name' + print refname + + tmp = set(get_files_to_load(get_commits_id(oldrev, newrev))) + file_list = file_list.union(tmp) + + reponame = get_repo_name() + username = get_username() + print 'repo:', reponame, username + + for filename in file_list: + print 'To load: %s' % filename + data = ''.join(read_git_lines(['show', 'HEAD:%s' % filename])) + if data: + data = json.loads(data) + if data: + progit.lib.git.update_request_from_git( + progit.SESSION, + reponame=reponame, + username=username, + request_uid=filename, + json_data=data, + gitfolder=progit.APP.config['GIT_FOLDER'], + forkfolder=progit.APP.config['FORK_FOLDER'], + docfolder=progit.APP.config['DOCS_FOLDER'], + ticketfolder=progit.APP.config['TICKETS_FOLDER'], + requestfolder=progit.APP.config['REQUESTS_FOLDER'], + ) + + +def main(args): + run_as_post_receive_hook() + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/progit/hooks/progit_request_hook.py b/progit/hooks/progit_request_hook.py new file mode 100644 index 0000000..efb23b9 --- /dev/null +++ b/progit/hooks/progit_request_hook.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2014 - Copyright Red Hat Inc + + Authors: + Pierre-Yves Chibon + +""" + +import os +import shutil + +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 progit.hooks import BaseHook +from progit.lib.model import BASE, Project +from progit import SESSION, APP + + +class ProgitRequestsTable(BASE): + """ Stores information about the progit requests hook deployed on a + project. + + Table -- hook_progit_requests + """ + + __tablename__ = 'hook_progit_requests' + + 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) + + active = sa.Column(sa.Boolean, nullable=False, default=False) + + project = relation( + 'Project', remote_side=[Project.id], + backref=backref( + 'progit_hook_requests', cascade="delete, delete-orphan", + single_parent=True) + ) + + +class ProgitRequestsForm(wtf.Form): + ''' Form to configure the progit hook. ''' + active = wtforms.BooleanField( + 'Active', + [wtforms.validators.Optional()] + ) + + +class ProgitRequestHook(BaseHook): + ''' Progit request hook. ''' + + name = 'progit requests' + form = ProgitRequestsForm + db_object = ProgitRequestsTable + backref = 'progit_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') + if not os.path.exists(postreceive): + shutil.copyfile( + os.path.join(hook_files, 'post-receive'), + postreceive) + os.chmod(postreceive, 0755) + + @classmethod + def install(cls, project, dbobj): + ''' Method called to install the hook for a project. + + :arg project: a ``progit.model.Project`` object to which the hook + should be installed + + ''' + 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') + repo_obj = pygit2.Repository(repopath) + + # Install the hook itself + shutil.copyfile( + os.path.join(hook_files, 'progit_hook_requets.py'), + os.path.join(repopath, 'hooks', 'post-receive.progit') + ) + os.chmod( + os.path.join(repopath, 'hooks', 'post-receive.progit'), + 0755) + + @classmethod + def remove(cls, project): + ''' Method called to remove the hook of a project. + + :arg project: a ``progit.model.Project`` object to which the hook + should be installed + + ''' + repopath = os.path.join(APP.config['REQUESTS_FOLDER'], project.path) + if not os.path.exists(repopath): + flask.abort(404, 'No git repo found') + + hook_path = os.path.join( + repopath, 'hooks', 'post-receive.progit') + if os.path.exists(hook_path): + os.unlink(hook_path)