##// END OF EJS Templates
nodes: fetching largefiles content is commit independent because the pointes is...
marcink -
r1633:00bda173 stable
parent child Browse files
Show More
@@ -1,800 +1,797 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2017 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
118 118 commit = None
119 119
120 120 def __init__(self, path, kind):
121 121 self._validate_path(path) # can throw exception if path is invalid
122 122 self.path = safe_str(path.rstrip('/')) # we store paths as str
123 123 if path == '' and kind != NodeKind.DIR:
124 124 raise NodeError("Only DirNode and its subclasses may be "
125 125 "initialized with empty path")
126 126 self.kind = kind
127 127
128 128 if self.is_root() and not self.is_dir():
129 129 raise NodeError("Root node cannot be FILE kind")
130 130
131 131 def _validate_path(self, path):
132 132 if path.startswith('/'):
133 133 raise NodeError(
134 134 "Cannot initialize Node objects with slash at "
135 135 "the beginning as only relative paths are supported. "
136 136 "Got %s" % (path,))
137 137
138 138 @LazyProperty
139 139 def parent(self):
140 140 parent_path = self.get_parent_path()
141 141 if parent_path:
142 142 if self.commit:
143 143 return self.commit.get_node(parent_path)
144 144 return DirNode(parent_path)
145 145 return None
146 146
147 147 @LazyProperty
148 148 def unicode_path(self):
149 149 return safe_unicode(self.path)
150 150
151 151 @LazyProperty
152 152 def dir_path(self):
153 153 """
154 154 Returns name of the directory from full path of this vcs node. Empty
155 155 string is returned if there's no directory in the path
156 156 """
157 157 _parts = self.path.rstrip('/').rsplit('/', 1)
158 158 if len(_parts) == 2:
159 159 return safe_unicode(_parts[0])
160 160 return u''
161 161
162 162 @LazyProperty
163 163 def name(self):
164 164 """
165 165 Returns name of the node so if its path
166 166 then only last part is returned.
167 167 """
168 168 return safe_unicode(self.path.rstrip('/').split('/')[-1])
169 169
170 170 @property
171 171 def kind(self):
172 172 return self._kind
173 173
174 174 @kind.setter
175 175 def kind(self, kind):
176 176 if hasattr(self, '_kind'):
177 177 raise NodeError("Cannot change node's kind")
178 178 else:
179 179 self._kind = kind
180 180 # Post setter check (path's trailing slash)
181 181 if self.path.endswith('/'):
182 182 raise NodeError("Node's path cannot end with slash")
183 183
184 184 def __cmp__(self, other):
185 185 """
186 186 Comparator using name of the node, needed for quick list sorting.
187 187 """
188 188 kind_cmp = cmp(self.kind, other.kind)
189 189 if kind_cmp:
190 190 return kind_cmp
191 191 return cmp(self.name, other.name)
192 192
193 193 def __eq__(self, other):
194 194 for attr in ['name', 'path', 'kind']:
195 195 if getattr(self, attr) != getattr(other, attr):
196 196 return False
197 197 if self.is_file():
198 198 if self.content != other.content:
199 199 return False
200 200 else:
201 201 # For DirNode's check without entering each dir
202 202 self_nodes_paths = list(sorted(n.path for n in self.nodes))
203 203 other_nodes_paths = list(sorted(n.path for n in self.nodes))
204 204 if self_nodes_paths != other_nodes_paths:
205 205 return False
206 206 return True
207 207
208 208 def __ne__(self, other):
209 209 return not self.__eq__(other)
210 210
211 211 def __repr__(self):
212 212 return '<%s %r>' % (self.__class__.__name__, self.path)
213 213
214 214 def __str__(self):
215 215 return self.__repr__()
216 216
217 217 def __unicode__(self):
218 218 return self.name
219 219
220 220 def get_parent_path(self):
221 221 """
222 222 Returns node's parent path or empty string if node is root.
223 223 """
224 224 if self.is_root():
225 225 return ''
226 226 return vcspath.dirname(self.path.rstrip('/')) + '/'
227 227
228 228 def is_file(self):
229 229 """
230 230 Returns ``True`` if node's kind is ``NodeKind.FILE``, ``False``
231 231 otherwise.
232 232 """
233 233 return self.kind == NodeKind.FILE
234 234
235 235 def is_dir(self):
236 236 """
237 237 Returns ``True`` if node's kind is ``NodeKind.DIR``, ``False``
238 238 otherwise.
239 239 """
240 240 return self.kind == NodeKind.DIR
241 241
242 242 def is_root(self):
243 243 """
244 244 Returns ``True`` if node is a root node and ``False`` otherwise.
245 245 """
246 246 return self.kind == NodeKind.DIR and self.path == ''
247 247
248 248 def is_submodule(self):
249 249 """
250 250 Returns ``True`` if node's kind is ``NodeKind.SUBMODULE``, ``False``
251 251 otherwise.
252 252 """
253 253 return self.kind == NodeKind.SUBMODULE
254 254
255 255 def is_largefile(self):
256 256 """
257 257 Returns ``True`` if node's kind is ``NodeKind.LARGEFILE``, ``False``
258 258 otherwise
259 259 """
260 260 return self.kind == NodeKind.LARGEFILE
261 261
262 262 def is_link(self):
263 263 if self.commit:
264 264 return self.commit.is_link(self.path)
265 265 return False
266 266
267 267 @LazyProperty
268 268 def added(self):
269 269 return self.state is NodeState.ADDED
270 270
271 271 @LazyProperty
272 272 def changed(self):
273 273 return self.state is NodeState.CHANGED
274 274
275 275 @LazyProperty
276 276 def not_changed(self):
277 277 return self.state is NodeState.NOT_CHANGED
278 278
279 279 @LazyProperty
280 280 def removed(self):
281 281 return self.state is NodeState.REMOVED
282 282
283 283
284 284 class FileNode(Node):
285 285 """
286 286 Class representing file nodes.
287 287
288 288 :attribute: path: path to the node, relative to repository's root
289 289 :attribute: content: if given arbitrary sets content of the file
290 290 :attribute: commit: if given, first time content is accessed, callback
291 291 :attribute: mode: stat mode for a node. Default is `FILEMODE_DEFAULT`.
292 292 """
293 293 _filter_pre_load = []
294 294
295 295 def __init__(self, path, content=None, commit=None, mode=None, pre_load=None):
296 296 """
297 297 Only one of ``content`` and ``commit`` may be given. Passing both
298 298 would raise ``NodeError`` exception.
299 299
300 300 :param path: relative path to the node
301 301 :param content: content may be passed to constructor
302 302 :param commit: if given, will use it to lazily fetch content
303 303 :param mode: ST_MODE (i.e. 0100644)
304 304 """
305 305 if content and commit:
306 306 raise NodeError("Cannot use both content and commit")
307 307 super(FileNode, self).__init__(path, kind=NodeKind.FILE)
308 308 self.commit = commit
309 309 self._content = content
310 310 self._mode = mode or FILEMODE_DEFAULT
311 311
312 312 self._set_bulk_properties(pre_load)
313 313
314 314 def _set_bulk_properties(self, pre_load):
315 315 if not pre_load:
316 316 return
317 317 pre_load = [entry for entry in pre_load
318 318 if entry not in self._filter_pre_load]
319 319 if not pre_load:
320 320 return
321 321
322 322 for attr_name in pre_load:
323 323 result = getattr(self, attr_name)
324 324 if callable(result):
325 325 result = result()
326 326 self.__dict__[attr_name] = result
327 327
328 328 @LazyProperty
329 329 def mode(self):
330 330 """
331 331 Returns lazily mode of the FileNode. If `commit` is not set, would
332 332 use value given at initialization or `FILEMODE_DEFAULT` (default).
333 333 """
334 334 if self.commit:
335 335 mode = self.commit.get_file_mode(self.path)
336 336 else:
337 337 mode = self._mode
338 338 return mode
339 339
340 340 @LazyProperty
341 341 def raw_bytes(self):
342 342 """
343 343 Returns lazily the raw bytes of the FileNode.
344 344 """
345 345 if self.commit:
346 346 if self._content is None:
347 347 self._content = self.commit.get_file_content(self.path)
348 348 content = self._content
349 349 else:
350 350 content = self._content
351 351 return content
352 352
353 353 @LazyProperty
354 354 def md5(self):
355 355 """
356 356 Returns md5 of the file node.
357 357 """
358 358 return md5(self.raw_bytes)
359 359
360 360 @LazyProperty
361 361 def content(self):
362 362 """
363 363 Returns lazily content of the FileNode. If possible, would try to
364 364 decode content from UTF-8.
365 365 """
366 366 content = self.raw_bytes
367 367
368 368 if self.is_binary:
369 369 return content
370 370 return safe_unicode(content)
371 371
372 372 @LazyProperty
373 373 def size(self):
374 374 if self.commit:
375 375 return self.commit.get_file_size(self.path)
376 376 raise NodeError(
377 377 "Cannot retrieve size of the file without related "
378 378 "commit attribute")
379 379
380 380 @LazyProperty
381 381 def message(self):
382 382 if self.commit:
383 383 return self.last_commit.message
384 384 raise NodeError(
385 385 "Cannot retrieve message of the file without related "
386 386 "commit attribute")
387 387
388 388 @LazyProperty
389 389 def last_commit(self):
390 390 if self.commit:
391 391 pre_load = ["author", "date", "message"]
392 392 return self.commit.get_file_commit(self.path, pre_load=pre_load)
393 393 raise NodeError(
394 394 "Cannot retrieve last commit of the file without "
395 395 "related commit attribute")
396 396
397 397 def get_mimetype(self):
398 398 """
399 399 Mimetype is calculated based on the file's content. If ``_mimetype``
400 400 attribute is available, it will be returned (backends which store
401 401 mimetypes or can easily recognize them, should set this private
402 402 attribute to indicate that type should *NOT* be calculated).
403 403 """
404 404
405 405 if hasattr(self, '_mimetype'):
406 406 if (isinstance(self._mimetype, (tuple, list,)) and
407 407 len(self._mimetype) == 2):
408 408 return self._mimetype
409 409 else:
410 410 raise NodeError('given _mimetype attribute must be an 2 '
411 411 'element list or tuple')
412 412
413 413 db = get_mimetypes_db()
414 414 mtype, encoding = db.guess_type(self.name)
415 415
416 416 if mtype is None:
417 417 if self.is_binary:
418 418 mtype = 'application/octet-stream'
419 419 encoding = None
420 420 else:
421 421 mtype = 'text/plain'
422 422 encoding = None
423 423
424 424 # try with pygments
425 425 try:
426 426 from pygments.lexers import get_lexer_for_filename
427 427 mt = get_lexer_for_filename(self.name).mimetypes
428 428 except Exception:
429 429 mt = None
430 430
431 431 if mt:
432 432 mtype = mt[0]
433 433
434 434 return mtype, encoding
435 435
436 436 @LazyProperty
437 437 def mimetype(self):
438 438 """
439 439 Wrapper around full mimetype info. It returns only type of fetched
440 440 mimetype without the encoding part. use get_mimetype function to fetch
441 441 full set of (type,encoding)
442 442 """
443 443 return self.get_mimetype()[0]
444 444
445 445 @LazyProperty
446 446 def mimetype_main(self):
447 447 return self.mimetype.split('/')[0]
448 448
449 449 @classmethod
450 450 def get_lexer(cls, filename, content=None):
451 451 from pygments import lexers
452 452
453 453 extension = filename.split('.')[-1]
454 454 lexer = None
455 455
456 456 try:
457 457 lexer = lexers.guess_lexer_for_filename(
458 458 filename, content, stripnl=False)
459 459 except lexers.ClassNotFound:
460 460 lexer = None
461 461
462 462 # try our EXTENSION_MAP
463 463 if not lexer:
464 464 try:
465 465 lexer_class = LANGUAGES_EXTENSIONS_MAP.get(extension)
466 466 if lexer_class:
467 467 lexer = lexers.get_lexer_by_name(lexer_class[0])
468 468 except lexers.ClassNotFound:
469 469 lexer = None
470 470
471 471 if not lexer:
472 472 lexer = lexers.TextLexer(stripnl=False)
473 473
474 474 return lexer
475 475
476 476 @LazyProperty
477 477 def lexer(self):
478 478 """
479 479 Returns pygment's lexer class. Would try to guess lexer taking file's
480 480 content, name and mimetype.
481 481 """
482 482 return self.get_lexer(self.name, self.content)
483 483
484 484 @LazyProperty
485 485 def lexer_alias(self):
486 486 """
487 487 Returns first alias of the lexer guessed for this file.
488 488 """
489 489 return self.lexer.aliases[0]
490 490
491 491 @LazyProperty
492 492 def history(self):
493 493 """
494 494 Returns a list of commit for this file in which the file was changed
495 495 """
496 496 if self.commit is None:
497 497 raise NodeError('Unable to get commit for this FileNode')
498 498 return self.commit.get_file_history(self.path)
499 499
500 500 @LazyProperty
501 501 def annotate(self):
502 502 """
503 503 Returns a list of three element tuples with lineno, commit and line
504 504 """
505 505 if self.commit is None:
506 506 raise NodeError('Unable to get commit for this FileNode')
507 507 pre_load = ["author", "date", "message"]
508 508 return self.commit.get_file_annotate(self.path, pre_load=pre_load)
509 509
510 510 @LazyProperty
511 511 def state(self):
512 512 if not self.commit:
513 513 raise NodeError(
514 514 "Cannot check state of the node if it's not "
515 515 "linked with commit")
516 516 elif self.path in (node.path for node in self.commit.added):
517 517 return NodeState.ADDED
518 518 elif self.path in (node.path for node in self.commit.changed):
519 519 return NodeState.CHANGED
520 520 else:
521 521 return NodeState.NOT_CHANGED
522 522
523 523 @LazyProperty
524 524 def is_binary(self):
525 525 """
526 526 Returns True if file has binary content.
527 527 """
528 528 _bin = self.raw_bytes and '\0' in self.raw_bytes
529 529 return _bin
530 530
531 531 @LazyProperty
532 532 def extension(self):
533 533 """Returns filenode extension"""
534 534 return self.name.split('.')[-1]
535 535
536 536 @property
537 537 def is_executable(self):
538 538 """
539 539 Returns ``True`` if file has executable flag turned on.
540 540 """
541 541 return bool(self.mode & stat.S_IXUSR)
542 542
543 543 def get_largefile_node(self):
544 544 """
545 545 Try to return a Mercurial FileNode from this node. It does internal
546 546 checks inside largefile store, if that file exist there it will
547 547 create special instance of LargeFileNode which can get content from
548 548 LF store.
549 549 """
550 550 if self.commit:
551 551 return self.commit.get_largefile_node(self.path)
552 552
553 553 def lines(self, count_empty=False):
554 554 all_lines, empty_lines = 0, 0
555 555
556 556 if not self.is_binary:
557 557 content = self.content
558 558 if count_empty:
559 559 all_lines = 0
560 560 empty_lines = 0
561 561 for line in content.splitlines(True):
562 562 if line == '\n':
563 563 empty_lines += 1
564 564 all_lines += 1
565 565
566 566 return all_lines, all_lines - empty_lines
567 567 else:
568 568 # fast method
569 569 empty_lines = all_lines = content.count('\n')
570 570 if all_lines == 0 and content:
571 571 # one-line without a newline
572 572 empty_lines = all_lines = 1
573 573
574 574 return all_lines, empty_lines
575 575
576 576 def __repr__(self):
577 577 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
578 578 getattr(self.commit, 'short_id', ''))
579 579
580 580
581 581 class RemovedFileNode(FileNode):
582 582 """
583 583 Dummy FileNode class - trying to access any public attribute except path,
584 584 name, kind or state (or methods/attributes checking those two) would raise
585 585 RemovedFileNodeError.
586 586 """
587 587 ALLOWED_ATTRIBUTES = [
588 588 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind',
589 589 'added', 'changed', 'not_changed', 'removed'
590 590 ]
591 591
592 592 def __init__(self, path):
593 593 """
594 594 :param path: relative path to the node
595 595 """
596 596 super(RemovedFileNode, self).__init__(path=path)
597 597
598 598 def __getattribute__(self, attr):
599 599 if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
600 600 return super(RemovedFileNode, self).__getattribute__(attr)
601 601 raise RemovedFileNodeError(
602 602 "Cannot access attribute %s on RemovedFileNode" % attr)
603 603
604 604 @LazyProperty
605 605 def state(self):
606 606 return NodeState.REMOVED
607 607
608 608
609 609 class DirNode(Node):
610 610 """
611 611 DirNode stores list of files and directories within this node.
612 612 Nodes may be used standalone but within repository context they
613 613 lazily fetch data within same repositorty's commit.
614 614 """
615 615
616 616 def __init__(self, path, nodes=(), commit=None):
617 617 """
618 618 Only one of ``nodes`` and ``commit`` may be given. Passing both
619 619 would raise ``NodeError`` exception.
620 620
621 621 :param path: relative path to the node
622 622 :param nodes: content may be passed to constructor
623 623 :param commit: if given, will use it to lazily fetch content
624 624 """
625 625 if nodes and commit:
626 626 raise NodeError("Cannot use both nodes and commit")
627 627 super(DirNode, self).__init__(path, NodeKind.DIR)
628 628 self.commit = commit
629 629 self._nodes = nodes
630 630
631 631 @LazyProperty
632 632 def content(self):
633 633 raise NodeError(
634 634 "%s represents a dir and has no `content` attribute" % self)
635 635
636 636 @LazyProperty
637 637 def nodes(self):
638 638 if self.commit:
639 639 nodes = self.commit.get_nodes(self.path)
640 640 else:
641 641 nodes = self._nodes
642 642 self._nodes_dict = dict((node.path, node) for node in nodes)
643 643 return sorted(nodes)
644 644
645 645 @LazyProperty
646 646 def files(self):
647 647 return sorted((node for node in self.nodes if node.is_file()))
648 648
649 649 @LazyProperty
650 650 def dirs(self):
651 651 return sorted((node for node in self.nodes if node.is_dir()))
652 652
653 653 def __iter__(self):
654 654 for node in self.nodes:
655 655 yield node
656 656
657 657 def get_node(self, path):
658 658 """
659 659 Returns node from within this particular ``DirNode``, so it is now
660 660 allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
661 661 'docs'. In order to access deeper nodes one must fetch nodes between
662 662 them first - this would work::
663 663
664 664 docs = root.get_node('docs')
665 665 docs.get_node('api').get_node('index.rst')
666 666
667 667 :param: path - relative to the current node
668 668
669 669 .. note::
670 670 To access lazily (as in example above) node have to be initialized
671 671 with related commit object - without it node is out of
672 672 context and may know nothing about anything else than nearest
673 673 (located at same level) nodes.
674 674 """
675 675 try:
676 676 path = path.rstrip('/')
677 677 if path == '':
678 678 raise NodeError("Cannot retrieve node without path")
679 679 self.nodes # access nodes first in order to set _nodes_dict
680 680 paths = path.split('/')
681 681 if len(paths) == 1:
682 682 if not self.is_root():
683 683 path = '/'.join((self.path, paths[0]))
684 684 else:
685 685 path = paths[0]
686 686 return self._nodes_dict[path]
687 687 elif len(paths) > 1:
688 688 if self.commit is None:
689 689 raise NodeError(
690 690 "Cannot access deeper nodes without commit")
691 691 else:
692 692 path1, path2 = paths[0], '/'.join(paths[1:])
693 693 return self.get_node(path1).get_node(path2)
694 694 else:
695 695 raise KeyError
696 696 except KeyError:
697 697 raise NodeError("Node does not exist at %s" % path)
698 698
699 699 @LazyProperty
700 700 def state(self):
701 701 raise NodeError("Cannot access state of DirNode")
702 702
703 703 @LazyProperty
704 704 def size(self):
705 705 size = 0
706 706 for root, dirs, files in self.commit.walk(self.path):
707 707 for f in files:
708 708 size += f.size
709 709
710 710 return size
711 711
712 712 def __repr__(self):
713 713 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
714 714 getattr(self.commit, 'short_id', ''))
715 715
716 716
717 717 class RootNode(DirNode):
718 718 """
719 719 DirNode being the root node of the repository.
720 720 """
721 721
722 722 def __init__(self, nodes=(), commit=None):
723 723 super(RootNode, self).__init__(path='', nodes=nodes, commit=commit)
724 724
725 725 def __repr__(self):
726 726 return '<%s>' % self.__class__.__name__
727 727
728 728
729 729 class SubModuleNode(Node):
730 730 """
731 731 represents a SubModule of Git or SubRepo of Mercurial
732 732 """
733 733 is_binary = False
734 734 size = 0
735 735
736 736 def __init__(self, name, url=None, commit=None, alias=None):
737 737 self.path = name
738 738 self.kind = NodeKind.SUBMODULE
739 739 self.alias = alias
740 740
741 741 # we have to use EmptyCommit here since this can point to svn/git/hg
742 742 # submodules we cannot get from repository
743 743 self.commit = EmptyCommit(str(commit), alias=alias)
744 744 self.url = url or self._extract_submodule_url()
745 745
746 746 def __repr__(self):
747 747 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
748 748 getattr(self.commit, 'short_id', ''))
749 749
750 750 def _extract_submodule_url(self):
751 751 # TODO: find a way to parse gits submodule file and extract the
752 752 # linking URL
753 753 return self.path
754 754
755 755 @LazyProperty
756 756 def name(self):
757 757 """
758 758 Returns name of the node so if its path
759 759 then only last part is returned.
760 760 """
761 761 org = safe_unicode(self.path.rstrip('/').split('/')[-1])
762 762 return u'%s @ %s' % (org, self.commit.short_id)
763 763
764 764
765 765 class LargeFileNode(FileNode):
766 766
767 767 def __init__(self, path, url=None, commit=None, alias=None, org_path=None):
768 768 self.path = path
769 769 self.org_path = org_path
770 770 self.kind = NodeKind.LARGEFILE
771 771 self.alias = alias
772 772
773 773 def _validate_path(self, path):
774 774 """
775 775 we override check since the LargeFileNode path is system absolute
776 776 """
777 777 pass
778 778
779 779 def __repr__(self):
780 780 return '<%s %r>' % (self.__class__.__name__, self.path)
781 781
782 782 @LazyProperty
783 783 def size(self):
784 784 return os.stat(self.path).st_size
785 785
786 786 @LazyProperty
787 787 def raw_bytes(self):
788 if self.commit:
789 788 with open(self.path, 'rb') as f:
790 789 content = f.read()
791 else:
792 content = self._content
793 790 return content
794 791
795 792 @LazyProperty
796 793 def name(self):
797 794 """
798 795 Overwrites name to be the org lf path
799 796 """
800 797 return self.org_path
General Comments 0
You need to be logged in to leave comments. Login now