# HG changeset patch # User RhodeCode Admin # Date 2023-03-01 15:57:59 # Node ID 05a103e84320f003812cab64a5045f07b355c40d # Parent 7571f5a63e5f5acecbc5a88a86d95f961469f8f8 svn: fixed use of hgsubversion in the code diff --git a/requirements.txt b/requirements.txt --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,6 @@ dogpile.cache==1.1.8 decorator==5.1.1 dulwich==0.21.3 -hgsubversion==1.9.3 hg-evolve==11.0.0 mercurial==6.3.2 diff --git a/vcsserver/lib/svnremoterepo.py b/vcsserver/lib/svnremoterepo.py new file mode 100644 --- /dev/null +++ b/vcsserver/lib/svnremoterepo.py @@ -0,0 +1,160 @@ +# RhodeCode VCSServer provides access to different vcs backends via network. +# Copyright (C) 2014-2020 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 +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# 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 General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import tempfile + +from svn import client +from svn import core +from svn import ra + +from mercurial import error + +from vcsserver.utils import safe_bytes + +core.svn_config_ensure(None) +svn_config = core.svn_config_get_config(None) + + +class RaCallbacks(ra.Callbacks): + @staticmethod + def open_tmp_file(pool): # pragma: no cover + (fd, fn) = tempfile.mkstemp() + os.close(fd) + return fn + + @staticmethod + def get_client_string(pool): + return b'RhodeCode-subversion-url-checker' + + +class SubversionException(Exception): + pass + + +class SubversionConnectionException(SubversionException): + """Exception raised when a generic error occurs when connecting to a repository.""" + + +def normalize_url(url): + if not url: + return url + if url.startswith(b'svn+http://') or url.startswith(b'svn+https://'): + url = url[4:] + url = url.rstrip(b'/') + return url + + +def _create_auth_baton(pool): + """Create a Subversion authentication baton. """ + # Give the client context baton a suite of authentication + # providers.h + platform_specific = [ + 'svn_auth_get_gnome_keyring_simple_provider', + 'svn_auth_get_gnome_keyring_ssl_client_cert_pw_provider', + 'svn_auth_get_keychain_simple_provider', + 'svn_auth_get_keychain_ssl_client_cert_pw_provider', + 'svn_auth_get_kwallet_simple_provider', + 'svn_auth_get_kwallet_ssl_client_cert_pw_provider', + 'svn_auth_get_ssl_client_cert_file_provider', + 'svn_auth_get_windows_simple_provider', + 'svn_auth_get_windows_ssl_server_trust_provider', + ] + + providers = [] + + for p in platform_specific: + if getattr(core, p, None) is not None: + try: + providers.append(getattr(core, p)()) + except RuntimeError: + pass + + providers += [ + client.get_simple_provider(), + client.get_username_provider(), + client.get_ssl_client_cert_file_provider(), + client.get_ssl_client_cert_pw_file_provider(), + client.get_ssl_server_trust_file_provider(), + ] + + return core.svn_auth_open(providers, pool) + + +class SubversionRepo(object): + """Wrapper for a Subversion repository. + + It uses the SWIG Python bindings, see above for requirements. + """ + def __init__(self, svn_url: bytes = b'', username: bytes = b'', password: bytes = b''): + + self.username = username + self.password = password + self.svn_url = core.svn_path_canonicalize(svn_url) + + self.auth_baton_pool = core.Pool() + self.auth_baton = _create_auth_baton(self.auth_baton_pool) + # self.init_ra_and_client() assumes that a pool already exists + self.pool = core.Pool() + + self.ra = self.init_ra_and_client() + self.uuid = ra.get_uuid(self.ra, self.pool) + + def init_ra_and_client(self): + """Initializes the RA and client layers, because sometimes getting + unified diffs runs the remote server out of open files. + """ + + if self.username: + core.svn_auth_set_parameter(self.auth_baton, + core.SVN_AUTH_PARAM_DEFAULT_USERNAME, + self.username) + if self.password: + core.svn_auth_set_parameter(self.auth_baton, + core.SVN_AUTH_PARAM_DEFAULT_PASSWORD, + self.password) + + callbacks = RaCallbacks() + callbacks.auth_baton = self.auth_baton + + try: + return ra.open2(self.svn_url, callbacks, svn_config, self.pool) + except SubversionException as e: + # e.child contains a detailed error messages + msglist = [] + svn_exc = e + while svn_exc: + if svn_exc.args[0]: + msglist.append(svn_exc.args[0]) + svn_exc = svn_exc.child + msg = '\n'.join(msglist) + raise SubversionConnectionException(msg) + + +class svnremoterepo(object): + """ the dumb wrapper for actual Subversion repositories """ + + def __init__(self, username: bytes = b'', password: bytes = b'', svn_url: bytes = b''): + self.username = username or b'' + self.password = password or b'' + self.path = normalize_url(svn_url) + + def svn(self): + try: + return SubversionRepo(self.path, self.username, self.password) + except SubversionConnectionException as e: + raise error.Abort(safe_bytes(e)) diff --git a/vcsserver/remote/svn.py b/vcsserver/remote/svn.py --- a/vcsserver/remote/svn.py +++ b/vcsserver/remote/svn.py @@ -23,7 +23,9 @@ import urllib.parse import logging import posixpath as vcspath import io -import urllib.request, urllib.parse, urllib.error +import urllib.request +import urllib.parse +import urllib.error import traceback import svn.client @@ -38,7 +40,7 @@ from vcsserver.base import RepoFactory, from vcsserver.exceptions import NoContentException from vcsserver.utils import safe_str from vcsserver.vcs_base import RemoteBase - +from vcsserver.lib.svnremoterepo import svnremoterepo log = logging.getLogger(__name__) @@ -50,7 +52,7 @@ svn_compatible_versions_map = { 'pre-1.9-compatible': '1.8', } -current_compatible_version = '1.12' +current_compatible_version = '1.14' def reraise_safe_exceptions(func): @@ -105,9 +107,6 @@ class SvnRemote(RemoteBase): def __init__(self, factory, hg_factory=None): self._factory = factory - # TODO: Remove once we do not use internal Mercurial objects anymore - # for subversion - self._hg_factory = hg_factory @reraise_safe_exceptions def discover_svn_version(self): @@ -127,15 +126,13 @@ class SvnRemote(RemoteBase): log.exception("failed to read object_store") return False - def check_url(self, url, config_items): - # this can throw exception if not installed, but we detect this - from hgsubversion import svnrepo + def check_url(self, url): - baseui = self._hg_factory._create_config(config_items) # uuid function get's only valid UUID from proper repo, else # throws exception + username, password, src_url = self.get_url_and_credentials(url) try: - svnrepo.svnremoterepo(baseui, url).svn.uuid + svnremoterepo(username, password, src_url).svn().uuid except Exception: tb = traceback.format_exc() log.debug("Invalid Subversion url: `%s`, tb: %s", url, tb) @@ -359,6 +356,7 @@ class SvnRemote(RemoteBase): cache_on, context_uid, repo_id = self._cache_on(wire) region = self._region(wire) + @region.conditional_cache_on_arguments(condition=cache_on) def _get_file_size(_repo_id, _path, _revision): repo = self._factory.repo(wire) diff --git a/vcsserver/utils.py b/vcsserver/utils.py --- a/vcsserver/utils.py +++ b/vcsserver/utils.py @@ -37,60 +37,57 @@ def safe_int(val, default=None): return val -def safe_str(unicode_, to_encoding=None): +def safe_str(str_, to_encoding=None) -> str: """ safe str function. Does few trick to turn unicode_ into string - :param unicode_: unicode to encode + :param str_: str to encode :param to_encoding: encode to this type UTF8 default :rtype: str :returns: str object """ + if isinstance(str_, str): + return str_ + + # if it's bytes cast to str + if not isinstance(str_, bytes): + return str(str_) + to_encoding = to_encoding or ['utf8'] - # if it's not basestr cast to str - if not isinstance(unicode_, basestring): - return str(unicode_) - - if isinstance(unicode_, str): - return unicode_ - if not isinstance(to_encoding, (list, tuple)): to_encoding = [to_encoding] for enc in to_encoding: try: - return unicode_.encode(enc) - except UnicodeEncodeError: + return str(str_, enc) + except UnicodeDecodeError: pass - return unicode_.encode(to_encoding[0], 'replace') + return str(str_, to_encoding[0], 'replace') -def safe_unicode(str_, from_encoding=None): +def safe_bytes(str_, from_encoding=None) -> bytes: """ - safe unicode function. Does few trick to turn str_ into unicode + safe bytes function. Does few trick to turn str_ into bytes string: :param str_: string to decode :param from_encoding: encode from this type UTF8 default :rtype: unicode :returns: unicode object """ - from_encoding = from_encoding or ['utf8'] - - if isinstance(str_, unicode): + if isinstance(str_, bytes): return str_ + if not isinstance(str_, str): + raise ValueError('safe_bytes cannot convert other types than str: got: {}'.format(type(str_))) + + from_encoding = from_encoding or ['utf8'] if not isinstance(from_encoding, (list, tuple)): from_encoding = [from_encoding] - try: - return unicode(str_) - except UnicodeDecodeError: - pass - for enc in from_encoding: try: - return unicode(str_, enc) + return str_.encode(enc) except UnicodeDecodeError: pass