diff --git a/vcsserver/exceptions.py b/vcsserver/exceptions.py --- a/vcsserver/exceptions.py +++ b/vcsserver/exceptions.py @@ -53,12 +53,6 @@ def ArchiveException(org_exc=None): return _make_exception_wrapper -def ClientNotSupportedException(org_exc=None): - def _make_exception_wrapper(*args): - return _make_exception('client_not_supported', org_exc, *args) - return _make_exception_wrapper - - def LookupException(org_exc=None): def _make_exception_wrapper(*args): return _make_exception('lookup', org_exc, *args) @@ -82,6 +76,10 @@ def RepositoryBranchProtectedException(o return _make_exception('repo_branch_protected', org_exc, *args) return _make_exception_wrapper +def ClientNotSupportedException(org_exc=None): + def _make_exception_wrapper(*args): + return _make_exception('client_not_supported', org_exc, *args) + return _make_exception_wrapper def RequirementException(org_exc=None): def _make_exception_wrapper(*args): @@ -118,9 +116,18 @@ class HTTPRepoLocked(HTTPLocked): super().__init__(**kwargs) -class HTTPRepoBranchProtected(HTTPForbidden): - def __init__(self, *args, **kwargs): - super(HTTPForbidden, self).__init__(*args, **kwargs) +class HTTPRepoBranchProtected(HTTPLocked): + def __init__(self, title, status_code=None, **kwargs): + self.code = status_code or HTTPLocked.code + self.title = title + super().__init__(**kwargs) + + +class HTTPClientNotSupported(HTTPLocked): + def __init__(self, title, status_code=None, **kwargs): + self.code = status_code or HTTPLocked.code + self.title = title + super().__init__(**kwargs) class RefNotFoundException(KeyError): diff --git a/vcsserver/hooks.py b/vcsserver/hooks.py --- a/vcsserver/hooks.py +++ b/vcsserver/hooks.py @@ -1,5 +1,5 @@ # RhodeCode VCSServer provides access to different vcs backends via network. -# Copyright (C) 2014-2023 RhodeCode GmbH +# Copyright (C) 2014-2024 RhodeCode GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -56,6 +56,9 @@ class HooksCeleryClient: self.celery_app = celery_app def __call__(self, method, extras): + # NOTE: exception handling for those tasks executed is in + # @adapt_for_celery decorator + # also see: _maybe_handle_exception which is handling exceptions inquired_task = self.celery_app.signature( f'rhodecode.lib.celerylib.tasks.{method}' ) @@ -83,11 +86,8 @@ class HgMessageWriter(RemoteMessageWrite self.ui = ui def write(self, message: str): - # TODO: Check why the quiet flag is set by default. - old = self.ui.quiet - self.ui.quiet = False - self.ui.status(message.encode('utf-8')) - self.ui.quiet = old + args = (message.encode('utf-8'),) + self.ui._writemsg(self.ui._fmsgerr, type=b'status', *args) class GitMessageWriter(RemoteMessageWriter): @@ -97,7 +97,7 @@ class GitMessageWriter(RemoteMessageWrit self.stdout = stdout or sys.stdout def write(self, message: str): - self.stdout.write(message) + self.stdout.write(message + "\n" if message else "") class SvnMessageWriter(RemoteMessageWriter): @@ -111,31 +111,33 @@ class SvnMessageWriter(RemoteMessageWrit self.stderr.write(message) -def _maybe_handle_exception(result): - +def _maybe_handle_exception(writer, result): + """ + adopt_for_celery defines the exception/exception_traceback + Ths result is a direct output from a celery task + """ exception_class = result.get('exception') exception_traceback = result.get('exception_traceback') - if not exception_class: - return - log.debug('Handling hook-call exception: %s', exception_class) - - if exception_traceback: - log.error('Got traceback from remote call:%s', exception_traceback) - - if exception_class == 'HTTPLockedRepo': - raise exceptions.LockedRepoException()(*result['exception_args']) - elif exception_class == 'ClientNotSupportedError': - raise exceptions.ClientNotSupportedException()(*result['exception_args']) - elif exception_class == 'HTTPBranchProtected': - raise exceptions.RepositoryBranchProtectedException()(*result['exception_args']) - elif exception_class == 'RepositoryError': - raise exceptions.VcsException()(*result['exception_args']) - elif exception_class: - raise Exception( - f"""Got remote exception "{exception_class}" with args "{result['exception_args']}" """ - ) + match exception_class: + # NOTE: the underlying exceptions are setting _vcs_kind special marker + # which is later handled by `handle_vcs_exception` and translated into a special HTTP exception + # propagated later to the client + case 'HTTPLockedRepo': + raise exceptions.LockedRepoException()(*result['exception_args']) + case 'ClientNotSupported': + raise exceptions.ClientNotSupportedException()(*result['exception_args']) + case 'HTTPBranchProtected': + raise exceptions.RepositoryBranchProtectedException()(*result['exception_args']) + case 'RepositoryError': + raise exceptions.VcsException()(*result['exception_args']) + case _: + if exception_class: + log.error('Handling hook-call exception. Got traceback from remote call:%s', exception_traceback) + raise Exception( + f"""Got remote exception "{exception_class}" with args "{result['exception_args']}" """ + ) def _get_hooks_client(extras): @@ -156,7 +158,7 @@ def _call_hook(hook_name, extras, writer log.debug('Hooks, using client:%s', hooks_client) result = hooks_client(hook_name, extras) log.debug('Hooks got result: %s', result) - _maybe_handle_exception(result) + _maybe_handle_exception(writer, result) writer.write(result['output']) return result['status'] diff --git a/vcsserver/http_main.py b/vcsserver/http_main.py --- a/vcsserver/http_main.py +++ b/vcsserver/http_main.py @@ -47,7 +47,7 @@ from vcsserver.server import VcsServer from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub from vcsserver.echo_stub.echo_app import EchoApp -from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected +from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected, HTTPClientNotSupported from vcsserver.lib.exc_tracking import store_exception, format_exc from vcsserver.lib.str_utils import safe_int from vcsserver.lib.statsd_client import StatsdClient @@ -635,19 +635,21 @@ class HTTPApplication: return _git_stream def handle_vcs_exception(self, exception, request): - _vcs_kind = getattr(exception, '_vcs_kind', '') - if _vcs_kind == 'repo_locked': - headers_call_context = get_headers_call_context(request.environ) - status_code = safe_int(headers_call_context['locked_status_code']) + match _vcs_kind := getattr(exception, '_vcs_kind', ''): + case 'repo_locked': + headers_call_context = get_headers_call_context(request.environ) + status_code = safe_int(headers_call_context['locked_status_code']) - return HTTPRepoLocked( - title=str(exception), status_code=status_code, headers=[('X-Rc-Locked', '1')]) - - elif _vcs_kind == 'repo_branch_protected': - # Get custom repo-branch-protected status code if present. - return HTTPRepoBranchProtected( - title=str(exception), headers=[('X-Rc-Branch-Protection', '1')]) + return HTTPRepoLocked( + title=str(exception), status_code=status_code, headers=[('X-Rc-Locked', '1')]) + case 'repo_branch_protected': + # Get custom repo-branch-protected status code if present. + return HTTPRepoBranchProtected( + title=str(exception), headers=[('X-Rc-Branch-Protection', '1')]) + case 'client_not_supported': + return HTTPClientNotSupported( + title=str(exception), headers=[('X-Rc-Client-Not-Supported', '1')]) exc_info = request.exc_info store_exception(id(exc_info), exc_info) diff --git a/vcsserver/subprocessio.py b/vcsserver/subprocessio.py --- a/vcsserver/subprocessio.py +++ b/vcsserver/subprocessio.py @@ -415,6 +415,7 @@ class SubprocessIOChunker: return_code = _p.poll() ret_code_ok = return_code in [None, 0] ret_code_fail = return_code is not None and return_code != 0 + if ( (ret_code_fail and fail_on_return_code) or (ret_code_ok and fail_on_stderr and bg_err.length)