changeset 2402:2eeb2ed72e55 beta

Added handling of git hooks, extract pushed revisions and store them inside rhodecode journal. F.I.N.A.L.Y !
author Marcin Kuzminski <marcin@python-works.com>
date Wed, 06 Jun 2012 19:51:16 +0200
parents e2af60e480ce
children 6418fdb7d807
files rhodecode/config/pre-receive.tmpl rhodecode/lib/hooks.py rhodecode/lib/middleware/pygrack.py rhodecode/lib/middleware/simplegit.py
diffstat 4 files changed, 124 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/config/pre-receive.tmpl	Wed Jun 06 19:51:16 2012 +0200
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+import os
+import sys
+
+try:
+    import rhodecode
+    from rhodecode.lib.hooks import handle_git_post_receive
+except ImportError:
+    rhodecode = None
+
+
+def main():
+    if rhodecode is None:
+        # exit with success if we cannot import rhodecode !!
+        # this allows simply push to this repo even without
+        # rhodecode
+        sys.exit(0)
+
+    repo_path = os.path.abspath('.')    
+    push_data = sys.stdin.read().strip().split(' ')
+    # os.environ is modified here by a subprocess call that
+    # runs git and later git executes this hook.
+    # Environ get's some additional info from rhodecode system
+    # like IP or username from basic-auth
+    handle_git_post_receive(repo_path, push_data, os.environ)
+    sys.exit(0)
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file
--- a/rhodecode/lib/hooks.py	Wed Jun 06 19:19:21 2012 +0200
+++ b/rhodecode/lib/hooks.py	Wed Jun 06 19:51:16 2012 +0200
@@ -139,7 +139,9 @@
         h = binascii.hexlify
         revs = (h(repo[r].node()) for r in xrange(start, stop + 1))
     elif scm == 'git':
-        revs = []
+        revs = kwargs.get('_git_revs', [])
+        if '_git_revs' in kwargs:
+            kwargs.pop('_git_revs')
 
     action = action % ','.join(revs)
 
@@ -190,3 +192,59 @@
         return callback(**kw)
 
     return 0
+
+
+def handle_git_post_receive(repo_path, revs, env):
+    """
+    A really hacky method that is runned by git pre-receive hook and logs
+    an push action together with pushed revisions. It's runned by subprocess
+    thus needs all info to be able to create a temp pylons enviroment, connect
+    to database and run the logging code. Hacky as sh**t but works. ps.
+    GIT SUCKS
+
+    :param repo_path:
+    :type repo_path:
+    :param revs:
+    :type revs:
+    :param env:
+    :type env:
+    """
+    from paste.deploy import appconfig
+    from sqlalchemy import engine_from_config
+    from rhodecode.config.environment import load_environment
+    from rhodecode.model import init_model
+    from rhodecode.model.db import RhodeCodeUi
+    from rhodecode.lib.utils import make_ui
+    from rhodecode.model.db import Repository
+
+    path, ini_name = os.path.split(env['RHODECODE_CONFIG_FILE'])
+    conf = appconfig('config:%s' % ini_name, relative_to=path)
+    load_environment(conf.global_conf, conf.local_conf)
+
+    engine = engine_from_config(conf, 'sqlalchemy.db1.')
+    init_model(engine)
+
+    baseui = make_ui('db')
+    repo = Repository.get_by_full_path(repo_path)
+
+    _hooks = dict(baseui.configitems('hooks')) or {}
+    # if push hook is enabled via web interface
+    if _hooks.get(RhodeCodeUi.HOOK_PUSH):
+
+        extras = {
+         'username': env['RHODECODE_USER'],
+         'repository': repo.repo_name,
+         'scm': 'git',
+         'action': 'push',
+         'ip': env['RHODECODE_CONFIG_IP'],
+        }
+        for k, v in extras.items():
+            baseui.setconfig('rhodecode_extras', k, v)
+        repo = repo.scm_instance
+        repo.ui = baseui
+        old_rev, new_rev = revs[0:-1]
+
+        cmd = 'log ' + old_rev + '..' + new_rev + ' --reverse --pretty=format:"%H"'
+        git_revs = repo.run_git_command(cmd)[0].splitlines()
+
+        log_push_action(baseui, repo, _git_revs=git_revs)
--- a/rhodecode/lib/middleware/pygrack.py	Wed Jun 06 19:19:21 2012 +0200
+++ b/rhodecode/lib/middleware/pygrack.py	Wed Jun 06 19:51:16 2012 +0200
@@ -41,7 +41,7 @@
     git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
     commands = ['git-upload-pack', 'git-receive-pack']
 
-    def __init__(self, repo_name, content_path):
+    def __init__(self, repo_name, content_path, username):
         files = set([f.lower() for f in os.listdir(content_path)])
         if  not (self.git_folder_signature.intersection(files)
                 == self.git_folder_signature):
@@ -50,6 +50,7 @@
         self.valid_accepts = ['application/x-%s-result' %
                               c for c in self.commands]
         self.repo_name = repo_name
+        self.username = username
 
     def _get_fixedpath(self, path):
         """
@@ -115,11 +116,25 @@
             inputstream = environ['wsgi.input']
 
         try:
+            gitenv = os.environ
+            from rhodecode import CONFIG
+            from rhodecode.lib.base import _get_ip_addr
+            gitenv['RHODECODE_USER'] = self.username
+            gitenv['RHODECODE_CONFIG_IP'] = _get_ip_addr(environ)
+            # forget all configs
+            gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
+            # we need current .ini file used to later initialize rhodecode
+            # env and connect to db
+            gitenv['RHODECODE_CONFIG_FILE'] = CONFIG['__file__']
+            opts = dict(
+                env=gitenv
+            )
             out = subprocessio.SubprocessIOChunker(
                 r'git %s --stateless-rpc "%s"' % (git_command[4:],
                                                   self.content_path),
-                inputstream=inputstream
-                )
+                inputstream=inputstream,
+                **opts
+            )
         except EnvironmentError, e:
             log.exception(e)
             raise exc.HTTPExpectationFailed()
@@ -156,7 +171,7 @@
 
 class GitDirectory(object):
 
-    def __init__(self, repo_root, repo_name):
+    def __init__(self, repo_root, repo_name, username):
         repo_location = os.path.join(repo_root, repo_name)
         if not os.path.isdir(repo_location):
             raise OSError(repo_location)
@@ -164,19 +179,20 @@
         self.content_path = repo_location
         self.repo_name = repo_name
         self.repo_location = repo_location
+        self.username = username
 
     def __call__(self, environ, start_response):
         content_path = self.content_path
         try:
-            app = GitRepository(self.repo_name, content_path)
+            app = GitRepository(self.repo_name, content_path, self.username)
         except (AssertionError, OSError):
             if os.path.isdir(os.path.join(content_path, '.git')):
                 app = GitRepository(self.repo_name,
                                     os.path.join(content_path, '.git'))
             else:
-                return exc.HTTPNotFound()(environ, start_response)
+                return exc.HTTPNotFound()(environ, start_response, self.username)
         return app(environ, start_response)
 
 
-def make_wsgi_app(repo_name, repo_root):
-    return GitDirectory(repo_root, repo_name)
+def make_wsgi_app(repo_name, repo_root, username):
+    return GitDirectory(repo_root, repo_name, username)
--- a/rhodecode/lib/middleware/simplegit.py	Wed Jun 06 19:19:21 2012 +0200
+++ b/rhodecode/lib/middleware/simplegit.py	Wed Jun 06 19:51:16 2012 +0200
@@ -68,8 +68,9 @@
   'git-receive-pack': dulserver.ReceivePackHandler,
 }
 
-from dulwich.repo import Repo
-from dulwich.web import make_wsgi_chain
+# not used for now until dulwich get's fixed
+#from dulwich.repo import Repo
+#from dulwich.web import make_wsgi_chain
 
 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 
@@ -77,7 +78,7 @@
 from rhodecode.lib.base import BaseVCSController
 from rhodecode.lib.auth import get_container_username
 from rhodecode.lib.utils import is_valid_repo, make_ui
-from rhodecode.model.db import User
+from rhodecode.model.db import User, RhodeCodeUi
 
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
 
@@ -205,13 +206,13 @@
             self._handle_githooks(repo_name, action, baseui, environ)
 
             log.info('%s action on GIT repo "%s"' % (action, repo_name))
-            app = self.__make_app(repo_name, repo_path)
+            app = self.__make_app(repo_name, repo_path, username)
             return app(environ, start_response)
         except Exception:
             log.error(traceback.format_exc())
             return HTTPInternalServerError()(environ, start_response)
 
-    def __make_app(self, repo_name, repo_path):
+    def __make_app(self, repo_name, repo_path, username):
         """
         Make an wsgi application using dulserver
 
@@ -223,6 +224,7 @@
         app = make_wsgi_app(
             repo_root=os.path.dirname(repo_path),
             repo_name=repo_name,
+            username=username,
         )
         return app
 
@@ -268,7 +270,10 @@
         return op
 
     def _handle_githooks(self, repo_name, action, baseui, environ):
-        from rhodecode.lib.hooks import log_pull_action, log_push_action
+        """
+        Handles pull action, push is handled by pre-receive hook
+        """
+        from rhodecode.lib.hooks import log_pull_action
         service = environ['QUERY_STRING'].split('=')
         if len(service) < 2:
             return
@@ -278,12 +283,8 @@
         _repo = _repo.scm_instance
         _repo._repo.ui = baseui
 
-        push_hook = 'pretxnchangegroup.push_logger'
-        pull_hook = 'preoutgoing.pull_logger'
         _hooks = dict(baseui.configitems('hooks')) or {}
-        if action == 'push' and _hooks.get(push_hook):
-            log_push_action(ui=baseui, repo=_repo._repo)
-        elif action == 'pull' and _hooks.get(pull_hook):
+        if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
             log_pull_action(ui=baseui, repo=_repo._repo)
 
     def __inject_extras(self, repo_path, baseui, extras={}):