commit.py
254 lines
| 8.4 KiB
| text/x-python
|
PythonLexer
r5088 | # Copyright (C) 2014-2023 RhodeCode GmbH | |||
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 commit module | ||||
""" | ||||
import dateutil.parser | ||||
from zope.cachedescriptors.property import Lazy as LazyProperty | ||||
r5074 | from rhodecode.lib.str_utils import safe_bytes, safe_str | |||
r1 | from rhodecode.lib.vcs import nodes, path as vcspath | |||
from rhodecode.lib.vcs.backends import base | ||||
r5074 | from rhodecode.lib.vcs.exceptions import CommitError | |||
r1 | ||||
_SVN_PROP_TRUE = '*' | ||||
class SubversionCommit(base.BaseCommit): | ||||
""" | ||||
Subversion specific implementation of commits | ||||
.. attribute:: branch | ||||
The Subversion backend does not support to assign branches to | ||||
specific commits. This attribute has always the value `None`. | ||||
""" | ||||
def __init__(self, repository, commit_id): | ||||
self.repository = repository | ||||
self.idx = self.repository._get_commit_idx(commit_id) | ||||
self._svn_rev = self.idx + 1 | ||||
self._remote = repository._remote | ||||
# TODO: handling of raw_id should be a method on repository itself, | ||||
# which knows how to translate commit index and commit id | ||||
self.raw_id = commit_id | ||||
self.short_id = commit_id | ||||
r5096 | self.id = f'r{commit_id}' | |||
r1 | ||||
# TODO: Implement the following placeholder attributes | ||||
self.nodes = {} | ||||
self.tags = [] | ||||
@property | ||||
def author(self): | ||||
r5074 | return safe_str(self._properties.get('svn:author')) | |||
r1 | ||||
@property | ||||
def date(self): | ||||
return _date_from_svn_properties(self._properties) | ||||
@property | ||||
def message(self): | ||||
r5074 | return safe_str(self._properties.get('svn:log')) | |||
r1 | ||||
@LazyProperty | ||||
def _properties(self): | ||||
return self._remote.revision_properties(self._svn_rev) | ||||
@LazyProperty | ||||
def parents(self): | ||||
parent_idx = self.idx - 1 | ||||
if parent_idx >= 0: | ||||
parent = self.repository.get_commit(commit_idx=parent_idx) | ||||
return [parent] | ||||
return [] | ||||
@LazyProperty | ||||
def children(self): | ||||
child_idx = self.idx + 1 | ||||
if child_idx < len(self.repository.commit_ids): | ||||
child = self.repository.get_commit(commit_idx=child_idx) | ||||
return [child] | ||||
return [] | ||||
r5074 | def get_file_mode(self, path: bytes): | |||
r1 | # Note: Subversion flags files which are executable with a special | |||
# property `svn:executable` which is set to the value ``"*"``. | ||||
if self._get_file_property(path, 'svn:executable') == _SVN_PROP_TRUE: | ||||
return base.FILEMODE_EXECUTABLE | ||||
else: | ||||
return base.FILEMODE_DEFAULT | ||||
def is_link(self, path): | ||||
# Note: Subversion has a flag for special files, the content of the | ||||
# file contains the type of that file. | ||||
if self._get_file_property(path, 'svn:special') == _SVN_PROP_TRUE: | ||||
r5074 | return self.get_file_content(path).startswith(b'link') | |||
r1 | return False | |||
r3896 | def is_node_binary(self, path): | |||
path = self._fix_path(path) | ||||
return self._remote.is_binary(self._svn_rev, safe_str(path)) | ||||
r5074 | def node_md5_hash(self, path): | |||
path = self._fix_path(path) | ||||
return self._remote.md5_hash(self._svn_rev, safe_str(path)) | ||||
r1 | def _get_file_property(self, path, name): | |||
file_properties = self._remote.node_properties( | ||||
safe_str(path), self._svn_rev) | ||||
return file_properties.get(name) | ||||
def get_file_content(self, path): | ||||
path = self._fix_path(path) | ||||
r5074 | return self._remote.get_file_content(self._svn_rev, safe_str(path)) | |||
r1 | ||||
r3895 | def get_file_content_streamed(self, path): | |||
path = self._fix_path(path) | ||||
r5074 | ||||
r3895 | stream_method = getattr(self._remote, 'stream:get_file_content') | |||
r5074 | return stream_method(self._svn_rev, safe_str(path)) | |||
r3895 | ||||
r1 | def get_file_size(self, path): | |||
path = self._fix_path(path) | ||||
r5074 | return self._remote.get_file_size(self._svn_rev, safe_str(path)) | |||
r1 | ||||
r3275 | def get_path_history(self, path, limit=None, pre_load=None): | |||
r1 | path = safe_str(self._fix_path(path)) | |||
history = self._remote.node_history(path, self._svn_rev, limit) | ||||
return [ | ||||
self.repository.get_commit(commit_id=str(svn_rev)) | ||||
for svn_rev in history] | ||||
def get_file_annotate(self, path, pre_load=None): | ||||
result = self._remote.file_annotate(safe_str(path), self._svn_rev) | ||||
for zero_based_line_no, svn_rev, content in result: | ||||
commit_id = str(svn_rev) | ||||
line_no = zero_based_line_no + 1 | ||||
yield ( | ||||
line_no, | ||||
commit_id, | ||||
lambda: self.repository.get_commit(commit_id=commit_id), | ||||
content) | ||||
r1355 | def get_node(self, path, pre_load=None): | |||
r1 | path = self._fix_path(path) | |||
if path not in self.nodes: | ||||
if path == '': | ||||
node = nodes.RootNode(commit=self) | ||||
else: | ||||
r5074 | node_type = self._remote.get_node_type(self._svn_rev, safe_str(path)) | |||
r1 | if node_type == 'dir': | |||
r5074 | node = nodes.DirNode(safe_bytes(path), commit=self) | |||
r1 | elif node_type == 'file': | |||
r5074 | node = nodes.FileNode(safe_bytes(path), commit=self, pre_load=pre_load) | |||
r1 | else: | |||
r1926 | raise self.no_node_at_path(path) | |||
r1 | ||||
self.nodes[path] = node | ||||
return self.nodes[path] | ||||
r5074 | def get_nodes(self, path, pre_load=None): | |||
r1 | if self._get_kind(path) != nodes.NodeKind.DIR: | |||
raise CommitError( | ||||
r5074 | f"Directory does not exist for commit {self.raw_id} at '{path}'") | |||
r4338 | path = safe_str(self._fix_path(path)) | |||
r1 | ||||
path_nodes = [] | ||||
r5074 | for name, kind in self._remote.get_nodes(self._svn_rev, path): | |||
r1 | node_path = vcspath.join(path, name) | |||
if kind == 'dir': | ||||
r5074 | node = nodes.DirNode(safe_bytes(node_path), commit=self) | |||
r1 | elif kind == 'file': | |||
r5074 | node = nodes.FileNode(safe_bytes(node_path), commit=self, pre_load=pre_load) | |||
r1 | else: | |||
r5074 | raise ValueError(f"Node kind {kind} not supported.") | |||
r1 | self.nodes[node_path] = node | |||
path_nodes.append(node) | ||||
return path_nodes | ||||
def _get_kind(self, path): | ||||
path = self._fix_path(path) | ||||
r5074 | kind = self._remote.get_node_type(self._svn_rev, path) | |||
r1 | if kind == 'file': | |||
return nodes.NodeKind.FILE | ||||
elif kind == 'dir': | ||||
return nodes.NodeKind.DIR | ||||
else: | ||||
raise CommitError( | ||||
r5096 | f"Node does not exist at the given path '{path}'") | |||
r1 | ||||
@LazyProperty | ||||
def _changes_cache(self): | ||||
return self._remote.revision_changes(self._svn_rev) | ||||
@LazyProperty | ||||
def affected_files(self): | ||||
changed_files = set() | ||||
r4961 | for files in self._changes_cache.values(): | |||
r1 | changed_files.update(files) | |||
return list(changed_files) | ||||
r621 | @LazyProperty | |||
def id(self): | ||||
return self.raw_id | ||||
r1 | @property | |||
def added(self): | ||||
r4242 | return nodes.AddedFileNodesGenerator(self.added_paths, self) | |||
@LazyProperty | ||||
def added_paths(self): | ||||
return [n for n in self._changes_cache['added']] | ||||
r1 | ||||
@property | ||||
def changed(self): | ||||
r4242 | return nodes.ChangedFileNodesGenerator(self.changed_paths, self) | |||
@LazyProperty | ||||
def changed_paths(self): | ||||
return [n for n in self._changes_cache['changed']] | ||||
r1 | ||||
@property | ||||
def removed(self): | ||||
r4242 | return nodes.RemovedFileNodesGenerator(self.removed_paths, self) | |||
@LazyProperty | ||||
def removed_paths(self): | ||||
return [n for n in self._changes_cache['removed']] | ||||
r1 | ||||
def _date_from_svn_properties(properties): | ||||
""" | ||||
Parses the date out of given svn properties. | ||||
:return: :class:`datetime.datetime` instance. The object is naive. | ||||
""" | ||||
r1348 | ||||
r1 | aware_date = dateutil.parser.parse(properties.get('svn:date')) | |||
r1348 | # final_date = aware_date.astimezone(dateutil.tz.tzlocal()) | |||
final_date = aware_date | ||||
return final_date.replace(tzinfo=None) | ||||