|
|
# Copyright (C) 2014-2023 RhodeCode GmbH
|
|
|
#
|
|
|
# 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.
|
|
|
"""
|
|
|
import logging
|
|
|
import functools
|
|
|
import urllib.error
|
|
|
import urllib.parse
|
|
|
import rhodecode
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
class VCSCommunicationError(Exception):
|
|
|
pass
|
|
|
|
|
|
|
|
|
class HttpVCSCommunicationError(VCSCommunicationError):
|
|
|
pass
|
|
|
|
|
|
|
|
|
class VCSError(Exception):
|
|
|
pass
|
|
|
|
|
|
|
|
|
class RepositoryError(VCSError):
|
|
|
pass
|
|
|
|
|
|
|
|
|
class RepositoryRequirementError(RepositoryError):
|
|
|
pass
|
|
|
|
|
|
|
|
|
class UnresolvedFilesInRepo(RepositoryError):
|
|
|
pass
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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.
|
|
|
"""
|
|
|
|
|
|
|
|
|
class ImproperArchiveTypeError(VCSError):
|
|
|
pass
|
|
|
|
|
|
|
|
|
class CommandError(VCSError):
|
|
|
pass
|
|
|
|
|
|
|
|
|
class ImproperlyConfiguredError(Exception):
|
|
|
pass
|
|
|
|
|
|
|
|
|
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.
|
|
|
'url_error': urllib.error.URLError,
|
|
|
'subrepo_merge_error': SubrepoMergeError,
|
|
|
}
|
|
|
|
|
|
|
|
|
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:
|
|
|
debug = rhodecode.ConfigGet().get_bool('debug')
|
|
|
|
|
|
# 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.
|
|
|
|
|
|
remote_tb = getattr(e, '_vcs_server_traceback', None)
|
|
|
org_remote_tb = getattr(e, '_vcs_server_org_exc_tb', '')
|
|
|
__traceback_info__ = None
|
|
|
if remote_tb:
|
|
|
if isinstance(remote_tb, str):
|
|
|
remote_tb = [remote_tb]
|
|
|
__traceback_info__ = (
|
|
|
'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)
|
|
|
)
|
|
|
|
|
|
# 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)
|
|
|
exc_name = getattr(e, '_vcs_server_org_exc_name', None)
|
|
|
|
|
|
if kind:
|
|
|
if any(e.args):
|
|
|
_args = [a for a in e.args]
|
|
|
# replace the first argument with a prefix exc name
|
|
|
args = ['{}:{}'.format(exc_name, _args[0] if _args else '?')] + _args[1:]
|
|
|
else:
|
|
|
args = [__traceback_info__ or f'{exc_name}: UnhandledException']
|
|
|
if debug or __traceback_info__ and kind not in ['unhandled', 'lookup']:
|
|
|
# for other than unhandled errors also log the traceback
|
|
|
# can be useful for debugging
|
|
|
log.error(__traceback_info__)
|
|
|
|
|
|
raise _EXCEPTION_MAP[kind](*args)
|
|
|
else:
|
|
|
raise
|
|
|
return wrapper
|
|
|
|