# HG changeset patch # User Marcin Kuzminski # Date 2018-07-23 12:56:40 # Node ID 2961b1db0e26bf0176f7e3e41f74700240c1a542 # Parent 0ece650237c32d685fd37e5eda34d58c81fe8044 exceptions: use new wrapper that store the org exception inside the newly generated exceptions. This will allow nicer debug and tracking capabilities. diff --git a/vcsserver/exceptions.py b/vcsserver/exceptions.py --- a/vcsserver/exceptions.py +++ b/vcsserver/exceptions.py @@ -28,7 +28,7 @@ import functools from pyramid.httpexceptions import HTTPLocked -def _make_exception(kind, *args): +def _make_exception(kind, org_exc, *args): """ Prepares a base `Exception` instance to be sent over the wire. @@ -37,26 +37,62 @@ def _make_exception(kind, *args): """ exc = Exception(*args) exc._vcs_kind = kind + exc._org_exc = org_exc return exc -AbortException = functools.partial(_make_exception, 'abort') +def AbortException(org_exc=None): + def _make_exception_wrapper(*args): + return _make_exception('abort', org_exc, *args) + return _make_exception_wrapper + -ArchiveException = functools.partial(_make_exception, 'archive') +def ArchiveException(org_exc=None): + def _make_exception_wrapper(*args): + return _make_exception('archive', org_exc, *args) + return _make_exception_wrapper + -LookupException = functools.partial(_make_exception, 'lookup') +def LookupException(org_exc=None): + def _make_exception_wrapper(*args): + return _make_exception('lookup', org_exc, *args) + return _make_exception_wrapper + -VcsException = functools.partial(_make_exception, 'error') +def VcsException(org_exc=None): + def _make_exception_wrapper(*args): + return _make_exception('error', org_exc, *args) + return _make_exception_wrapper + -RepositoryLockedException = functools.partial(_make_exception, 'repo_locked') +def RepositoryLockedException(org_exc=None): + def _make_exception_wrapper(*args): + return _make_exception('repo_locked', org_exc, *args) + return _make_exception_wrapper + -RequirementException = functools.partial(_make_exception, 'requirement') +def RequirementException(org_exc=None): + def _make_exception_wrapper(*args): + return _make_exception('requirement', org_exc, *args) + return _make_exception_wrapper + -UnhandledException = functools.partial(_make_exception, 'unhandled') +def UnhandledException(org_exc=None): + def _make_exception_wrapper(*args): + return _make_exception('unhandled', org_exc, *args) + return _make_exception_wrapper + -URLError = functools.partial(_make_exception, 'url_error') +def URLError(org_exc=None): + def _make_exception_wrapper(*args): + return _make_exception('url_error', org_exc, *args) + return _make_exception_wrapper -SubrepoMergeException = functools.partial(_make_exception, 'subrepo_merge_error') + +def SubrepoMergeException(org_exc=None): + def _make_exception_wrapper(*args): + return _make_exception('subrepo_merge_error', org_exc, *args) + return _make_exception_wrapper class HTTPRepoLocked(HTTPLocked): diff --git a/vcsserver/git.py b/vcsserver/git.py --- a/vcsserver/git.py +++ b/vcsserver/git.py @@ -56,9 +56,9 @@ def reraise_safe_exceptions(func): return func(*args, **kwargs) except (ChecksumMismatch, WrongObjectException, MissingCommitError, ObjectMissing) as e: - raise exceptions.LookupException(e.message) + raise exceptions.LookupException(e)(e.message) except (HangupException, UnexpectedCommandError) as e: - raise exceptions.VcsException(e.message) + raise exceptions.VcsException(e)(e.message) except Exception as e: # NOTE(marcink): becuase of how dulwich handles some exceptions # (KeyError on empty repos), we cannot track this and catch all @@ -214,8 +214,8 @@ class GitRemote(object): elif attr in ["author", "message", "parents"]: args.append(attr) result[attr] = method(*args) - except KeyError: - raise exceptions.VcsException( + except KeyError as e: + raise exceptions.VcsException(e)( "Unknown bulk attribute: %s" % attr) return result @@ -258,11 +258,11 @@ class GitRemote(object): log.debug("Trying to open URL %s", cleaned_uri) resp = o.open(req) if resp.code != 200: - raise exceptions.URLError('Return Code is not 200') + raise exceptions.URLError()('Return Code is not 200') except Exception as e: log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True) # means it cannot be cloned - raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e)) + raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e)) # now detect if it's proper git repo gitdata = resp.read() @@ -272,7 +272,7 @@ class GitRemote(object): # old style git can return some other format ! pass else: - raise exceptions.URLError( + raise exceptions.URLError()( "url [%s] does not look like an git" % (cleaned_uri,)) return True @@ -419,7 +419,7 @@ class GitRemote(object): log.warning( 'Trying to fetch from "%s" failed, not a Git repository.', url) # Exception can contain unicode which we convert - raise exceptions.AbortException(repr(e)) + raise exceptions.AbortException(e)(repr(e)) # mikhail: client.fetch() returns all the remote refs, but fetches only # refs filtered by `determine_wants` function. We need to filter result @@ -655,7 +655,7 @@ class GitRemote(object): if safe_call: return '', err else: - raise exceptions.VcsException(tb_err) + raise exceptions.VcsException()(tb_err) @reraise_safe_exceptions def install_hooks(self, wire, force=False): diff --git a/vcsserver/hg.py b/vcsserver/hg.py --- a/vcsserver/hg.py +++ b/vcsserver/hg.py @@ -74,20 +74,21 @@ def reraise_safe_exceptions(func): def wrapper(*args, **kwargs): try: return func(*args, **kwargs) - except (Abort, InterventionRequired): - raise_from_original(exceptions.AbortException) - except RepoLookupError: - raise_from_original(exceptions.LookupException) - except RequirementError: - raise_from_original(exceptions.RequirementException) - except RepoError: - raise_from_original(exceptions.VcsException) - except LookupError: - raise_from_original(exceptions.LookupException) + except (Abort, InterventionRequired) as e: + raise_from_original(exceptions.AbortException(e)) + except RepoLookupError as e: + raise_from_original(exceptions.LookupException(e)) + except RequirementError as e: + raise_from_original(exceptions.RequirementException(e)) + except RepoError as e: + raise_from_original(exceptions.VcsException(e)) + except LookupError as e: + raise_from_original(exceptions.LookupException(e)) except Exception as e: if not hasattr(e, '_vcs_kind'): log.exception("Unhandled exception in hg remote call") - raise_from_original(exceptions.UnhandledException) + raise_from_original(exceptions.UnhandledException(e)) + raise return wrapper @@ -149,7 +150,7 @@ class HgRemote(object): elif kind == 'zip': archiver = archival.zipit(archive_path, mtime) else: - raise exceptions.ArchiveException( + raise exceptions.ArchiveException()( 'Remote does not support: "%s".' % kind) for f_path, f_mode, f_is_link, f_content in file_info: @@ -181,8 +182,8 @@ class HgRemote(object): try: method = self._bulk_methods[attr] result[attr] = method(wire, rev) - except KeyError: - raise exceptions.VcsException( + except KeyError as e: + raise exceptions.VcsException(e)( 'Unknown bulk attribute: "%s"' % attr) return result @@ -219,7 +220,7 @@ class HgRemote(object): isexec=bool(node['mode'] & stat.S_IXUSR), copied=False) - raise exceptions.AbortException( + raise exceptions.AbortException()( "Given path haven't been marked as added, " "changed or removed (%s)" % path) @@ -369,11 +370,11 @@ class HgRemote(object): log.debug("Trying to open URL %s", cleaned_uri) resp = o.open(req) if resp.code != 200: - raise exceptions.URLError('Return Code is not 200') + raise exceptions.URLError()('Return Code is not 200') except Exception as e: log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True) # means it cannot be cloned - raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e)) + raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e)) # now check if it's a proper hg repo, but don't do it for svn try: @@ -390,7 +391,7 @@ class HgRemote(object): except Exception as e: log.warning("URL is not a valid Mercurial repository: %s", cleaned_uri) - raise exceptions.URLError( + raise exceptions.URLError(e)( "url [%s] does not look like an hg repo org_exc: %s" % (cleaned_uri, e)) @@ -412,8 +413,8 @@ class HgRemote(object): try: return "".join(patch.diff( repo, node1=rev1, node2=rev2, match=match_filter, opts=opts)) - except RepoLookupError: - raise exceptions.LookupException() + except RepoLookupError as e: + raise exceptions.LookupException(e)() @reraise_safe_exceptions def file_history(self, wire, revision, path, limit): @@ -555,10 +556,10 @@ class HgRemote(object): try: ctx = repo[revision] - except RepoLookupError: - raise exceptions.LookupException(revision) + except RepoLookupError as e: + raise exceptions.LookupException(e)(revision) except LookupError as e: - raise exceptions.LookupException(e.name) + raise exceptions.LookupException(e)(e.name) if not both: return ctx.hex() @@ -674,7 +675,7 @@ class HgRemote(object): except Abort as e: log.exception("Tag operation aborted") # Exception can contain unicode which we convert - raise exceptions.AbortException(repr(e)) + raise exceptions.AbortException(e)(repr(e)) @reraise_safe_exceptions def tags(self, wire): diff --git a/vcsserver/hgpatches.py b/vcsserver/hgpatches.py --- a/vcsserver/hgpatches.py +++ b/vcsserver/hgpatches.py @@ -116,7 +116,7 @@ def patch_subrepo_type_mapping(): def merge(self, state): """merge currently-saved state with the new state.""" - raise SubrepoMergeException() + raise SubrepoMergeException()() def push(self, opts): """perform whatever action is analogous to 'hg push' diff --git a/vcsserver/hooks.py b/vcsserver/hooks.py --- a/vcsserver/hooks.py +++ b/vcsserver/hooks.py @@ -120,9 +120,9 @@ def _handle_exception(result): log.error('Got traceback from remote call:%s', exception_traceback) if exception_class == 'HTTPLockedRC': - raise exceptions.RepositoryLockedException(*result['exception_args']) + raise exceptions.RepositoryLockedException()(*result['exception_args']) elif exception_class == 'RepositoryError': - raise exceptions.VcsException(*result['exception_args']) + raise exceptions.VcsException()(*result['exception_args']) elif exception_class: raise Exception('Got remote exception "%s" with args "%s"' % (exception_class, result['exception_args'])) diff --git a/vcsserver/scm_app.py b/vcsserver/scm_app.py --- a/vcsserver/scm_app.py +++ b/vcsserver/scm_app.py @@ -152,8 +152,8 @@ def create_hg_wsgi_app(repo_path, repo_n try: return HgWeb(repo_path, name=repo_name, baseui=baseui) - except mercurial.error.RequirementError as exc: - raise exceptions.RequirementException(exc) + except mercurial.error.RequirementError as e: + raise exceptions.RequirementException(e)(e) class GitHandler(object): diff --git a/vcsserver/svn.py b/vcsserver/svn.py --- a/vcsserver/svn.py +++ b/vcsserver/svn.py @@ -64,8 +64,8 @@ def reraise_safe_exceptions(func): return func(*args, **kwargs) except Exception as e: if not hasattr(e, '_vcs_kind'): - log.exception("Unhandled exception in hg remote call") - raise_from_original(exceptions.UnhandledException) + log.exception("Unhandled exception in svn remote call") + raise_from_original(exceptions.UnhandledException(e)) raise return wrapper diff --git a/vcsserver/tests/test_hg.py b/vcsserver/tests/test_hg.py --- a/vcsserver/tests/test_hg.py +++ b/vcsserver/tests/test_hg.py @@ -120,7 +120,7 @@ class TestReraiseSafeExceptions(object): def test_does_not_map_known_exceptions(self): @hg.reraise_safe_exceptions def stub_method(): - raise exceptions.LookupException('stub') + raise exceptions.LookupException()('stub') with pytest.raises(Exception) as exc_info: stub_method()