|
|
# RhodeCode VCSServer provides access to different vcs backends via network.
|
|
|
# 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
|
|
|
# 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.lib.str_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:
|
|
|
"""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:
|
|
|
""" 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))
|
|
|
|