##// END OF EJS Templates
vcs: refactor get_lexer for nodes so it can be used in external code.
marcink -
r1357:3b528eef default
parent child Browse files
Show More
@@ -1,773 +1,779 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
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 @LazyProperty
450 def lexer(self):
451 """
452 Returns pygment's lexer class. Would try to guess lexer taking file's
453 content, name and mimetype.
454 """
449 @classmethod
450 def get_lexer(cls, filename, content=None):
455 451 from pygments import lexers
456 452
453 extension = filename.split('.')[-1]
457 454 lexer = None
455
458 456 try:
459 457 lexer = lexers.guess_lexer_for_filename(
460 self.name, self.content, stripnl=False)
458 filename, content, stripnl=False)
461 459 except lexers.ClassNotFound:
462 460 lexer = None
463 461
464 462 # try our EXTENSION_MAP
465 463 if not lexer:
466 464 try:
467 lexer_class = LANGUAGES_EXTENSIONS_MAP.get(self.extension)
465 lexer_class = LANGUAGES_EXTENSIONS_MAP.get(extension)
468 466 if lexer_class:
469 467 lexer = lexers.get_lexer_by_name(lexer_class[0])
470 468 except lexers.ClassNotFound:
471 469 lexer = None
472 470
473 471 if not lexer:
474 472 lexer = lexers.TextLexer(stripnl=False)
475 473
476 474 return lexer
477 475
478 476 @LazyProperty
477 def lexer(self):
478 """
479 Returns pygment's lexer class. Would try to guess lexer taking file's
480 content, name and mimetype.
481 """
482 return self.get_lexer(self.name, self.content)
483
484 @LazyProperty
479 485 def lexer_alias(self):
480 486 """
481 487 Returns first alias of the lexer guessed for this file.
482 488 """
483 489 return self.lexer.aliases[0]
484 490
485 491 @LazyProperty
486 492 def history(self):
487 493 """
488 494 Returns a list of commit for this file in which the file was changed
489 495 """
490 496 if self.commit is None:
491 497 raise NodeError('Unable to get commit for this FileNode')
492 498 return self.commit.get_file_history(self.path)
493 499
494 500 @LazyProperty
495 501 def annotate(self):
496 502 """
497 503 Returns a list of three element tuples with lineno, commit and line
498 504 """
499 505 if self.commit is None:
500 506 raise NodeError('Unable to get commit for this FileNode')
501 507 pre_load = ["author", "date", "message"]
502 508 return self.commit.get_file_annotate(self.path, pre_load=pre_load)
503 509
504 510 @LazyProperty
505 511 def state(self):
506 512 if not self.commit:
507 513 raise NodeError(
508 514 "Cannot check state of the node if it's not "
509 515 "linked with commit")
510 516 elif self.path in (node.path for node in self.commit.added):
511 517 return NodeState.ADDED
512 518 elif self.path in (node.path for node in self.commit.changed):
513 519 return NodeState.CHANGED
514 520 else:
515 521 return NodeState.NOT_CHANGED
516 522
517 523 @LazyProperty
518 524 def is_binary(self):
519 525 """
520 526 Returns True if file has binary content.
521 527 """
522 528 _bin = self.raw_bytes and '\0' in self.raw_bytes
523 529 return _bin
524 530
525 531 @LazyProperty
526 532 def extension(self):
527 533 """Returns filenode extension"""
528 534 return self.name.split('.')[-1]
529 535
530 536 @property
531 537 def is_executable(self):
532 538 """
533 539 Returns ``True`` if file has executable flag turned on.
534 540 """
535 541 return bool(self.mode & stat.S_IXUSR)
536 542
537 543 def get_largefile_node(self):
538 544 """
539 545 Try to return a Mercurial FileNode from this node. It does internal
540 546 checks inside largefile store, if that file exist there it will
541 547 create special instance of LargeFileNode which can get content from
542 548 LF store.
543 549 """
544 550 if self.commit and self.path.startswith(LARGEFILE_PREFIX):
545 551 largefile_path = self.path.split(LARGEFILE_PREFIX)[-1].lstrip('/')
546 552 return self.commit.get_largefile_node(largefile_path)
547 553
548 554 def lines(self, count_empty=False):
549 555 all_lines, empty_lines = 0, 0
550 556
551 557 if not self.is_binary:
552 558 content = self.content
553 559 if count_empty:
554 560 all_lines = 0
555 561 empty_lines = 0
556 562 for line in content.splitlines(True):
557 563 if line == '\n':
558 564 empty_lines += 1
559 565 all_lines += 1
560 566
561 567 return all_lines, all_lines - empty_lines
562 568 else:
563 569 # fast method
564 570 empty_lines = all_lines = content.count('\n')
565 571 if all_lines == 0 and content:
566 572 # one-line without a newline
567 573 empty_lines = all_lines = 1
568 574
569 575 return all_lines, empty_lines
570 576
571 577 def __repr__(self):
572 578 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
573 579 getattr(self.commit, 'short_id', ''))
574 580
575 581
576 582 class RemovedFileNode(FileNode):
577 583 """
578 584 Dummy FileNode class - trying to access any public attribute except path,
579 585 name, kind or state (or methods/attributes checking those two) would raise
580 586 RemovedFileNodeError.
581 587 """
582 588 ALLOWED_ATTRIBUTES = [
583 589 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind',
584 590 'added', 'changed', 'not_changed', 'removed'
585 591 ]
586 592
587 593 def __init__(self, path):
588 594 """
589 595 :param path: relative path to the node
590 596 """
591 597 super(RemovedFileNode, self).__init__(path=path)
592 598
593 599 def __getattribute__(self, attr):
594 600 if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
595 601 return super(RemovedFileNode, self).__getattribute__(attr)
596 602 raise RemovedFileNodeError(
597 603 "Cannot access attribute %s on RemovedFileNode" % attr)
598 604
599 605 @LazyProperty
600 606 def state(self):
601 607 return NodeState.REMOVED
602 608
603 609
604 610 class DirNode(Node):
605 611 """
606 612 DirNode stores list of files and directories within this node.
607 613 Nodes may be used standalone but within repository context they
608 614 lazily fetch data within same repositorty's commit.
609 615 """
610 616
611 617 def __init__(self, path, nodes=(), commit=None):
612 618 """
613 619 Only one of ``nodes`` and ``commit`` may be given. Passing both
614 620 would raise ``NodeError`` exception.
615 621
616 622 :param path: relative path to the node
617 623 :param nodes: content may be passed to constructor
618 624 :param commit: if given, will use it to lazily fetch content
619 625 """
620 626 if nodes and commit:
621 627 raise NodeError("Cannot use both nodes and commit")
622 628 super(DirNode, self).__init__(path, NodeKind.DIR)
623 629 self.commit = commit
624 630 self._nodes = nodes
625 631
626 632 @LazyProperty
627 633 def content(self):
628 634 raise NodeError(
629 635 "%s represents a dir and has no `content` attribute" % self)
630 636
631 637 @LazyProperty
632 638 def nodes(self):
633 639 if self.commit:
634 640 nodes = self.commit.get_nodes(self.path)
635 641 else:
636 642 nodes = self._nodes
637 643 self._nodes_dict = dict((node.path, node) for node in nodes)
638 644 return sorted(nodes)
639 645
640 646 @LazyProperty
641 647 def files(self):
642 648 return sorted((node for node in self.nodes if node.is_file()))
643 649
644 650 @LazyProperty
645 651 def dirs(self):
646 652 return sorted((node for node in self.nodes if node.is_dir()))
647 653
648 654 def __iter__(self):
649 655 for node in self.nodes:
650 656 yield node
651 657
652 658 def get_node(self, path):
653 659 """
654 660 Returns node from within this particular ``DirNode``, so it is now
655 661 allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
656 662 'docs'. In order to access deeper nodes one must fetch nodes between
657 663 them first - this would work::
658 664
659 665 docs = root.get_node('docs')
660 666 docs.get_node('api').get_node('index.rst')
661 667
662 668 :param: path - relative to the current node
663 669
664 670 .. note::
665 671 To access lazily (as in example above) node have to be initialized
666 672 with related commit object - without it node is out of
667 673 context and may know nothing about anything else than nearest
668 674 (located at same level) nodes.
669 675 """
670 676 try:
671 677 path = path.rstrip('/')
672 678 if path == '':
673 679 raise NodeError("Cannot retrieve node without path")
674 680 self.nodes # access nodes first in order to set _nodes_dict
675 681 paths = path.split('/')
676 682 if len(paths) == 1:
677 683 if not self.is_root():
678 684 path = '/'.join((self.path, paths[0]))
679 685 else:
680 686 path = paths[0]
681 687 return self._nodes_dict[path]
682 688 elif len(paths) > 1:
683 689 if self.commit is None:
684 690 raise NodeError(
685 691 "Cannot access deeper nodes without commit")
686 692 else:
687 693 path1, path2 = paths[0], '/'.join(paths[1:])
688 694 return self.get_node(path1).get_node(path2)
689 695 else:
690 696 raise KeyError
691 697 except KeyError:
692 698 raise NodeError("Node does not exist at %s" % path)
693 699
694 700 @LazyProperty
695 701 def state(self):
696 702 raise NodeError("Cannot access state of DirNode")
697 703
698 704 @LazyProperty
699 705 def size(self):
700 706 size = 0
701 707 for root, dirs, files in self.commit.walk(self.path):
702 708 for f in files:
703 709 size += f.size
704 710
705 711 return size
706 712
707 713 def __repr__(self):
708 714 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
709 715 getattr(self.commit, 'short_id', ''))
710 716
711 717
712 718 class RootNode(DirNode):
713 719 """
714 720 DirNode being the root node of the repository.
715 721 """
716 722
717 723 def __init__(self, nodes=(), commit=None):
718 724 super(RootNode, self).__init__(path='', nodes=nodes, commit=commit)
719 725
720 726 def __repr__(self):
721 727 return '<%s>' % self.__class__.__name__
722 728
723 729
724 730 class SubModuleNode(Node):
725 731 """
726 732 represents a SubModule of Git or SubRepo of Mercurial
727 733 """
728 734 is_binary = False
729 735 size = 0
730 736
731 737 def __init__(self, name, url=None, commit=None, alias=None):
732 738 self.path = name
733 739 self.kind = NodeKind.SUBMODULE
734 740 self.alias = alias
735 741
736 742 # we have to use EmptyCommit here since this can point to svn/git/hg
737 743 # submodules we cannot get from repository
738 744 self.commit = EmptyCommit(str(commit), alias=alias)
739 745 self.url = url or self._extract_submodule_url()
740 746
741 747 def __repr__(self):
742 748 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
743 749 getattr(self.commit, 'short_id', ''))
744 750
745 751 def _extract_submodule_url(self):
746 752 # TODO: find a way to parse gits submodule file and extract the
747 753 # linking URL
748 754 return self.path
749 755
750 756 @LazyProperty
751 757 def name(self):
752 758 """
753 759 Returns name of the node so if its path
754 760 then only last part is returned.
755 761 """
756 762 org = safe_unicode(self.path.rstrip('/').split('/')[-1])
757 763 return u'%s @ %s' % (org, self.commit.short_id)
758 764
759 765
760 766 class LargeFileNode(FileNode):
761 767
762 768 def _validate_path(self, path):
763 769 """
764 770 we override check since the LargeFileNode path is system absolute
765 771 """
766 772
767 773 def raw_bytes(self):
768 774 if self.commit:
769 775 with open(self.path, 'rb') as f:
770 776 content = f.read()
771 777 else:
772 778 content = self._content
773 779 return content No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now