exceptions.py
236 lines
| 6.0 KiB
| text/x-python
|
PythonLexer
r5608 | # Copyright (C) 2014-2024 RhodeCode GmbH | |||
r1 | # | |||
# This program is free software: you can redistribute it and/or modify | ||||
# it under the terms of the GNU Affero General Public License, version 3 | ||||
# (only), as published by the Free Software Foundation. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU Affero General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
# | ||||
# This program is dual-licensed. If you wish to learn more about the | ||||
# RhodeCode Enterprise Edition, including its added features, Support services, | ||||
# and proprietary license terms, please see https://rhodecode.com/licenses/ | ||||
""" | ||||
Custom vcs exceptions module. | ||||
""" | ||||
r2449 | import logging | |||
r1 | import functools | |||
r5075 | import urllib.error | |||
import urllib.parse | ||||
r3369 | import rhodecode | |||
Martin Bornhold
|
r964 | |||
r2449 | log = logging.getLogger(__name__) | |||
r682 | ||||
class VCSCommunicationError(Exception): | ||||
pass | ||||
class HttpVCSCommunicationError(VCSCommunicationError): | ||||
pass | ||||
r1 | ||||
class VCSError(Exception): | ||||
pass | ||||
class RepositoryError(VCSError): | ||||
pass | ||||
class RepositoryRequirementError(RepositoryError): | ||||
pass | ||||
r4080 | class UnresolvedFilesInRepo(RepositoryError): | |||
pass | ||||
r1 | class VCSBackendNotSupportedError(VCSError): | |||
""" | ||||
Exception raised when VCSServer does not support requested backend | ||||
""" | ||||
class EmptyRepositoryError(RepositoryError): | ||||
pass | ||||
class TagAlreadyExistError(RepositoryError): | ||||
pass | ||||
class TagDoesNotExistError(RepositoryError): | ||||
pass | ||||
class BranchAlreadyExistError(RepositoryError): | ||||
pass | ||||
class BranchDoesNotExistError(RepositoryError): | ||||
pass | ||||
class CommitError(RepositoryError): | ||||
""" | ||||
Exceptions related to an existing commit | ||||
""" | ||||
class CommitDoesNotExistError(CommitError): | ||||
pass | ||||
class CommittingError(RepositoryError): | ||||
""" | ||||
Exceptions happening while creating a new commit | ||||
""" | ||||
class NothingChangedError(CommittingError): | ||||
pass | ||||
class NodeError(VCSError): | ||||
pass | ||||
class RemovedFileNodeError(NodeError): | ||||
pass | ||||
class NodeAlreadyExistsError(CommittingError): | ||||
pass | ||||
class NodeAlreadyChangedError(CommittingError): | ||||
pass | ||||
class NodeDoesNotExistError(CommittingError): | ||||
pass | ||||
class NodeNotChangedError(CommittingError): | ||||
pass | ||||
class NodeAlreadyAddedError(CommittingError): | ||||
pass | ||||
class NodeAlreadyRemovedError(CommittingError): | ||||
pass | ||||
Martin Bornhold
|
r1107 | class SubrepoMergeError(RepositoryError): | ||
""" | ||||
This happens if we try to merge a repository which contains subrepos and | ||||
the subrepos cannot be merged. The subrepos are not merged itself but | ||||
their references in the root repo are merged. | ||||
""" | ||||
r1 | class ImproperArchiveTypeError(VCSError): | |||
pass | ||||
class CommandError(VCSError): | ||||
pass | ||||
r5314 | class ImproperlyConfiguredError(Exception): | |||
pass | ||||
r1 | class UnhandledException(VCSError): | |||
""" | ||||
Signals that something unexpected went wrong. | ||||
This usually means we have a programming error on the side of the VCSServer | ||||
and should inspect the logfile of the VCSServer to find more details. | ||||
""" | ||||
_EXCEPTION_MAP = { | ||||
'abort': RepositoryError, | ||||
'archive': ImproperArchiveTypeError, | ||||
'error': RepositoryError, | ||||
'lookup': CommitDoesNotExistError, | ||||
'repo_locked': RepositoryError, | ||||
'requirement': RepositoryRequirementError, | ||||
'unhandled': UnhandledException, | ||||
# TODO: johbo: Define our own exception for this and stop abusing | ||||
# urllib's exception class. | ||||
r4914 | 'url_error': urllib.error.URLError, | |||
Martin Bornhold
|
r1107 | 'subrepo_merge_error': SubrepoMergeError, | ||
r1 | } | |||
def map_vcs_exceptions(func): | ||||
""" | ||||
Utility to decorate functions so that plain exceptions are translated. | ||||
The translation is based on `exc_map` which maps a `str` indicating | ||||
the error type into an exception class representing this error inside | ||||
of the vcs layer. | ||||
""" | ||||
@functools.wraps(func) | ||||
def wrapper(*args, **kwargs): | ||||
try: | ||||
return func(*args, **kwargs) | ||||
except Exception as e: | ||||
r5075 | debug = rhodecode.ConfigGet().get_bool('debug') | |||
r3369 | ||||
r1 | # The error middleware adds information if it finds | |||
# __traceback_info__ in a frame object. This way the remote | ||||
# traceback information is made available in error reports. | ||||
r5075 | ||||
r1257 | remote_tb = getattr(e, '_vcs_server_traceback', None) | |||
r3369 | org_remote_tb = getattr(e, '_vcs_server_org_exc_tb', '') | |||
r2183 | __traceback_info__ = None | |||
r1 | if remote_tb: | |||
r4908 | if isinstance(remote_tb, str): | |||
r2183 | remote_tb = [remote_tb] | |||
r1 | __traceback_info__ = ( | |||
r3369 | 'Found VCSServer remote traceback information:\n' | |||
'{}\n' | ||||
'+++ BEG SOURCE EXCEPTION +++\n\n' | ||||
'{}\n' | ||||
'+++ END SOURCE EXCEPTION +++\n' | ||||
''.format('\n'.join(remote_tb), org_remote_tb) | ||||
) | ||||
r1 | ||||
# Avoid that remote_tb also appears in the frame | ||||
del remote_tb | ||||
# Special vcs errors had an attribute "_vcs_kind" which is used | ||||
# to translate them to the proper exception class in the vcs | ||||
# client layer. | ||||
kind = getattr(e, '_vcs_kind', None) | ||||
r4535 | exc_name = getattr(e, '_vcs_server_org_exc_name', None) | |||
r2183 | ||||
r1 | if kind: | |||
r2183 | if any(e.args): | |||
r4606 | _args = [a for a in e.args] | |||
# replace the first argument with a prefix exc name | ||||
r4627 | args = ['{}:{}'.format(exc_name, _args[0] if _args else '?')] + _args[1:] | |||
r2183 | else: | |||
r5091 | args = [__traceback_info__ or f'{exc_name}: UnhandledException'] | |||
r3369 | if debug or __traceback_info__ and kind not in ['unhandled', 'lookup']: | |||
r2449 | # for other than unhandled errors also log the traceback | |||
r3369 | # can be useful for debugging | |||
r2449 | log.error(__traceback_info__) | |||
r4535 | ||||
r2183 | raise _EXCEPTION_MAP[kind](*args) | |||
r1 | else: | |||
raise | ||||
return wrapper | ||||