##// END OF EJS Templates
fix(svn): cache path check similar like for git
fix(svn): cache path check similar like for git

File last commit:

r5216:07778025 default
r5565:78129c9a default
Show More
repository.py
361 lines | 12.6 KiB | text/x-python | PythonLexer
copyrights: updated for 2023
r5088 # Copyright (C) 2014-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/
"""
SVN repository module
"""
import logging
import os
vcs-lib: bulk of changes for python3 support
r5074 import urllib.request
import urllib.parse
import urllib.error
project: added all source files and assets
r1
from zope.cachedescriptors.property import Lazy as LazyProperty
python3: removed compat modules
r4928 from collections import OrderedDict
project: added all source files and assets
r1 from rhodecode.lib.datelib import date_astimestamp
vcs-lib: bulk of changes for python3 support
r5074 from rhodecode.lib.str_utils import safe_str
git: adjusted code for new libgit2 backend...
r3842 from rhodecode.lib.utils2 import CachedProperty
project: added all source files and assets
r1 from rhodecode.lib.vcs import connection, path as vcspath
from rhodecode.lib.vcs.backends import base
from rhodecode.lib.vcs.backends.svn.commit import (
SubversionCommit, _date_from_svn_properties)
from rhodecode.lib.vcs.backends.svn.diff import SubversionDiff
from rhodecode.lib.vcs.backends.svn.inmemory import SubversionInMemoryCommit
from rhodecode.lib.vcs.conf import settings
from rhodecode.lib.vcs.exceptions import (
CommitDoesNotExistError, EmptyRepositoryError, RepositoryError,
VCSError, NodeDoesNotExistError)
log = logging.getLogger(__name__)
class SubversionRepository(base.BaseRepository):
"""
Subversion backend implementation
.. important::
It is very important to distinguish the commit index and the commit id
which is assigned by Subversion. The first one is always handled as an
`int` by this implementation. The commit id assigned by Subversion on
the other side will always be a `str`.
There is a specific trap since the first commit will have the index
``0`` but the svn id will be ``"1"``.
"""
# Note: Subversion does not really have a default branch name.
DEFAULT_BRANCH_NAME = None
contact = base.BaseRepository.DEFAULT_CONTACT
description = base.BaseRepository.DEFAULT_DESCRIPTION
caches: updated cache backend to new vcsserver caches implementation.
r3848 def __init__(self, repo_path, config=None, create=False, src_url=None, with_wire=None,
bare=False, **kwargs):
project: added all source files and assets
r1 self.path = safe_str(os.path.abspath(repo_path))
mercurial: fix new 4.4.X code change that does strict requirement checks....
r2518 self.config = config if config else self.get_default_config()
caches: updated cache backend to new vcsserver caches implementation.
r3848 self.with_wire = with_wire or {"cache": False} # default should not use cache
project: added all source files and assets
r1
self._init_repo(create, src_url)
git: adjusted code for new libgit2 backend...
r3842 # caches
self._commit_ids = {}
caches: make gevent curl connection cache friendly....
r2946 @LazyProperty
def _remote(self):
caches: updated cache backend to new vcsserver caches implementation.
r3848 repo_id = self.path
return connection.Svn(self.path, repo_id, self.config, with_wire=self.with_wire)
caches: make gevent curl connection cache friendly....
r2946
project: added all source files and assets
r1 def _init_repo(self, create, src_url):
if create and os.path.exists(self.path):
raise RepositoryError(
feat(git/svn): remove filesystem modifications from git/svn calls. When sharding comes in place we can't do this....
r5216 f"Cannot create repository at {self.path}, location already exist")
project: added all source files and assets
r1
if create:
self._remote.create_repository(settings.SVN_COMPATIBLE_VERSION)
if src_url:
src_url = _sanitize_url(src_url)
self._remote.import_remote_repository(src_url)
else:
feat(git/svn): remove filesystem modifications from git/svn calls. When sharding comes in place we can't do this....
r5216 if not self._remote.is_path_valid_repository(self.path):
raise VCSError(
f'Path "{self.path}" does not contain a Subversion repository')
project: added all source files and assets
r1
git: adjusted code for new libgit2 backend...
r3842 @CachedProperty
project: added all source files and assets
r1 def commit_ids(self):
head = self._remote.lookup(None)
python3: removed use of xrang
r4906 return [str(r) for r in range(1, head + 1)]
project: added all source files and assets
r1
commits: updated logic of in-memory-commits, fixed tests and re-architectured a bit how commit_ids are calculated and updated....
r3743 def _rebuild_cache(self, commit_ids):
pass
svn: expose run_svn_command
r3574 def run_svn_command(self, cmd, **opts):
"""
Runs given ``cmd`` as svn command and returns tuple
(stdout, stderr).
:param cmd: full svn command to be executed
:param opts: env options to pass into Subprocess command
"""
if not isinstance(cmd, list):
vcs-lib: bulk of changes for python3 support
r5074 raise ValueError(f'cmd must be a list, got {type(cmd)} instead')
svn: expose run_svn_command
r3574
skip_stderr_log = opts.pop('skip_stderr_log', False)
out, err = self._remote.run_svn_command(cmd, **opts)
if err and not skip_stderr_log:
log.debug('Stderr output of svn command "%s":\n%s', cmd, err)
return out, err
project: added all source files and assets
r1 @LazyProperty
def branches(self):
return self._tags_or_branches('vcs_svn_branch')
@LazyProperty
def branches_closed(self):
return {}
@LazyProperty
vcs: use proper attributes/inits on base and child classes.
r2617 def bookmarks(self):
return {}
@LazyProperty
project: added all source files and assets
r1 def branches_all(self):
# TODO: johbo: Implement proper branch support
all_branches = {}
all_branches.update(self.branches)
all_branches.update(self.branches_closed)
return all_branches
@LazyProperty
def tags(self):
return self._tags_or_branches('vcs_svn_tag')
def _tags_or_branches(self, config_section):
found_items = {}
if self.is_empty():
return {}
for pattern in self._patterns_from_section(config_section):
pattern = vcspath.sanitize(pattern)
tip = self.get_commit()
try:
if pattern.endswith('*'):
basedir = tip.get_node(vcspath.dirname(pattern))
directories = basedir.dirs
else:
directories = (tip.get_node(pattern), )
except NodeDoesNotExistError:
continue
vcs-lib: bulk of changes for python3 support
r5074 found_items.update((safe_str(n.path), self.commit_ids[-1]) for n in directories)
project: added all source files and assets
r1
def get_name(item):
return item[0]
return OrderedDict(sorted(found_items.items(), key=get_name))
def _patterns_from_section(self, section):
return (pattern for key, pattern in self.config.items(section))
def get_common_ancestor(self, commit_id1, commit_id2, repo2):
if self != repo2:
raise ValueError(
"Subversion does not support getting common ancestor of"
" different repositories.")
if int(commit_id1) < int(commit_id2):
return commit_id1
return commit_id2
maintenance: added svn verify command to tasks to be able to verify...
r1765 def verify(self):
verify = self._remote.verify()
self._remote.invalidate_vcs_cache()
return verify
project: added all source files and assets
r1 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
# TODO: johbo: Implement better comparison, this is a very naive
# version which does not allow to compare branches, tags or folders
# at all.
if repo2 != self:
raise ValueError(
"Subversion does not support comparison of of different "
"repositories.")
if commit_id1 == commit_id2:
return []
commit_idx1 = self._get_commit_idx(commit_id1)
commit_idx2 = self._get_commit_idx(commit_id2)
commits = [
self.get_commit(commit_idx=idx)
for idx in range(commit_idx1 + 1, commit_idx2 + 1)]
return commits
def _get_commit_idx(self, commit_id):
try:
svn_rev = int(commit_id)
except:
# TODO: johbo: this might be only one case, HEAD, check this
svn_rev = self._remote.lookup(commit_id)
commit_idx = svn_rev - 1
if commit_idx >= len(self.commit_ids):
raise CommitDoesNotExistError(
modernize: python3 updates
r5096 f"Commit at index {commit_idx} does not exist.")
project: added all source files and assets
r1 return commit_idx
@staticmethod
def check_url(url, config):
"""
Check if `url` is a valid source to import a Subversion repository.
"""
# convert to URL if it's a local directory
if os.path.isdir(url):
python3: fix urllib usage
r4914 url = 'file://' + urllib.request.pathname2url(url)
project: added all source files and assets
r1 return connection.Svn.check_url(url, config.serialize())
@staticmethod
def is_valid_repository(path):
try:
SubversionRepository(path)
return True
except VCSError:
pass
return False
@LazyProperty
def last_change(self):
"""
Returns last change made on this repository as
`datetime.datetime` object.
"""
# Subversion always has a first commit which has id "0" and contains
# what we are looking for.
last_id = len(self.commit_ids)
properties = self._remote.revision_properties(last_id)
return _date_from_svn_properties(properties)
@LazyProperty
def in_memory_commit(self):
return SubversionInMemoryCommit(self)
def get_hook_location(self):
"""
returns absolute path to location where hooks are stored
"""
return os.path.join(self.path, 'hooks')
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
backends: use reference explicitly to properly translate GIT references to commits such as numeric branches
r4653 translate_tag=None, maybe_unreachable=False, reference_obj=None):
project: added all source files and assets
r1 if self.is_empty():
raise EmptyRepositoryError("There are no commits yet")
if commit_id is not None:
self._validate_commit_id(commit_id)
elif commit_idx is not None:
self._validate_commit_idx(commit_idx)
try:
commit_id = self.commit_ids[commit_idx]
except IndexError:
libs: more python3 reformats
r5091 raise CommitDoesNotExistError(f'No commit with idx: {commit_idx}')
project: added all source files and assets
r1
commit_id = self._sanitize_commit_id(commit_id)
commit = SubversionCommit(repository=self, commit_id=commit_id)
return commit
def get_commits(
self, start_id=None, end_id=None, start_date=None, end_date=None,
commits: allow tag commit translation to be skipped for faster commit fetching in big chunks.
r3468 branch_name=None, show_hidden=False, pre_load=None, translate_tags=None):
project: added all source files and assets
r1 if self.is_empty():
raise EmptyRepositoryError("There are no commit_ids yet")
self._validate_branch_name(branch_name)
if start_id is not None:
self._validate_commit_id(start_id)
if end_id is not None:
self._validate_commit_id(end_id)
start_raw_id = self._sanitize_commit_id(start_id)
start_pos = self.commit_ids.index(start_raw_id) if start_id else None
end_raw_id = self._sanitize_commit_id(end_id)
end_pos = max(0, self.commit_ids.index(end_raw_id)) if end_id else None
if None not in [start_id, end_id] and start_pos > end_pos:
raise RepositoryError(
"Start commit '%s' cannot be after end commit '%s'" %
(start_id, end_id))
if end_pos is not None:
end_pos += 1
# Date based filtering
if start_date or end_date:
start_raw_id, end_raw_id = self._remote.lookup_interval(
date_astimestamp(start_date) if start_date else None,
date_astimestamp(end_date) if end_date else None)
start_pos = start_raw_id - 1
end_pos = end_raw_id
commit_ids = self.commit_ids
# TODO: johbo: Reconsider impact of DEFAULT_BRANCH_NAME here
if branch_name not in [None, self.DEFAULT_BRANCH_NAME]:
vcs-lib: bulk of changes for python3 support
r5074 svn_rev = int(self.commit_ids[-1])
project: added all source files and assets
r1 commit_ids = self._remote.node_history(
path=branch_name, revision=svn_rev, limit=None)
commit_ids = [str(i) for i in reversed(commit_ids)]
if start_pos or end_pos:
commit_ids = commit_ids[start_pos:end_pos]
return base.CollectionGenerator(self, commit_ids, pre_load=pre_load)
def _sanitize_commit_id(self, commit_id):
if commit_id and commit_id.isdigit():
if int(commit_id) <= len(self.commit_ids):
return commit_id
else:
raise CommitDoesNotExistError(
modernize: python3 updates
r5096 f"Commit {commit_id} does not exist.")
project: added all source files and assets
r1 if commit_id not in [
None, 'HEAD', 'tip', self.DEFAULT_BRANCH_NAME]:
raise CommitDoesNotExistError(
modernize: python3 updates
r5096 f"Commit id {commit_id} not understood.")
project: added all source files and assets
r1 svn_rev = self._remote.lookup('HEAD')
return str(svn_rev)
def get_diff(
self, commit1, commit2, path=None, ignore_whitespace=False,
context=3, path1=None):
self._validate_diff_commits(commit1, commit2)
vcs-lib: bulk of changes for python3 support
r5074 svn_rev1 = int(commit1.raw_id)
svn_rev2 = int(commit2.raw_id)
project: added all source files and assets
r1 diff = self._remote.diff(
svn_rev1, svn_rev2, path1=path1, path2=path,
ignore_whitespace=ignore_whitespace, context=context)
return SubversionDiff(diff)
def _sanitize_url(url):
if '://' not in url:
python3: fix urllib usage
r4914 url = 'file://' + urllib.request.pathname2url(url)
project: added all source files and assets
r1 return url