##// END OF EJS Templates
vcs-support: bulk of changes for python3
super-admin -
r5075:d1c4b80b default
parent child Browse files
Show More
@@ -26,7 +26,9 b' import copy'
26 26 import logging
27 27 import threading
28 28 import time
29 import urllib.request, urllib.error, urllib.parse
29 import urllib.request
30 import urllib.error
31 import urllib.parse
30 32 import urllib.parse
31 33 import uuid
32 34 import traceback
@@ -59,6 +61,7 b' def _remote_call(url, payload, exception'
59 61 for attempt in range(retries):
60 62 try:
61 63 response = session.post(url, data=msgpack.packb(payload))
64 break
62 65 except pycurl.error as e:
63 66 error_code, error_message = e.args
64 67 if error_code == pycurl.E_RECV_ERROR:
@@ -76,12 +79,13 b' def _remote_call(url, payload, exception'
76 79 raise
77 80
78 81 if response.status_code >= 400:
79 log.error('Call to %s returned non 200 HTTP code: %s',
80 url, response.status_code)
82 content_type = response.content_type
83 log.error('Call to %s returned non 200 HTTP code: %s [%s]',
84 url, response.status_code, content_type)
81 85 raise exceptions.HttpVCSCommunicationError(repr(response.content))
82 86
83 87 try:
84 response = msgpack.unpackb(response.content, raw=False)
88 response = msgpack.unpackb(response.content)
85 89 except Exception:
86 90 log.exception('Failed to decode response from msgpack')
87 91 raise
@@ -103,10 +107,20 b' def _remote_call(url, payload, exception'
103 107 except KeyError:
104 108 pass
105 109
106 raise exc
110 exc.add_note(attach_exc_details(error))
111 raise exc # raising the org exception from vcsserver
107 112 return response.get('result')
108 113
109 114
115 def attach_exc_details(error):
116 note = '-- EXC NOTE -- :\n'
117 note += f'vcs_kind: {error.get("_vcs_kind")}\n'
118 note += f'org_exc: {error.get("_vcs_kind")}\n'
119 note += f'tb: {error.get("traceback")}\n'
120 note += '-- END EXC NOTE --'
121 return note
122
123
110 124 def _streaming_remote_call(url, payload, exceptions_map, session, chunk_size):
111 125 try:
112 126 headers = {
@@ -166,7 +180,7 b' class RemoteVCSMaker(object):'
166 180
167 181 @classmethod
168 182 def init_cache_region(cls, repo_id):
169 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
183 cache_namespace_uid = 'repo.{}'.format(repo_id)
170 184 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
171 185 return region, cache_namespace_uid
172 186
@@ -267,7 +281,7 b' class RemoteRepo(object):'
267 281 def get_local_cache(self, name, args):
268 282 cache_on = False
269 283 cache_key = ''
270 local_cache_on = str2bool(rhodecode.CONFIG.get('vcs.methods.cache'))
284 local_cache_on = rhodecode.ConfigGet().get_bool('vcs.methods.cache')
271 285
272 286 cache_methods = [
273 287 'branches', 'tags', 'bookmarks',
@@ -300,7 +314,7 b' class RemoteRepo(object):'
300 314 namespace=self._cache_namespace, condition=cache_on and cache_key)
301 315 def remote_call(_cache_key):
302 316 if self._call_with_logging:
303 args_repr = f'ARG: {str(args):.256}|KW: {str(kwargs):.256}'
317 args_repr = f'ARG: {str(args):.512}|KW: {str(kwargs):.512}'
304 318 log.debug('Calling %s@%s with args:%r. wire_context: %s cache_on: %s',
305 319 url, name, args_repr, context_uid, cache_on)
306 320 return _remote_call(url, payload, EXCEPTIONS_MAP, self._session)
@@ -323,7 +337,7 b' class RemoteRepo(object):'
323 337 # Cache is a problem because this is a stream
324 338 def streaming_remote_call(_cache_key):
325 339 if self._call_with_logging:
326 args_repr = f'ARG: {str(args):.256}|KW: {str(kwargs):.256}'
340 args_repr = f'ARG: {str(args):.512}|KW: {str(kwargs):.512}'
327 341 log.debug('Calling %s@%s with args:%r. wire_context: %s cache_on: %s',
328 342 url, name, args_repr, context_uid, cache_on)
329 343 return _streaming_remote_call(url, payload, EXCEPTIONS_MAP, self._session, self.CHUNK_SIZE)
@@ -22,7 +22,7 b''
22 22 Internal settings for vcs-lib
23 23 """
24 24
25 # list of default encoding used in safe_unicode/safe_str methods
25 # list of default encoding used in safe_str methods
26 26 DEFAULT_ENCODINGS = ['utf8']
27 27
28 28
@@ -23,7 +23,8 b' Custom vcs exceptions module.'
23 23 """
24 24 import logging
25 25 import functools
26 import urllib.request, urllib.error, urllib.parse
26 import urllib.error
27 import urllib.parse
27 28 import rhodecode
28 29
29 30 log = logging.getLogger(__name__)
@@ -185,12 +186,12 b' def map_vcs_exceptions(func):'
185 186 try:
186 187 return func(*args, **kwargs)
187 188 except Exception as e:
188 from rhodecode.lib.utils2 import str2bool
189 debug = str2bool(rhodecode.CONFIG.get('debug'))
189 debug = rhodecode.ConfigGet().get_bool('debug')
190 190
191 191 # The error middleware adds information if it finds
192 192 # __traceback_info__ in a frame object. This way the remote
193 193 # traceback information is made available in error reports.
194
194 195 remote_tb = getattr(e, '_vcs_server_traceback', None)
195 196 org_remote_tb = getattr(e, '_vcs_server_org_exc_tb', '')
196 197 __traceback_info__ = None
@@ -21,16 +21,15 b''
21 21 """
22 22 Module holding everything related to vcs nodes, with vcs2 architecture.
23 23 """
24
24 import functools
25 25 import os
26 26 import stat
27 27
28 28 from zope.cachedescriptors.property import Lazy as LazyProperty
29 29
30 import rhodecode
31 30 from rhodecode.config.conf import LANGUAGES_EXTENSIONS_MAP
32 from rhodecode.lib.utils import safe_unicode, safe_str
33 from rhodecode.lib.utils2 import md5
31 from rhodecode.lib.str_utils import safe_str, safe_bytes
32 from rhodecode.lib.hash_utils import md5
34 33 from rhodecode.lib.vcs import path as vcspath
35 34 from rhodecode.lib.vcs.backends.base import EmptyCommit, FILEMODE_DEFAULT
36 35 from rhodecode.lib.vcs.conf.mtypes import get_mimetypes_db
@@ -52,6 +51,10 b' class NodeState:'
52 51 NOT_CHANGED = 'not changed'
53 52 REMOVED = 'removed'
54 53
54 #TODO: not sure if that should be bytes or str ?
55 # most probably bytes because content should be bytes and we check it
56 BIN_BYTE_MARKER = b'\0'
57
55 58
56 59 class NodeGeneratorBase(object):
57 60 """
@@ -68,9 +71,10 b' class NodeGeneratorBase(object):'
68 71 def __call__(self):
69 72 return [n for n in self]
70 73
71 def __getslice__(self, i, j):
72 for p in self.current_paths[i:j]:
73 yield self.cs.get_node(p)
74 def __getitem__(self, key):
75 if isinstance(key, slice):
76 for p in self.current_paths[key.start:key.stop]:
77 yield self.cs.get_node(p)
74 78
75 79 def __len__(self):
76 80 return len(self.current_paths)
@@ -98,13 +102,15 b' class RemovedFileNodesGenerator(NodeGene'
98 102 """
99 103 def __iter__(self):
100 104 for p in self.current_paths:
101 yield RemovedFileNode(path=p)
105 yield RemovedFileNode(path=safe_bytes(p))
102 106
103 def __getslice__(self, i, j):
104 for p in self.current_paths[i:j]:
105 yield RemovedFileNode(path=p)
107 def __getitem__(self, key):
108 if isinstance(key, slice):
109 for p in self.current_paths[key.start:key.stop]:
110 yield RemovedFileNode(path=safe_bytes(p))
106 111
107 112
113 @functools.total_ordering
108 114 class Node(object):
109 115 """
110 116 Simplest class representing file or directory on repository. SCM backends
@@ -115,14 +121,19 b' class Node(object):'
115 121 only. Moreover, every single node is identified by the ``path`` attribute,
116 122 so it cannot end with slash, too. Otherwise, path could lead to mistakes.
117 123 """
118 RTLO_MARKER = "\u202E" # RTLO marker allows swapping text, and certain
119 # security attacks could be used with this
124 # RTLO marker allows swapping text, and certain
125 # security attacks could be used with this
126 RTLO_MARKER = "\u202E"
127
120 128 commit = None
121 129
122 def __init__(self, path, kind):
130 def __init__(self, path: bytes, kind):
123 131 self._validate_path(path) # can throw exception if path is invalid
124 self.path = safe_str(path.rstrip('/')) # we store paths as str
125 if path == '' and kind != NodeKind.DIR:
132
133 self.bytes_path = path.rstrip(b'/') # store for __repr__
134 self.path = safe_str(self.bytes_path) # we store paths as str
135
136 if self.bytes_path == b'' and kind != NodeKind.DIR:
126 137 raise NodeError("Only DirNode and its subclasses may be "
127 138 "initialized with empty path")
128 139 self.kind = kind
@@ -130,12 +141,65 b' class Node(object):'
130 141 if self.is_root() and not self.is_dir():
131 142 raise NodeError("Root node cannot be FILE kind")
132 143
133 def _validate_path(self, path):
134 if path.startswith('/'):
144 def __eq__(self, other):
145 if type(self) is not type(other):
146 return False
147 for attr in ['name', 'path', 'kind']:
148 if getattr(self, attr) != getattr(other, attr):
149 return False
150 if self.is_file():
151 # FileNode compare, we need to fallback to content compare
152 return None
153 else:
154 # For DirNode's check without entering each dir
155 self_nodes_paths = list(sorted(n.path for n in self.nodes))
156 other_nodes_paths = list(sorted(n.path for n in self.nodes))
157 if self_nodes_paths != other_nodes_paths:
158 return False
159 return True
160
161 def __lt__(self, other):
162 if self.kind < other.kind:
163 return True
164 if self.kind > other.kind:
165 return False
166 if self.path < other.path:
167 return True
168 if self.path > other.path:
169 return False
170
171 # def __cmp__(self, other):
172 # """
173 # Comparator using name of the node, needed for quick list sorting.
174 # """
175 #
176 # kind_cmp = cmp(self.kind, other.kind)
177 # if kind_cmp:
178 # if isinstance(self, SubModuleNode):
179 # # we make submodules equal to dirnode for "sorting" purposes
180 # return NodeKind.DIR
181 # return kind_cmp
182 # return cmp(self.name, other.name)
183
184 def __repr__(self):
185 maybe_path = getattr(self, 'path', 'UNKNOWN_PATH')
186 return f'<{self.__class__.__name__} {maybe_path!r}>'
187
188 def __str__(self):
189 return self.name
190
191 def _validate_path(self, path: bytes):
192 self._assert_bytes(path)
193
194 if path.startswith(b'/'):
135 195 raise NodeError(
136 "Cannot initialize Node objects with slash at "
137 "the beginning as only relative paths are supported. "
138 "Got %s" % (path,))
196 f"Cannot initialize Node objects with slash at "
197 f"the beginning as only relative paths are supported. "
198 f"Got {path}")
199
200 def _assert_bytes(self, value):
201 if not isinstance(value, bytes):
202 raise TypeError(f"Bytes required as input, got {type(value)} of {value}.")
139 203
140 204 @LazyProperty
141 205 def parent(self):
@@ -147,22 +211,13 b' class Node(object):'
147 211 return None
148 212
149 213 @LazyProperty
150 def unicode_path(self):
151 return safe_unicode(self.path)
214 def str_path(self) -> str:
215 return safe_str(self.path)
152 216
153 217 @LazyProperty
154 218 def has_rtlo(self):
155 219 """Detects if a path has right-to-left-override marker"""
156 return self.RTLO_MARKER in self.unicode_path
157
158 @LazyProperty
159 def unicode_path_safe(self):
160 """
161 Special SAFE representation of path without the right-to-left-override.
162 This should be only used for "showing" the file, cannot be used for any
163 urls etc.
164 """
165 return safe_unicode(self.path).replace(self.RTLO_MARKER, '')
220 return self.RTLO_MARKER in self.str_path
166 221
167 222 @LazyProperty
168 223 def dir_path(self):
@@ -172,7 +227,7 b' class Node(object):'
172 227 """
173 228 _parts = self.path.rstrip('/').rsplit('/', 1)
174 229 if len(_parts) == 2:
175 return safe_unicode(_parts[0])
230 return _parts[0]
176 231 return ''
177 232
178 233 @LazyProperty
@@ -181,7 +236,7 b' class Node(object):'
181 236 Returns name of the node so if its path
182 237 then only last part is returned.
183 238 """
184 return safe_unicode(self.path.rstrip('/').split('/')[-1])
239 return self.path.rstrip('/').split('/')[-1]
185 240
186 241 @property
187 242 def kind(self):
@@ -197,53 +252,15 b' class Node(object):'
197 252 if self.path.endswith('/'):
198 253 raise NodeError("Node's path cannot end with slash")
199 254
200 def __cmp__(self, other):
201 """
202 Comparator using name of the node, needed for quick list sorting.
203 """
204
205 kind_cmp = cmp(self.kind, other.kind)
206 if kind_cmp:
207 if isinstance(self, SubModuleNode):
208 # we make submodules equal to dirnode for "sorting" purposes
209 return NodeKind.DIR
210 return kind_cmp
211 return cmp(self.name, other.name)
212
213 def __eq__(self, other):
214 for attr in ['name', 'path', 'kind']:
215 if getattr(self, attr) != getattr(other, attr):
216 return False
217 if self.is_file():
218 if self.content != other.content:
219 return False
220 else:
221 # For DirNode's check without entering each dir
222 self_nodes_paths = list(sorted(n.path for n in self.nodes))
223 other_nodes_paths = list(sorted(n.path for n in self.nodes))
224 if self_nodes_paths != other_nodes_paths:
225 return False
226 return True
227
228 def __ne__(self, other):
229 return not self.__eq__(other)
230
231 def __repr__(self):
232 return '<%s %r>' % (self.__class__.__name__, self.path)
233
234 def __str__(self):
235 return self.__repr__()
236
237 def __unicode__(self):
238 return self.name
239
240 def get_parent_path(self):
255 def get_parent_path(self) -> bytes:
241 256 """
242 257 Returns node's parent path or empty string if node is root.
243 258 """
244 259 if self.is_root():
245 return ''
246 return vcspath.dirname(self.path.rstrip('/')) + '/'
260 return b''
261 str_path = vcspath.dirname(self.path.rstrip('/')) + '/'
262
263 return safe_bytes(str_path)
247 264
248 265 def is_file(self):
249 266 """
@@ -312,7 +329,7 b' class FileNode(Node):'
312 329 """
313 330 _filter_pre_load = []
314 331
315 def __init__(self, path, content=None, commit=None, mode=None, pre_load=None):
332 def __init__(self, path: bytes, content: bytes | None = None, commit=None, mode=None, pre_load=None):
316 333 """
317 334 Only one of ``content`` and ``commit`` may be given. Passing both
318 335 would raise ``NodeError`` exception.
@@ -324,13 +341,39 b' class FileNode(Node):'
324 341 """
325 342 if content and commit:
326 343 raise NodeError("Cannot use both content and commit")
327 super(FileNode, self).__init__(path, kind=NodeKind.FILE)
344
345 super().__init__(path, kind=NodeKind.FILE)
346
328 347 self.commit = commit
348 if content and not isinstance(content, bytes):
349 # File content is one thing that inherently must be bytes
350 # we support passing str too, and convert the content
351 content = safe_bytes(content)
329 352 self._content = content
330 353 self._mode = mode or FILEMODE_DEFAULT
331 354
332 355 self._set_bulk_properties(pre_load)
333 356
357 def __eq__(self, other):
358 eq = super(FileNode, self).__eq__(other)
359 if eq is not None:
360 return eq
361 return self.content == other.content
362
363 def __hash__(self):
364 raw_id = getattr(self.commit, 'raw_id', '')
365 return hash((self.path, raw_id))
366
367 def __lt__(self, other):
368 lt = super(FileNode, self).__lt__(other)
369 if lt is not None:
370 return lt
371 return self.content < other.content
372
373 def __repr__(self):
374 short_id = getattr(self.commit, 'short_id', '')
375 return f'<{self.__class__.__name__} path={self.path!r}, short_id={short_id}>'
376
334 377 def _set_bulk_properties(self, pre_load):
335 378 if not pre_load:
336 379 return
@@ -339,11 +382,22 b' class FileNode(Node):'
339 382 if not pre_load:
340 383 return
341 384
342 for attr_name in pre_load:
343 result = getattr(self, attr_name)
344 if callable(result):
345 result = result()
346 self.__dict__[attr_name] = result
385 remote = self.commit.get_remote()
386 result = remote.bulk_file_request(self.commit.raw_id, self.path, pre_load)
387
388 for attr, value in result.items():
389 if attr == "flags":
390 self.__dict__['mode'] = safe_str(value)
391 elif attr == "size":
392 self.__dict__['size'] = value
393 elif attr == "data":
394 self.__dict__['_content'] = value
395 elif attr == "is_binary":
396 self.__dict__['is_binary'] = value
397 elif attr == "md5":
398 self.__dict__['md5'] = value
399 else:
400 raise ValueError(f'Unsupported attr in bulk_property: {attr}')
347 401
348 402 @LazyProperty
349 403 def mode(self):
@@ -358,7 +412,7 b' class FileNode(Node):'
358 412 return mode
359 413
360 414 @LazyProperty
361 def raw_bytes(self):
415 def raw_bytes(self) -> bytes:
362 416 """
363 417 Returns lazily the raw bytes of the FileNode.
364 418 """
@@ -370,6 +424,16 b' class FileNode(Node):'
370 424 content = self._content
371 425 return content
372 426
427 def content_uncached(self):
428 """
429 Returns lazily content of the FileNode.
430 """
431 if self.commit:
432 content = self.commit.get_file_content(self.path)
433 else:
434 content = self._content
435 return content
436
373 437 def stream_bytes(self):
374 438 """
375 439 Returns an iterator that will stream the content of the file directly from
@@ -379,13 +443,6 b' class FileNode(Node):'
379 443 return self.commit.get_file_content_streamed(self.path)
380 444 raise NodeError("Cannot retrieve stream_bytes without related commit attribute")
381 445
382 @LazyProperty
383 def md5(self):
384 """
385 Returns md5 of the file node.
386 """
387 return md5(self.raw_bytes)
388
389 446 def metadata_uncached(self):
390 447 """
391 448 Returns md5, binary flag of the file node, without any cache usage.
@@ -393,35 +450,26 b' class FileNode(Node):'
393 450
394 451 content = self.content_uncached()
395 452
396 is_binary = content and '\0' in content
453 is_binary = bool(content and BIN_BYTE_MARKER in content)
397 454 size = 0
398 455 if content:
399 456 size = len(content)
400 457
401 458 return is_binary, md5(content), size, content
402 459
403 def content_uncached(self):
404 """
405 Returns lazily content of the FileNode. If possible, would try to
406 decode content from UTF-8.
460 @LazyProperty
461 def content(self) -> bytes:
407 462 """
408 if self.commit:
409 content = self.commit.get_file_content(self.path)
410 else:
411 content = self._content
463 Returns lazily content of the FileNode.
464 """
465 content = self.raw_bytes
466 if content and not isinstance(content, bytes):
467 raise ValueError(f'Content is of type {type(content)} instead of bytes')
412 468 return content
413 469
414 470 @LazyProperty
415 def content(self):
416 """
417 Returns lazily content of the FileNode. If possible, would try to
418 decode content from UTF-8.
419 """
420 content = self.raw_bytes
421
422 if self.is_binary:
423 return content
424 return safe_unicode(content)
471 def str_content(self) -> str:
472 return safe_str(self.raw_bytes)
425 473
426 474 @LazyProperty
427 475 def size(self):
@@ -457,7 +505,7 b' class FileNode(Node):'
457 505 """
458 506
459 507 if hasattr(self, '_mimetype'):
460 if (isinstance(self._mimetype, (tuple, list,)) and
508 if (isinstance(self._mimetype, (tuple, list)) and
461 509 len(self._mimetype) == 2):
462 510 return self._mimetype
463 511 else:
@@ -511,7 +559,7 b' class FileNode(Node):'
511 559 lexer = lexers.guess_lexer_for_filename(
512 560 filename, content, stripnl=False)
513 561 except lexers.ClassNotFound:
514 lexer = None
562 pass
515 563
516 564 # try our EXTENSION_MAP
517 565 if not lexer:
@@ -520,7 +568,7 b' class FileNode(Node):'
520 568 if lexer_class:
521 569 lexer = lexers.get_lexer_by_name(lexer_class[0])
522 570 except lexers.ClassNotFound:
523 lexer = None
571 pass
524 572
525 573 if not lexer:
526 574 lexer = lexers.TextLexer(stripnl=False)
@@ -533,7 +581,10 b' class FileNode(Node):'
533 581 Returns pygment's lexer class. Would try to guess lexer taking file's
534 582 content, name and mimetype.
535 583 """
536 return self.get_lexer(self.name, self.content)
584 # TODO: this is more proper, but super heavy on investigating the type based on the content
585 #self.get_lexer(self.name, self.content)
586
587 return self.get_lexer(self.name)
537 588
538 589 @LazyProperty
539 590 def lexer_alias(self):
@@ -583,7 +634,20 b' class FileNode(Node):'
583 634 return self.commit.is_node_binary(self.path)
584 635 else:
585 636 raw_bytes = self._content
586 return raw_bytes and '\0' in raw_bytes
637 return bool(raw_bytes and BIN_BYTE_MARKER in raw_bytes)
638
639 @LazyProperty
640 def md5(self):
641 """
642 Returns md5 of the file node.
643 """
644
645 if self.commit:
646 return self.commit.node_md5_hash(self.path)
647 else:
648 raw_bytes = self._content
649 # TODO: this sucks, we're computing md5 on potentially super big stream data...
650 return md5(raw_bytes)
587 651
588 652 @LazyProperty
589 653 def extension(self):
@@ -607,20 +671,26 b' class FileNode(Node):'
607 671 if self.commit:
608 672 return self.commit.get_largefile_node(self.path)
609 673
610 def count_lines(self, content, count_empty=False):
674 def count_lines(self, content: str | bytes, count_empty=False):
675 if isinstance(content, str):
676 newline_marker = '\n'
677 elif isinstance(content, bytes):
678 newline_marker = b'\n'
679 else:
680 raise ValueError('content must be bytes or str got {type(content)} instead')
611 681
612 682 if count_empty:
613 683 all_lines = 0
614 684 empty_lines = 0
615 685 for line in content.splitlines(True):
616 if line == '\n':
686 if line == newline_marker:
617 687 empty_lines += 1
618 688 all_lines += 1
619 689
620 690 return all_lines, all_lines - empty_lines
621 691 else:
622 692 # fast method
623 empty_lines = all_lines = content.count('\n')
693 empty_lines = all_lines = content.count(newline_marker)
624 694 if all_lines == 0 and content:
625 695 # one-line without a newline
626 696 empty_lines = all_lines = 1
@@ -635,10 +705,6 b' class FileNode(Node):'
635 705 all_lines, empty_lines = self.count_lines(content, count_empty=count_empty)
636 706 return all_lines, empty_lines
637 707
638 def __repr__(self):
639 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
640 getattr(self.commit, 'short_id', ''))
641
642 708
643 709 class RemovedFileNode(FileNode):
644 710 """
@@ -648,20 +714,19 b' class RemovedFileNode(FileNode):'
648 714 """
649 715 ALLOWED_ATTRIBUTES = [
650 716 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind',
651 'added', 'changed', 'not_changed', 'removed'
717 'added', 'changed', 'not_changed', 'removed', 'bytes_path'
652 718 ]
653 719
654 720 def __init__(self, path):
655 721 """
656 722 :param path: relative path to the node
657 723 """
658 super(RemovedFileNode, self).__init__(path=path)
724 super().__init__(path=path)
659 725
660 726 def __getattribute__(self, attr):
661 727 if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
662 return super(RemovedFileNode, self).__getattribute__(attr)
663 raise RemovedFileNodeError(
664 "Cannot access attribute %s on RemovedFileNode" % attr)
728 return super().__getattribute__(attr)
729 raise RemovedFileNodeError(f"Cannot access attribute {attr} on RemovedFileNode. Not in allowed attributes")
665 730
666 731 @LazyProperty
667 732 def state(self):
@@ -675,7 +740,7 b' class DirNode(Node):'
675 740 lazily fetch data within same repository's commit.
676 741 """
677 742
678 def __init__(self, path, nodes=(), commit=None):
743 def __init__(self, path, nodes=(), commit=None, default_pre_load=None):
679 744 """
680 745 Only one of ``nodes`` and ``commit`` may be given. Passing both
681 746 would raise ``NodeError`` exception.
@@ -689,16 +754,38 b' class DirNode(Node):'
689 754 super(DirNode, self).__init__(path, NodeKind.DIR)
690 755 self.commit = commit
691 756 self._nodes = nodes
757 self.default_pre_load = default_pre_load or ['is_binary', 'size']
758
759 def __iter__(self):
760 for node in self.nodes:
761 yield node
762
763 def __eq__(self, other):
764 eq = super(DirNode, self).__eq__(other)
765 if eq is not None:
766 return eq
767 # check without entering each dir
768 self_nodes_paths = list(sorted(n.path for n in self.nodes))
769 other_nodes_paths = list(sorted(n.path for n in self.nodes))
770 return self_nodes_paths == other_nodes_paths
771
772 def __lt__(self, other):
773 lt = super(DirNode, self).__lt__(other)
774 if lt is not None:
775 return lt
776 # check without entering each dir
777 self_nodes_paths = list(sorted(n.path for n in self.nodes))
778 other_nodes_paths = list(sorted(n.path for n in self.nodes))
779 return self_nodes_paths < other_nodes_paths
692 780
693 781 @LazyProperty
694 782 def content(self):
695 raise NodeError(
696 "%s represents a dir and has no `content` attribute" % self)
783 raise NodeError(f"{self} represents a dir and has no `content` attribute")
697 784
698 785 @LazyProperty
699 786 def nodes(self):
700 787 if self.commit:
701 nodes = self.commit.get_nodes(self.path)
788 nodes = self.commit.get_nodes(self.path, pre_load=self.default_pre_load)
702 789 else:
703 790 nodes = self._nodes
704 791 self._nodes_dict = dict((node.path, node) for node in nodes)
@@ -712,10 +799,6 b' class DirNode(Node):'
712 799 def dirs(self):
713 800 return sorted((node for node in self.nodes if node.is_dir()))
714 801
715 def __iter__(self):
716 for node in self.nodes:
717 yield node
718
719 802 def get_node(self, path):
720 803 """
721 804 Returns node from within this particular ``DirNode``, so it is now
@@ -755,7 +838,7 b' class DirNode(Node):'
755 838 else:
756 839 raise KeyError
757 840 except KeyError:
758 raise NodeError("Node does not exist at %s" % path)
841 raise NodeError(f"Node does not exist at {path}")
759 842
760 843 @LazyProperty
761 844 def state(self):
@@ -780,8 +863,8 b' class DirNode(Node):'
780 863 "related commit attribute")
781 864
782 865 def __repr__(self):
783 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
784 getattr(self.commit, 'short_id', ''))
866 short_id = getattr(self.commit, 'short_id', '')
867 return f'<{self.__class__.__name__} {self.path!r} @ {short_id}>'
785 868
786 869
787 870 class RootNode(DirNode):
@@ -790,10 +873,10 b' class RootNode(DirNode):'
790 873 """
791 874
792 875 def __init__(self, nodes=(), commit=None):
793 super(RootNode, self).__init__(path='', nodes=nodes, commit=commit)
876 super(RootNode, self).__init__(path=b'', nodes=nodes, commit=commit)
794 877
795 878 def __repr__(self):
796 return '<%s>' % self.__class__.__name__
879 return f'<{self.__class__.__name__}>'
797 880
798 881
799 882 class SubModuleNode(Node):
@@ -814,8 +897,8 b' class SubModuleNode(Node):'
814 897 self.url = url or self._extract_submodule_url()
815 898
816 899 def __repr__(self):
817 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
818 getattr(self.commit, 'short_id', ''))
900 short_id = getattr(self.commit, 'short_id', '')
901 return f'<{self.__class__.__name__} {self.path!r} @ {short_id}>'
819 902
820 903 def _extract_submodule_url(self):
821 904 # TODO: find a way to parse gits submodule file and extract the
@@ -828,27 +911,31 b' class SubModuleNode(Node):'
828 911 Returns name of the node so if its path
829 912 then only last part is returned.
830 913 """
831 org = safe_unicode(self.path.rstrip('/').split('/')[-1])
832 return '%s @ %s' % (org, self.commit.short_id)
914 org = safe_str(self.path.rstrip('/').split('/')[-1])
915 return f'{org} @ {self.commit.short_id}'
833 916
834 917
835 918 class LargeFileNode(FileNode):
836 919
837 920 def __init__(self, path, url=None, commit=None, alias=None, org_path=None):
838 self.path = path
839 self.org_path = org_path
921 self._validate_path(path) # can throw exception if path is invalid
922 self.org_path = org_path # as stored in VCS as LF pointer
923
924 self.bytes_path = path.rstrip(b'/') # store for __repr__
925 self.path = safe_str(self.bytes_path) # we store paths as str
926
840 927 self.kind = NodeKind.LARGEFILE
841 928 self.alias = alias
842 self._content = ''
929 self._content = b''
843 930
844 def _validate_path(self, path):
931 def _validate_path(self, path: bytes):
845 932 """
846 we override check since the LargeFileNode path is system absolute
933 we override check since the LargeFileNode path is system absolute, but we check for bytes only
847 934 """
848 pass
935 self._assert_bytes(path)
849 936
850 937 def __repr__(self):
851 return '<%s %r>' % (self.__class__.__name__, self.path)
938 return f'<{self.__class__.__name__} {self.org_path} -> {self.path!r}>'
852 939
853 940 @LazyProperty
854 941 def size(self):
@@ -55,7 +55,7 b' def get_scm(path):'
55 55 raise VCSError(
56 56 'More than one [%s] scm found at given path %s' % (found, path))
57 57
58 if len(found_scms) is 0:
58 if len(found_scms) == 0:
59 59 raise VCSError('No scm found at given path %s' % path)
60 60
61 61 return found_scms[0]
General Comments 0
You need to be logged in to leave comments. Login now