##// END OF EJS Templates
merge: Resolved conflicts
merge: Resolved conflicts

File last commit:

r5459:7f730862 default
r5471:df8a724f merge v5.1.0 stable
Show More
simplesvn.py
258 lines | 9.7 KiB | text/x-python | PythonLexer
project: added all source files and assets
r1
copyrights: updated for 2023
r5088 # Copyright (C) 2010-2023 RhodeCode GmbH
project: added all source files and assets
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/
fix(svn): svn events fixes and change the way how we handle the events
r5459 import re
import os
vcs: added logging into VCS middlewares
r753 import logging
core: multiple fixes to unicode vs str usage...
r5065 import urllib.request
import urllib.parse
import urllib.error
python3: fixed urlparse import
r4919 import urllib.parse
project: added all source files and assets
r1
import requests
svn: fix usage of http exception from webob to pyramid one....
r2771 from pyramid.httpexceptions import HTTPNotAcceptable
project: added all source files and assets
r1
feat(svn-config): moved svn related config keys to *.ini file. Fixes: RCCE-60
r5328 from rhodecode import ConfigGet
project: added all source files and assets
r1 from rhodecode.lib.middleware import simplevcs
PATH_INFO: use new method to consistently extract proper PATH_INFO data
r5032 from rhodecode.lib.middleware.utils import get_path_info
project: added all source files and assets
r1 from rhodecode.lib.utils import is_valid_repo
fix(svn): svn events fixes and change the way how we handle the events
r5459 from rhodecode.lib.str_utils import safe_str
project: added all source files and assets
r1
vcs: added logging into VCS middlewares
r753 log = logging.getLogger(__name__)
project: added all source files and assets
r1
class SimpleSvnApp(object):
IGNORED_HEADERS = [
'connection', 'keep-alive', 'content-encoding',
Martin Bornhold
svn: Ignore the content length header from response, fixes #4112...
r473 'transfer-encoding', 'content-length']
simplevcs: store rc_extras reference inside the apps itself so it can be used during...
r2389 rc_extras = {}
project: added all source files and assets
r1 def __init__(self, config):
self.config = config
feat(svn): improvements to handle SVN protocol 1.4 features...
r5215 self.session = requests.Session()
project: added all source files and assets
r1
def __call__(self, environ, start_response):
request_headers = self._get_request_headers(environ)
svn: fixed svn operations
r5156 data_io = environ['wsgi.input']
req_method: str = environ['REQUEST_METHOD']
chore(debug): improved logging for simplesvn
r5399 has_content_length: bool = 'CONTENT_LENGTH' in environ
PATH_INFO: use new method to consistently extract proper PATH_INFO data
r5032
svn: support proxy-prefix properly fixes #5521
r3323 path_info = self._get_url(
PATH_INFO: use new method to consistently extract proper PATH_INFO data
r5032 self.config.get('subversion_http_server_url', ''), get_path_info(environ))
svn: use streaming uploads/downloads of files....
r3022 transfer_encoding = environ.get('HTTP_TRANSFER_ENCODING', '')
chore(debug): improved logging for simplesvn
r5399 log.debug('Handling: %s method via `%s` has_content_length:%s', req_method, path_info, has_content_length)
project: added all source files and assets
r1
svn: use streaming uploads/downloads of files....
r3022 # stream control flag, based on request and content type...
stream = False
if req_method in ['MKCOL'] or has_content_length:
fix(svn): svn events fixes and change the way how we handle the events
r5459 # NOTE(johbo): Avoid that we end up with sending the request in chunked
# transfer encoding (mainly on Gunicorn). If we know the content
# length, then we should transfer the payload in one request.
data_io = data_io.read()
svn: enable hooks and integration framework execution....
r2677
svn: use streaming uploads/downloads of files....
r3022 if req_method in ['GET', 'PUT'] or transfer_encoding == 'chunked':
svn: fixed svn operations
r5156 # NOTE(marcink): when getting/uploading files, we want to STREAM content
svn: use streaming uploads/downloads of files....
r3022 # back to the client/proxy instead of buffering it here...
stream = True
stream = stream
svn: add better connection error logging in case the SVN backend is offline
r3573 log.debug('Calling SVN PROXY at `%s`, using method:%s. Stream: %s',
path_info, req_method, stream)
svn: fixed svn operations
r5156
feat(svn): improvements to handle SVN protocol 1.4 features...
r5215 call_kwargs = dict(
data=data_io,
headers=request_headers,
stream=stream
)
if req_method in ['HEAD', 'DELETE']:
fix(svn): svn events fixes and change the way how we handle the events
r5459 # NOTE(marcink): HEAD might be deprecated for SVN 1.14+ protocol
feat(svn): improvements to handle SVN protocol 1.4 features...
r5215 del call_kwargs['data']
svn: add better connection error logging in case the SVN backend is offline
r3573 try:
feat(svn): improvements to handle SVN protocol 1.4 features...
r5215 response = self.session.request(
req_method, path_info, **call_kwargs)
svn: add better connection error logging in case the SVN backend is offline
r3573 except requests.ConnectionError:
log.exception('ConnectionError occurred for endpoint %s', path_info)
raise
project: added all source files and assets
r1
svn: escape special chars to allow interactions with non-standard svn paths....
r1586 if response.status_code not in [200, 401]:
svn: handle non-ascii message editing.
r3827 text = '\n{}'.format(safe_str(response.text)) if response.text else ''
svn: escape special chars to allow interactions with non-standard svn paths....
r1586 if response.status_code >= 500:
svn: add better connection error logging in case the SVN backend is offline
r3573 log.error('Got SVN response:%s with text:`%s`', response, text)
svn: escape special chars to allow interactions with non-standard svn paths....
r1586 else:
svn: add better connection error logging in case the SVN backend is offline
r3573 log.debug('Got SVN response:%s with text:`%s`', response, text)
svn: log response code for SVN calls always even for 200 codes.
r2403 else:
log.debug('got response code: %s', response.status_code)
svn: escape special chars to allow interactions with non-standard svn paths....
r1586
project: added all source files and assets
r1 response_headers = self._get_response_headers(response.headers)
svn: fixed svn operations
r5156 start_response(f'{response.status_code} {response.reason}', response_headers)
project: added all source files and assets
r1 return response.iter_content(chunk_size=1024)
svn: support proxy-prefix properly fixes #5521
r3323 def _get_url(self, svn_http_server, path):
svn_http_server_url = (svn_http_server or '').rstrip('/')
python3: fixed urllib usage
r4950 url_path = urllib.parse.urljoin(svn_http_server_url + '/', (path or '').lstrip('/'))
python3: fix urllib usage
r4914 url_path = urllib.parse.quote(url_path, safe="/:=~+!$,;'")
svn: escape special chars to allow interactions with non-standard svn paths....
r1586 return url_path
project: added all source files and assets
r1
fix(svn): svn events fixes and change the way how we handle the events
r5459 def _get_txn_id(self, environ):
url = environ['RAW_URI']
# Define the regex pattern
pattern = r'/txr/([^/]+)/'
# Search for the pattern in the URL
match = re.search(pattern, url)
# Check if a match is found and extract the captured group
if match:
txn_id = match.group(1)
return txn_id
project: added all source files and assets
r1 def _get_request_headers(self, environ):
headers = {}
fix(svn): fixed svn tests and pass-in the Auth headers back to the svn server
r5224 whitelist = {
'Authorization': {}
}
project: added all source files and assets
r1 for key in environ:
fix(svn): fixed svn tests and pass-in the Auth headers back to the svn server
r5224 if key in whitelist:
headers[key] = environ[key]
elif not key.startswith('HTTP_'):
project: added all source files and assets
r1 continue
fix(svn): fixed svn tests and pass-in the Auth headers back to the svn server
r5224 else:
new_key = key.split('_')
new_key = [k.capitalize() for k in new_key[1:]]
new_key = '-'.join(new_key)
headers[new_key] = environ[key]
project: added all source files and assets
r1
if 'CONTENT_TYPE' in environ:
headers['Content-Type'] = environ['CONTENT_TYPE']
if 'CONTENT_LENGTH' in environ:
headers['Content-Length'] = environ['CONTENT_LENGTH']
return headers
def _get_response_headers(self, headers):
Martin Bornhold
vcs: Add custom response header 'X-RhodeCode-Backend' to indicate VCS responses and which backend is in use.
r608 headers = [
project: added all source files and assets
r1 (h, headers[h])
for h in headers
if h.lower() not in self.IGNORED_HEADERS
]
Martin Bornhold
vcs: Add custom response header 'X-RhodeCode-Backend' to indicate VCS responses and which backend is in use.
r608 return headers
project: added all source files and assets
r1
vcs: moved svn proxy settings into vcs related settings...
r754 class DisabledSimpleSvnApp(object):
def __init__(self, config):
self.config = config
def __call__(self, environ, start_response):
reason = 'Cannot handle SVN call because: SVN HTTP Proxy is not enabled'
log.warning(reason)
return HTTPNotAcceptable(reason)(environ, start_response)
project: added all source files and assets
r1 class SimpleSvn(simplevcs.SimpleVCS):
fix(svn): svn events fixes and change the way how we handle the events
r5459 """
details: https://svn.apache.org/repos/asf/subversion/trunk/notes/http-and-webdav/webdav-protocol
Read Commands : (OPTIONS, PROPFIND, GET, REPORT)
GET: fetch info about resources
PROPFIND: Used to retrieve properties of resources.
REPORT: Used for specialized queries to the repository. E.g History etc...
OPTIONS: request is sent to an SVN server, the server responds with information about the available HTTP
methods and other server capabilities.
Write Commands : (MKACTIVITY, PROPPATCH, PUT, CHECKOUT, MKCOL, MOVE,
-------------- COPY, DELETE, LOCK, UNLOCK, MERGE)
With the exception of LOCK/UNLOCK, every write command performs some
sort of DeltaV commit operation. In DeltaV, a commit always starts
by creating a transaction (MKACTIVITY), applies a log message
(PROPPATCH), does some other write methods, and then ends by
committing the transaction (MERGE). If the MERGE fails, the client
may try to remove the transaction with a DELETE.
PROPPATCH: Used to set and/or remove properties on resources.
MKCOL: Creates a new collection (directory).
DELETE: Removes a resource.
COPY and MOVE: Used for copying and moving resources.
MERGE: Used to merge changes from different branches.
CHECKOUT, CHECKIN, UNCHECKOUT: DeltaV methods for managing working resources and versions.
"""
project: added all source files and assets
r1
SCM = 'svn'
READ_ONLY_COMMANDS = ('OPTIONS', 'PROPFIND', 'GET', 'REPORT')
fix(svn): svn events fixes and change the way how we handle the events
r5459 WRITE_COMMANDS = ('MERGE', 'POST', 'PUT', 'COPY', 'MOVE', 'DELETE', 'MKCOL')
DEFAULT_HTTP_SERVER = 'http://svn:8090'
project: added all source files and assets
r1
def _get_repository_name(self, environ):
"""
Gets repository name out of PATH_INFO header
:param environ: environ where PATH_INFO is stored
"""
PATH_INFO: use new method to consistently extract proper PATH_INFO data
r5032 path = get_path_info(environ).split('!')
project: added all source files and assets
r1 repo_name = path[0].strip('/')
# SVN includes the whole path in it's requests, including
# subdirectories inside the repo. Therefore we have to search for
# the repo root directory.
svn: pass in explicit SCM into the call function for faster detection of protocol.
r2402 if not is_valid_repo(
repo_name, self.base_path, explicit_scm=self.SCM):
project: added all source files and assets
r1 current_path = ''
for component in repo_name.split('/'):
current_path += component
svn: pass in explicit SCM into the call function for faster detection of protocol.
r2402 if is_valid_repo(
current_path, self.base_path, explicit_scm=self.SCM):
project: added all source files and assets
r1 return current_path
current_path += '/'
return repo_name
def _get_action(self, environ):
return (
'pull'
if environ['REQUEST_METHOD'] in self.READ_ONLY_COMMANDS
else 'push')
svn: enable hooks and integration framework execution....
r2677 def _should_use_callback_daemon(self, extras, environ, action):
fix(svn): svn events fixes and change the way how we handle the events
r5459 # only PUT & MERGE command triggers hooks, so we don't want to start
svn: enable hooks and integration framework execution....
r2677 # hooks server too many times. POST however starts the svn transaction
# so we also need to run the init of callback daemon of POST
fix(svn): svn events fixes and change the way how we handle the events
r5459 if environ['REQUEST_METHOD'] not in self.READ_ONLY_COMMANDS:
svn: enable hooks and integration framework execution....
r2677 return True
return False
project: added all source files and assets
r1 def _create_wsgi_app(self, repo_path, repo_name, config):
vcs: moved svn proxy settings into vcs related settings...
r754 if self._is_svn_enabled():
return SimpleSvnApp(config)
# we don't have http proxy enabled return dummy request handler
return DisabledSimpleSvnApp(config)
def _is_svn_enabled(self):
feat(svn-config): moved svn related config keys to *.ini file. Fixes: RCCE-60
r5328 return ConfigGet().get_bool('vcs.svn.proxy.enabled')
project: added all source files and assets
r1
dan
git-lfs: fixed bug #5399 git-lfs application failed to generate HTTPS urls properly.
r3781 def _create_config(self, extras, repo_name, scheme='http'):
feat(svn-config): moved svn related config keys to *.ini file. Fixes: RCCE-60
r5328 server_url = ConfigGet().get_str('vcs.svn.proxy.host')
vcs: moved svn proxy settings into vcs related settings...
r754 server_url = server_url or self.DEFAULT_HTTP_SERVER
extras['subversion_http_server_url'] = server_url
project: added all source files and assets
r1 return extras