##// END OF EJS Templates
nodes: make submodules sorting be equal to Directory
ergo -
r3470:5fd6f678 default
parent child Browse files
Show More
@@ -1,837 +1,841 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Module holding everything related to vcs nodes, with vcs2 architecture.
23 23 """
24 24
25 25 import os
26 26 import stat
27 27
28 28 from zope.cachedescriptors.property import Lazy as LazyProperty
29 29
30 30 from rhodecode.config.conf import LANGUAGES_EXTENSIONS_MAP
31 31 from rhodecode.lib.utils import safe_unicode, safe_str
32 32 from rhodecode.lib.utils2 import md5
33 33 from rhodecode.lib.vcs import path as vcspath
34 34 from rhodecode.lib.vcs.backends.base import EmptyCommit, FILEMODE_DEFAULT
35 35 from rhodecode.lib.vcs.conf.mtypes import get_mimetypes_db
36 36 from rhodecode.lib.vcs.exceptions import NodeError, RemovedFileNodeError
37 37
38 38 LARGEFILE_PREFIX = '.hglf'
39 39
40 40
41 41 class NodeKind:
42 42 SUBMODULE = -1
43 43 DIR = 1
44 44 FILE = 2
45 45 LARGEFILE = 3
46 46
47 47
48 48 class NodeState:
49 49 ADDED = u'added'
50 50 CHANGED = u'changed'
51 51 NOT_CHANGED = u'not changed'
52 52 REMOVED = u'removed'
53 53
54 54
55 55 class NodeGeneratorBase(object):
56 56 """
57 57 Base class for removed added and changed filenodes, it's a lazy generator
58 58 class that will create filenodes only on iteration or call
59 59
60 60 The len method doesn't need to create filenodes at all
61 61 """
62 62
63 63 def __init__(self, current_paths, cs):
64 64 self.cs = cs
65 65 self.current_paths = current_paths
66 66
67 67 def __call__(self):
68 68 return [n for n in self]
69 69
70 70 def __getslice__(self, i, j):
71 71 for p in self.current_paths[i:j]:
72 72 yield self.cs.get_node(p)
73 73
74 74 def __len__(self):
75 75 return len(self.current_paths)
76 76
77 77 def __iter__(self):
78 78 for p in self.current_paths:
79 79 yield self.cs.get_node(p)
80 80
81 81
82 82 class AddedFileNodesGenerator(NodeGeneratorBase):
83 83 """
84 84 Class holding added files for current commit
85 85 """
86 86
87 87
88 88 class ChangedFileNodesGenerator(NodeGeneratorBase):
89 89 """
90 90 Class holding changed files for current commit
91 91 """
92 92
93 93
94 94 class RemovedFileNodesGenerator(NodeGeneratorBase):
95 95 """
96 96 Class holding removed files for current commit
97 97 """
98 98 def __iter__(self):
99 99 for p in self.current_paths:
100 100 yield RemovedFileNode(path=p)
101 101
102 102 def __getslice__(self, i, j):
103 103 for p in self.current_paths[i:j]:
104 104 yield RemovedFileNode(path=p)
105 105
106 106
107 107 class Node(object):
108 108 """
109 109 Simplest class representing file or directory on repository. SCM backends
110 110 should use ``FileNode`` and ``DirNode`` subclasses rather than ``Node``
111 111 directly.
112 112
113 113 Node's ``path`` cannot start with slash as we operate on *relative* paths
114 114 only. Moreover, every single node is identified by the ``path`` attribute,
115 115 so it cannot end with slash, too. Otherwise, path could lead to mistakes.
116 116 """
117 117 RTLO_MARKER = u"\u202E" # RTLO marker allows swapping text, and certain
118 118 # security attacks could be used with this
119 119 commit = None
120 120
121 121 def __init__(self, path, kind):
122 122 self._validate_path(path) # can throw exception if path is invalid
123 123 self.path = safe_str(path.rstrip('/')) # we store paths as str
124 124 if path == '' and kind != NodeKind.DIR:
125 125 raise NodeError("Only DirNode and its subclasses may be "
126 126 "initialized with empty path")
127 127 self.kind = kind
128 128
129 129 if self.is_root() and not self.is_dir():
130 130 raise NodeError("Root node cannot be FILE kind")
131 131
132 132 def _validate_path(self, path):
133 133 if path.startswith('/'):
134 134 raise NodeError(
135 135 "Cannot initialize Node objects with slash at "
136 136 "the beginning as only relative paths are supported. "
137 137 "Got %s" % (path,))
138 138
139 139 @LazyProperty
140 140 def parent(self):
141 141 parent_path = self.get_parent_path()
142 142 if parent_path:
143 143 if self.commit:
144 144 return self.commit.get_node(parent_path)
145 145 return DirNode(parent_path)
146 146 return None
147 147
148 148 @LazyProperty
149 149 def unicode_path(self):
150 150 return safe_unicode(self.path)
151 151
152 152 @LazyProperty
153 153 def has_rtlo(self):
154 154 """Detects if a path has right-to-left-override marker"""
155 155 return self.RTLO_MARKER in self.unicode_path
156 156
157 157 @LazyProperty
158 158 def unicode_path_safe(self):
159 159 """
160 160 Special SAFE representation of path without the right-to-left-override.
161 161 This should be only used for "showing" the file, cannot be used for any
162 162 urls etc.
163 163 """
164 164 return safe_unicode(self.path).replace(self.RTLO_MARKER, '')
165 165
166 166 @LazyProperty
167 167 def dir_path(self):
168 168 """
169 169 Returns name of the directory from full path of this vcs node. Empty
170 170 string is returned if there's no directory in the path
171 171 """
172 172 _parts = self.path.rstrip('/').rsplit('/', 1)
173 173 if len(_parts) == 2:
174 174 return safe_unicode(_parts[0])
175 175 return u''
176 176
177 177 @LazyProperty
178 178 def name(self):
179 179 """
180 180 Returns name of the node so if its path
181 181 then only last part is returned.
182 182 """
183 183 return safe_unicode(self.path.rstrip('/').split('/')[-1])
184 184
185 185 @property
186 186 def kind(self):
187 187 return self._kind
188 188
189 189 @kind.setter
190 190 def kind(self, kind):
191 191 if hasattr(self, '_kind'):
192 192 raise NodeError("Cannot change node's kind")
193 193 else:
194 194 self._kind = kind
195 195 # Post setter check (path's trailing slash)
196 196 if self.path.endswith('/'):
197 197 raise NodeError("Node's path cannot end with slash")
198 198
199 199 def __cmp__(self, other):
200 200 """
201 201 Comparator using name of the node, needed for quick list sorting.
202 202 """
203
203 204 kind_cmp = cmp(self.kind, other.kind)
204 205 if kind_cmp:
206 if isinstance(self, SubModuleNode):
207 # we make submodules equal to dirnode for "sorting" purposes
208 return NodeKind.DIR
205 209 return kind_cmp
206 210 return cmp(self.name, other.name)
207 211
208 212 def __eq__(self, other):
209 213 for attr in ['name', 'path', 'kind']:
210 214 if getattr(self, attr) != getattr(other, attr):
211 215 return False
212 216 if self.is_file():
213 217 if self.content != other.content:
214 218 return False
215 219 else:
216 220 # For DirNode's check without entering each dir
217 221 self_nodes_paths = list(sorted(n.path for n in self.nodes))
218 222 other_nodes_paths = list(sorted(n.path for n in self.nodes))
219 223 if self_nodes_paths != other_nodes_paths:
220 224 return False
221 225 return True
222 226
223 227 def __ne__(self, other):
224 228 return not self.__eq__(other)
225 229
226 230 def __repr__(self):
227 231 return '<%s %r>' % (self.__class__.__name__, self.path)
228 232
229 233 def __str__(self):
230 234 return self.__repr__()
231 235
232 236 def __unicode__(self):
233 237 return self.name
234 238
235 239 def get_parent_path(self):
236 240 """
237 241 Returns node's parent path or empty string if node is root.
238 242 """
239 243 if self.is_root():
240 244 return ''
241 245 return vcspath.dirname(self.path.rstrip('/')) + '/'
242 246
243 247 def is_file(self):
244 248 """
245 249 Returns ``True`` if node's kind is ``NodeKind.FILE``, ``False``
246 250 otherwise.
247 251 """
248 252 return self.kind == NodeKind.FILE
249 253
250 254 def is_dir(self):
251 255 """
252 256 Returns ``True`` if node's kind is ``NodeKind.DIR``, ``False``
253 257 otherwise.
254 258 """
255 259 return self.kind == NodeKind.DIR
256 260
257 261 def is_root(self):
258 262 """
259 263 Returns ``True`` if node is a root node and ``False`` otherwise.
260 264 """
261 265 return self.kind == NodeKind.DIR and self.path == ''
262 266
263 267 def is_submodule(self):
264 268 """
265 269 Returns ``True`` if node's kind is ``NodeKind.SUBMODULE``, ``False``
266 270 otherwise.
267 271 """
268 272 return self.kind == NodeKind.SUBMODULE
269 273
270 274 def is_largefile(self):
271 275 """
272 276 Returns ``True`` if node's kind is ``NodeKind.LARGEFILE``, ``False``
273 277 otherwise
274 278 """
275 279 return self.kind == NodeKind.LARGEFILE
276 280
277 281 def is_link(self):
278 282 if self.commit:
279 283 return self.commit.is_link(self.path)
280 284 return False
281 285
282 286 @LazyProperty
283 287 def added(self):
284 288 return self.state is NodeState.ADDED
285 289
286 290 @LazyProperty
287 291 def changed(self):
288 292 return self.state is NodeState.CHANGED
289 293
290 294 @LazyProperty
291 295 def not_changed(self):
292 296 return self.state is NodeState.NOT_CHANGED
293 297
294 298 @LazyProperty
295 299 def removed(self):
296 300 return self.state is NodeState.REMOVED
297 301
298 302
299 303 class FileNode(Node):
300 304 """
301 305 Class representing file nodes.
302 306
303 307 :attribute: path: path to the node, relative to repository's root
304 308 :attribute: content: if given arbitrary sets content of the file
305 309 :attribute: commit: if given, first time content is accessed, callback
306 310 :attribute: mode: stat mode for a node. Default is `FILEMODE_DEFAULT`.
307 311 """
308 312 _filter_pre_load = []
309 313
310 314 def __init__(self, path, content=None, commit=None, mode=None, pre_load=None):
311 315 """
312 316 Only one of ``content`` and ``commit`` may be given. Passing both
313 317 would raise ``NodeError`` exception.
314 318
315 319 :param path: relative path to the node
316 320 :param content: content may be passed to constructor
317 321 :param commit: if given, will use it to lazily fetch content
318 322 :param mode: ST_MODE (i.e. 0100644)
319 323 """
320 324 if content and commit:
321 325 raise NodeError("Cannot use both content and commit")
322 326 super(FileNode, self).__init__(path, kind=NodeKind.FILE)
323 327 self.commit = commit
324 328 self._content = content
325 329 self._mode = mode or FILEMODE_DEFAULT
326 330
327 331 self._set_bulk_properties(pre_load)
328 332
329 333 def _set_bulk_properties(self, pre_load):
330 334 if not pre_load:
331 335 return
332 336 pre_load = [entry for entry in pre_load
333 337 if entry not in self._filter_pre_load]
334 338 if not pre_load:
335 339 return
336 340
337 341 for attr_name in pre_load:
338 342 result = getattr(self, attr_name)
339 343 if callable(result):
340 344 result = result()
341 345 self.__dict__[attr_name] = result
342 346
343 347 @LazyProperty
344 348 def mode(self):
345 349 """
346 350 Returns lazily mode of the FileNode. If `commit` is not set, would
347 351 use value given at initialization or `FILEMODE_DEFAULT` (default).
348 352 """
349 353 if self.commit:
350 354 mode = self.commit.get_file_mode(self.path)
351 355 else:
352 356 mode = self._mode
353 357 return mode
354 358
355 359 @LazyProperty
356 360 def raw_bytes(self):
357 361 """
358 362 Returns lazily the raw bytes of the FileNode.
359 363 """
360 364 if self.commit:
361 365 if self._content is None:
362 366 self._content = self.commit.get_file_content(self.path)
363 367 content = self._content
364 368 else:
365 369 content = self._content
366 370 return content
367 371
368 372 @LazyProperty
369 373 def md5(self):
370 374 """
371 375 Returns md5 of the file node.
372 376 """
373 377 return md5(self.raw_bytes)
374 378
375 379 def metadata_uncached(self):
376 380 """
377 381 Returns md5, binary flag of the file node, without any cache usage.
378 382 """
379 383
380 384 if self.commit:
381 385 content = self.commit.get_file_content(self.path)
382 386 else:
383 387 content = self._content
384 388
385 389 is_binary = content and '\0' in content
386 390 size = 0
387 391 if content:
388 392 size = len(content)
389 393 return is_binary, md5(content), size
390 394
391 395 @LazyProperty
392 396 def content(self):
393 397 """
394 398 Returns lazily content of the FileNode. If possible, would try to
395 399 decode content from UTF-8.
396 400 """
397 401 content = self.raw_bytes
398 402
399 403 if self.is_binary:
400 404 return content
401 405 return safe_unicode(content)
402 406
403 407 @LazyProperty
404 408 def size(self):
405 409 if self.commit:
406 410 return self.commit.get_file_size(self.path)
407 411 raise NodeError(
408 412 "Cannot retrieve size of the file without related "
409 413 "commit attribute")
410 414
411 415 @LazyProperty
412 416 def message(self):
413 417 if self.commit:
414 418 return self.last_commit.message
415 419 raise NodeError(
416 420 "Cannot retrieve message of the file without related "
417 421 "commit attribute")
418 422
419 423 @LazyProperty
420 424 def last_commit(self):
421 425 if self.commit:
422 426 pre_load = ["author", "date", "message"]
423 427 return self.commit.get_path_commit(self.path, pre_load=pre_load)
424 428 raise NodeError(
425 429 "Cannot retrieve last commit of the file without "
426 430 "related commit attribute")
427 431
428 432 def get_mimetype(self):
429 433 """
430 434 Mimetype is calculated based on the file's content. If ``_mimetype``
431 435 attribute is available, it will be returned (backends which store
432 436 mimetypes or can easily recognize them, should set this private
433 437 attribute to indicate that type should *NOT* be calculated).
434 438 """
435 439
436 440 if hasattr(self, '_mimetype'):
437 441 if (isinstance(self._mimetype, (tuple, list,)) and
438 442 len(self._mimetype) == 2):
439 443 return self._mimetype
440 444 else:
441 445 raise NodeError('given _mimetype attribute must be an 2 '
442 446 'element list or tuple')
443 447
444 448 db = get_mimetypes_db()
445 449 mtype, encoding = db.guess_type(self.name)
446 450
447 451 if mtype is None:
448 452 if self.is_binary:
449 453 mtype = 'application/octet-stream'
450 454 encoding = None
451 455 else:
452 456 mtype = 'text/plain'
453 457 encoding = None
454 458
455 459 # try with pygments
456 460 try:
457 461 from pygments.lexers import get_lexer_for_filename
458 462 mt = get_lexer_for_filename(self.name).mimetypes
459 463 except Exception:
460 464 mt = None
461 465
462 466 if mt:
463 467 mtype = mt[0]
464 468
465 469 return mtype, encoding
466 470
467 471 @LazyProperty
468 472 def mimetype(self):
469 473 """
470 474 Wrapper around full mimetype info. It returns only type of fetched
471 475 mimetype without the encoding part. use get_mimetype function to fetch
472 476 full set of (type,encoding)
473 477 """
474 478 return self.get_mimetype()[0]
475 479
476 480 @LazyProperty
477 481 def mimetype_main(self):
478 482 return self.mimetype.split('/')[0]
479 483
480 484 @classmethod
481 485 def get_lexer(cls, filename, content=None):
482 486 from pygments import lexers
483 487
484 488 extension = filename.split('.')[-1]
485 489 lexer = None
486 490
487 491 try:
488 492 lexer = lexers.guess_lexer_for_filename(
489 493 filename, content, stripnl=False)
490 494 except lexers.ClassNotFound:
491 495 lexer = None
492 496
493 497 # try our EXTENSION_MAP
494 498 if not lexer:
495 499 try:
496 500 lexer_class = LANGUAGES_EXTENSIONS_MAP.get(extension)
497 501 if lexer_class:
498 502 lexer = lexers.get_lexer_by_name(lexer_class[0])
499 503 except lexers.ClassNotFound:
500 504 lexer = None
501 505
502 506 if not lexer:
503 507 lexer = lexers.TextLexer(stripnl=False)
504 508
505 509 return lexer
506 510
507 511 @LazyProperty
508 512 def lexer(self):
509 513 """
510 514 Returns pygment's lexer class. Would try to guess lexer taking file's
511 515 content, name and mimetype.
512 516 """
513 517 return self.get_lexer(self.name, self.content)
514 518
515 519 @LazyProperty
516 520 def lexer_alias(self):
517 521 """
518 522 Returns first alias of the lexer guessed for this file.
519 523 """
520 524 return self.lexer.aliases[0]
521 525
522 526 @LazyProperty
523 527 def history(self):
524 528 """
525 529 Returns a list of commit for this file in which the file was changed
526 530 """
527 531 if self.commit is None:
528 532 raise NodeError('Unable to get commit for this FileNode')
529 533 return self.commit.get_path_history(self.path)
530 534
531 535 @LazyProperty
532 536 def annotate(self):
533 537 """
534 538 Returns a list of three element tuples with lineno, commit and line
535 539 """
536 540 if self.commit is None:
537 541 raise NodeError('Unable to get commit for this FileNode')
538 542 pre_load = ["author", "date", "message"]
539 543 return self.commit.get_file_annotate(self.path, pre_load=pre_load)
540 544
541 545 @LazyProperty
542 546 def state(self):
543 547 if not self.commit:
544 548 raise NodeError(
545 549 "Cannot check state of the node if it's not "
546 550 "linked with commit")
547 551 elif self.path in (node.path for node in self.commit.added):
548 552 return NodeState.ADDED
549 553 elif self.path in (node.path for node in self.commit.changed):
550 554 return NodeState.CHANGED
551 555 else:
552 556 return NodeState.NOT_CHANGED
553 557
554 558 @LazyProperty
555 559 def is_binary(self):
556 560 """
557 561 Returns True if file has binary content.
558 562 """
559 563 _bin = self.raw_bytes and '\0' in self.raw_bytes
560 564 return _bin
561 565
562 566 @LazyProperty
563 567 def extension(self):
564 568 """Returns filenode extension"""
565 569 return self.name.split('.')[-1]
566 570
567 571 @property
568 572 def is_executable(self):
569 573 """
570 574 Returns ``True`` if file has executable flag turned on.
571 575 """
572 576 return bool(self.mode & stat.S_IXUSR)
573 577
574 578 def get_largefile_node(self):
575 579 """
576 580 Try to return a Mercurial FileNode from this node. It does internal
577 581 checks inside largefile store, if that file exist there it will
578 582 create special instance of LargeFileNode which can get content from
579 583 LF store.
580 584 """
581 585 if self.commit:
582 586 return self.commit.get_largefile_node(self.path)
583 587
584 588 def lines(self, count_empty=False):
585 589 all_lines, empty_lines = 0, 0
586 590
587 591 if not self.is_binary:
588 592 content = self.content
589 593 if count_empty:
590 594 all_lines = 0
591 595 empty_lines = 0
592 596 for line in content.splitlines(True):
593 597 if line == '\n':
594 598 empty_lines += 1
595 599 all_lines += 1
596 600
597 601 return all_lines, all_lines - empty_lines
598 602 else:
599 603 # fast method
600 604 empty_lines = all_lines = content.count('\n')
601 605 if all_lines == 0 and content:
602 606 # one-line without a newline
603 607 empty_lines = all_lines = 1
604 608
605 609 return all_lines, empty_lines
606 610
607 611 def __repr__(self):
608 612 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
609 613 getattr(self.commit, 'short_id', ''))
610 614
611 615
612 616 class RemovedFileNode(FileNode):
613 617 """
614 618 Dummy FileNode class - trying to access any public attribute except path,
615 619 name, kind or state (or methods/attributes checking those two) would raise
616 620 RemovedFileNodeError.
617 621 """
618 622 ALLOWED_ATTRIBUTES = [
619 623 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind',
620 624 'added', 'changed', 'not_changed', 'removed'
621 625 ]
622 626
623 627 def __init__(self, path):
624 628 """
625 629 :param path: relative path to the node
626 630 """
627 631 super(RemovedFileNode, self).__init__(path=path)
628 632
629 633 def __getattribute__(self, attr):
630 634 if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
631 635 return super(RemovedFileNode, self).__getattribute__(attr)
632 636 raise RemovedFileNodeError(
633 637 "Cannot access attribute %s on RemovedFileNode" % attr)
634 638
635 639 @LazyProperty
636 640 def state(self):
637 641 return NodeState.REMOVED
638 642
639 643
640 644 class DirNode(Node):
641 645 """
642 646 DirNode stores list of files and directories within this node.
643 647 Nodes may be used standalone but within repository context they
644 648 lazily fetch data within same repositorty's commit.
645 649 """
646 650
647 651 def __init__(self, path, nodes=(), commit=None):
648 652 """
649 653 Only one of ``nodes`` and ``commit`` may be given. Passing both
650 654 would raise ``NodeError`` exception.
651 655
652 656 :param path: relative path to the node
653 657 :param nodes: content may be passed to constructor
654 658 :param commit: if given, will use it to lazily fetch content
655 659 """
656 660 if nodes and commit:
657 661 raise NodeError("Cannot use both nodes and commit")
658 662 super(DirNode, self).__init__(path, NodeKind.DIR)
659 663 self.commit = commit
660 664 self._nodes = nodes
661 665
662 666 @LazyProperty
663 667 def content(self):
664 668 raise NodeError(
665 669 "%s represents a dir and has no `content` attribute" % self)
666 670
667 671 @LazyProperty
668 672 def nodes(self):
669 673 if self.commit:
670 674 nodes = self.commit.get_nodes(self.path)
671 675 else:
672 676 nodes = self._nodes
673 677 self._nodes_dict = dict((node.path, node) for node in nodes)
674 678 return sorted(nodes)
675 679
676 680 @LazyProperty
677 681 def files(self):
678 682 return sorted((node for node in self.nodes if node.is_file()))
679 683
680 684 @LazyProperty
681 685 def dirs(self):
682 686 return sorted((node for node in self.nodes if node.is_dir()))
683 687
684 688 def __iter__(self):
685 689 for node in self.nodes:
686 690 yield node
687 691
688 692 def get_node(self, path):
689 693 """
690 694 Returns node from within this particular ``DirNode``, so it is now
691 695 allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
692 696 'docs'. In order to access deeper nodes one must fetch nodes between
693 697 them first - this would work::
694 698
695 699 docs = root.get_node('docs')
696 700 docs.get_node('api').get_node('index.rst')
697 701
698 702 :param: path - relative to the current node
699 703
700 704 .. note::
701 705 To access lazily (as in example above) node have to be initialized
702 706 with related commit object - without it node is out of
703 707 context and may know nothing about anything else than nearest
704 708 (located at same level) nodes.
705 709 """
706 710 try:
707 711 path = path.rstrip('/')
708 712 if path == '':
709 713 raise NodeError("Cannot retrieve node without path")
710 714 self.nodes # access nodes first in order to set _nodes_dict
711 715 paths = path.split('/')
712 716 if len(paths) == 1:
713 717 if not self.is_root():
714 718 path = '/'.join((self.path, paths[0]))
715 719 else:
716 720 path = paths[0]
717 721 return self._nodes_dict[path]
718 722 elif len(paths) > 1:
719 723 if self.commit is None:
720 724 raise NodeError(
721 725 "Cannot access deeper nodes without commit")
722 726 else:
723 727 path1, path2 = paths[0], '/'.join(paths[1:])
724 728 return self.get_node(path1).get_node(path2)
725 729 else:
726 730 raise KeyError
727 731 except KeyError:
728 732 raise NodeError("Node does not exist at %s" % path)
729 733
730 734 @LazyProperty
731 735 def state(self):
732 736 raise NodeError("Cannot access state of DirNode")
733 737
734 738 @LazyProperty
735 739 def size(self):
736 740 size = 0
737 741 for root, dirs, files in self.commit.walk(self.path):
738 742 for f in files:
739 743 size += f.size
740 744
741 745 return size
742 746
743 747 @LazyProperty
744 748 def last_commit(self):
745 749 if self.commit:
746 750 pre_load = ["author", "date", "message"]
747 751 return self.commit.get_path_commit(self.path, pre_load=pre_load)
748 752 raise NodeError(
749 753 "Cannot retrieve last commit of the file without "
750 754 "related commit attribute")
751 755
752 756 def __repr__(self):
753 757 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
754 758 getattr(self.commit, 'short_id', ''))
755 759
756 760
757 761 class RootNode(DirNode):
758 762 """
759 763 DirNode being the root node of the repository.
760 764 """
761 765
762 766 def __init__(self, nodes=(), commit=None):
763 767 super(RootNode, self).__init__(path='', nodes=nodes, commit=commit)
764 768
765 769 def __repr__(self):
766 770 return '<%s>' % self.__class__.__name__
767 771
768 772
769 773 class SubModuleNode(Node):
770 774 """
771 775 represents a SubModule of Git or SubRepo of Mercurial
772 776 """
773 777 is_binary = False
774 778 size = 0
775 779
776 780 def __init__(self, name, url=None, commit=None, alias=None):
777 781 self.path = name
778 782 self.kind = NodeKind.SUBMODULE
779 783 self.alias = alias
780 784
781 785 # we have to use EmptyCommit here since this can point to svn/git/hg
782 786 # submodules we cannot get from repository
783 787 self.commit = EmptyCommit(str(commit), alias=alias)
784 788 self.url = url or self._extract_submodule_url()
785 789
786 790 def __repr__(self):
787 791 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
788 792 getattr(self.commit, 'short_id', ''))
789 793
790 794 def _extract_submodule_url(self):
791 795 # TODO: find a way to parse gits submodule file and extract the
792 796 # linking URL
793 797 return self.path
794 798
795 799 @LazyProperty
796 800 def name(self):
797 801 """
798 802 Returns name of the node so if its path
799 803 then only last part is returned.
800 804 """
801 805 org = safe_unicode(self.path.rstrip('/').split('/')[-1])
802 806 return u'%s @ %s' % (org, self.commit.short_id)
803 807
804 808
805 809 class LargeFileNode(FileNode):
806 810
807 811 def __init__(self, path, url=None, commit=None, alias=None, org_path=None):
808 812 self.path = path
809 813 self.org_path = org_path
810 814 self.kind = NodeKind.LARGEFILE
811 815 self.alias = alias
812 816
813 817 def _validate_path(self, path):
814 818 """
815 819 we override check since the LargeFileNode path is system absolute
816 820 """
817 821 pass
818 822
819 823 def __repr__(self):
820 824 return '<%s %r>' % (self.__class__.__name__, self.path)
821 825
822 826 @LazyProperty
823 827 def size(self):
824 828 return os.stat(self.path).st_size
825 829
826 830 @LazyProperty
827 831 def raw_bytes(self):
828 832 with open(self.path, 'rb') as f:
829 833 content = f.read()
830 834 return content
831 835
832 836 @LazyProperty
833 837 def name(self):
834 838 """
835 839 Overwrites name to be the org lf path
836 840 """
837 841 return self.org_path
General Comments 0
You need to be logged in to leave comments. Login now