# HG changeset patch # User RhodeCode Admin # Date 2024-03-01 11:02:32 # Node ID 77e2f888b44dfa5bb57286c09e29dd30c2eb6125 # Parent 4042350b3cb2fc9d879b136259b29c15453b51aa fix(svn-hooks): fixed problem with svn subprocess execution fixes RCCE-62 diff --git a/vcsserver/hook_utils/__init__.py b/vcsserver/hook_utils/__init__.py --- a/vcsserver/hook_utils/__init__.py +++ b/vcsserver/hook_utils/__init__.py @@ -15,6 +15,7 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + import re import os import sys @@ -23,6 +24,7 @@ import logging import pkg_resources import vcsserver +import vcsserver.settings from vcsserver.str_utils import safe_bytes log = logging.getLogger(__name__) @@ -138,10 +140,18 @@ def install_svn_hooks(repo_path, executa if _rhodecode_hook or force_create: log.debug('writing svn %s hook file at %s !', h_type, _hook_file) + env_expand = str([ + ('RC_CORE_BINARY_DIR', vcsserver.settings.BINARY_DIR), + ('RC_GIT_EXECUTABLE', vcsserver.settings.GIT_EXECUTABLE), + ('RC_SVN_EXECUTABLE', vcsserver.settings.SVN_EXECUTABLE), + ('RC_SVNLOOK_EXECUTABLE', vcsserver.settings.SVNLOOK_EXECUTABLE), + + ]) try: with open(_hook_file, 'wb') as f: template = template.replace(b'_TMPL_', safe_bytes(vcsserver.get_version())) template = template.replace(b'_DATE_', safe_bytes(timestamp)) + template = template.replace(b'_OS_EXPAND_', safe_bytes(env_expand)) template = template.replace(b'_ENV_', safe_bytes(executable)) template = template.replace(b'_PATH_', safe_bytes(path)) diff --git a/vcsserver/hook_utils/hook_templates/svn_post_commit_hook.py.tmpl b/vcsserver/hook_utils/hook_templates/svn_post_commit_hook.py.tmpl --- a/vcsserver/hook_utils/hook_templates/svn_post_commit_hook.py.tmpl +++ b/vcsserver/hook_utils/hook_templates/svn_post_commit_hook.py.tmpl @@ -20,6 +20,11 @@ except ImportError: RC_HOOK_VER = '_TMPL_' +# special trick to pass in some information from rc to hooks +# mod_dav strips ALL env vars and we can't even access things like PATH +for env_k, env_v in _OS_EXPAND_: + os.environ[env_k] = env_v + def main(): if hooks is None: # exit with success if we cannot import vcsserver.hooks !! @@ -45,6 +50,5 @@ def main(): sys.exit(0) - if __name__ == '__main__': main() diff --git a/vcsserver/hook_utils/hook_templates/svn_pre_commit_hook.py.tmpl b/vcsserver/hook_utils/hook_templates/svn_pre_commit_hook.py.tmpl --- a/vcsserver/hook_utils/hook_templates/svn_pre_commit_hook.py.tmpl +++ b/vcsserver/hook_utils/hook_templates/svn_pre_commit_hook.py.tmpl @@ -20,6 +20,11 @@ except ImportError: RC_HOOK_VER = '_TMPL_' +# special trick to pass in some information from rc to hooks +# mod_dav strips ALL env vars and we can't even access things like PATH +for env_k, env_v in _OS_EXPAND_: + os.environ[env_k] = env_v + def main(): if os.environ.get('SSH_READ_ONLY') == '1': sys.stderr.write('Only read-only access is allowed') @@ -29,6 +34,7 @@ def main(): # exit with success if we cannot import vcsserver.hooks !! # this allows simply push to this repo even without rhodecode sys.exit(0) + if os.environ.get('RC_SKIP_HOOKS') or os.environ.get('RC_SKIP_SVN_HOOKS'): sys.exit(0) repo_path = os.getcwd() diff --git a/vcsserver/hooks.py b/vcsserver/hooks.py --- a/vcsserver/hooks.py +++ b/vcsserver/hooks.py @@ -31,6 +31,7 @@ from celery import Celery import mercurial.scmutil import mercurial.node +import vcsserver.settings from vcsserver.lib.rc_json import json from vcsserver import exceptions, subprocessio, settings from vcsserver.str_utils import ascii_str, safe_str @@ -153,7 +154,7 @@ class SvnMessageWriter(RemoteMessageWrit self.stderr = stderr or sys.stderr def write(self, message): - self.stderr.write(message.encode('utf-8')) + self.stderr.write(message) def _handle_exception(result): @@ -293,6 +294,22 @@ def _get_hg_env(old_rev, new_rev, txnid, return [(k, v) for k, v in env.items()] +def _fix_hooks_executables(): + """ + This is a trick to set proper settings.EXECUTABLE paths for certain execution patterns + especially for subversion where hooks strip entire env, and calling just 'svn' command will most likely fail + because svn is not on PATH + """ + vcsserver.settings.BINARY_DIR = ( + os.environ.get('RC_BINARY_DIR') or vcsserver.settings.BINARY_DIR) + vcsserver.settings.GIT_EXECUTABLE = ( + os.environ.get('RC_GIT_EXECUTABLE') or vcsserver.settings.GIT_EXECUTABLE) + vcsserver.settings.SVN_EXECUTABLE = ( + os.environ.get('RC_SVN_EXECUTABLE') or vcsserver.settings.SVN_EXECUTABLE) + vcsserver.settings.SVNLOOK_EXECUTABLE = ( + os.environ.get('RC_SVNLOOK_EXECUTABLE') or vcsserver.settings.SVNLOOK_EXECUTABLE) + + def repo_size(ui, repo, **kwargs): extras = _extras_from_ui(ui) return _call_hook('repo_size', extras, HgMessageWriter(ui)) @@ -554,7 +571,7 @@ def git_pre_receive(unused_repo_path, re empty_commit_id = '0' * 40 detect_force_push = extras.get('detect_force_push') - + _fix_hooks_executables() for push_ref in rev_data: # store our git-env which holds the temp store push_ref['git_env'] = _get_git_env() @@ -594,6 +611,7 @@ def git_post_receive(unused_repo_path, r extras = json.loads(env['RC_SCM_DATA']) if 'push' not in extras['hooks']: return 0 + _fix_hooks_executables() rev_data = _parse_git_ref_lines(revision_lines) @@ -732,6 +750,7 @@ def svn_pre_commit(repo_path, commit_dat branches = [] tags = [] + _fix_hooks_executables() if env.get('RC_SCM_DATA'): extras = json.loads(env['RC_SCM_DATA']) else: @@ -757,6 +776,7 @@ def svn_post_commit(repo_path, commit_da """ commit_data is path, rev, txn_id """ + if len(commit_data) == 3: path, commit_id, txn_id = commit_data elif len(commit_data) == 2: @@ -764,10 +784,13 @@ def svn_post_commit(repo_path, commit_da 'Some functionality might be limited') path, commit_id = commit_data txn_id = None + else: + return 0 branches = [] tags = [] + _fix_hooks_executables() if env.get('RC_SCM_DATA'): extras = json.loads(env['RC_SCM_DATA']) else: diff --git a/vcsserver/http_main.py b/vcsserver/http_main.py --- a/vcsserver/http_main.py +++ b/vcsserver/http_main.py @@ -259,12 +259,14 @@ class HTTPApplication: settings_merged = global_config.copy() settings_merged.update(app_settings) - git_path = app_settings.get('git_path', None) - if git_path: - settings.GIT_EXECUTABLE = git_path - binary_dir = app_settings.get('core.binary_dir', None) - if binary_dir: - settings.BINARY_DIR = binary_dir + binary_dir = app_settings['core.binary_dir'] + + settings.BINARY_DIR = binary_dir + + # from core.binary dir we set executable paths + settings.GIT_EXECUTABLE = os.path.join(binary_dir, settings.GIT_EXECUTABLE) + settings.SVN_EXECUTABLE = os.path.join(binary_dir, settings.SVN_EXECUTABLE) + settings.SVNLOOK_EXECUTABLE = os.path.join(binary_dir, settings.SVNLOOK_EXECUTABLE) # Store the settings to make them available to other modules. vcsserver.PYRAMID_SETTINGS = settings_merged @@ -714,7 +716,7 @@ def sanitize_settings_and_apply_defaults settings_maker.make_setting('pyramid.default_locale_name', 'en') settings_maker.make_setting('locale', 'en_US.UTF-8') - settings_maker.make_setting('core.binary_dir', '') + settings_maker.make_setting('core.binary_dir', '/usr/local/bin/rhodecode_bin/vcs_bin') temp_store = tempfile.gettempdir() default_cache_dir = os.path.join(temp_store, 'rc_cache') diff --git a/vcsserver/subprocessio.py b/vcsserver/subprocessio.py --- a/vcsserver/subprocessio.py +++ b/vcsserver/subprocessio.py @@ -552,7 +552,7 @@ def run_command(arguments, env=None): proc = SubprocessIOChunker(cmd, **_opts) return b''.join(proc), b''.join(proc.stderr) except OSError as err: - cmd = ' '.join(map(safe_str, cmd)) # human friendly CMD + cmd = ' '.join(map(safe_str, cmd)) # human friendly CMD tb_err = ("Couldn't run subprocessio command (%s).\n" "Original error was:%s\n" % (cmd, err)) log.exception(tb_err)