diff --git a/rhodecode/config/pre-receive.tmpl b/rhodecode/config/pre-receive.tmpl new file mode 100644 --- /dev/null +++ b/rhodecode/config/pre-receive.tmpl @@ -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 diff --git a/rhodecode/lib/hooks.py b/rhodecode/lib/hooks.py --- a/rhodecode/lib/hooks.py +++ b/rhodecode/lib/hooks.py @@ -139,7 +139,9 @@ def log_push_action(ui, repo, **kwargs): 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 @@ def log_create_repository(repository_dic 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) diff --git a/rhodecode/lib/middleware/pygrack.py b/rhodecode/lib/middleware/pygrack.py --- a/rhodecode/lib/middleware/pygrack.py +++ b/rhodecode/lib/middleware/pygrack.py @@ -41,7 +41,7 @@ class GitRepository(object): 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 @@ class GitRepository(object): 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 @@ class GitRepository(object): 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 GitRepository(object): 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 @@ class GitDirectory(object): 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) diff --git a/rhodecode/lib/middleware/simplegit.py b/rhodecode/lib/middleware/simplegit.py --- a/rhodecode/lib/middleware/simplegit.py +++ b/rhodecode/lib/middleware/simplegit.py @@ -68,8 +68,9 @@ dulserver.DEFAULT_HANDLERS = { '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.utils2 import safe_st 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 @@ class SimpleGit(BaseVCSController): 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 @@ class SimpleGit(BaseVCSController): app = make_wsgi_app( repo_root=os.path.dirname(repo_path), repo_name=repo_name, + username=username, ) return app @@ -268,7 +270,10 @@ class SimpleGit(BaseVCSController): 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 @@ class SimpleGit(BaseVCSController): _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={}):