##// END OF EJS Templates
removed garbage return statement
marcink -
r3005:7f520c24 beta
parent child Browse files
Show More
@@ -1,619 +1,618 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.nodes
4 4 ~~~~~~~~~
5 5
6 6 Module holding everything related to vcs nodes.
7 7
8 8 :created_on: Apr 8, 2010
9 9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 10 """
11 11 import os
12 12 import stat
13 13 import posixpath
14 14 import mimetypes
15 15
16 16 from pygments import lexers
17 17
18 18 from rhodecode.lib.vcs.utils.lazy import LazyProperty
19 19 from rhodecode.lib.vcs.utils import safe_unicode
20 20 from rhodecode.lib.vcs.exceptions import NodeError
21 21 from rhodecode.lib.vcs.exceptions import RemovedFileNodeError
22 22 from rhodecode.lib.vcs.backends.base import EmptyChangeset
23 23
24 24
25 25 class NodeKind:
26 26 SUBMODULE = -1
27 27 DIR = 1
28 28 FILE = 2
29 29
30 30
31 31 class NodeState:
32 32 ADDED = u'added'
33 33 CHANGED = u'changed'
34 34 NOT_CHANGED = u'not changed'
35 35 REMOVED = u'removed'
36 36
37 37
38 38 class NodeGeneratorBase(object):
39 39 """
40 40 Base class for removed added and changed filenodes, it's a lazy generator
41 41 class that will create filenodes only on iteration or call
42 42
43 43 The len method doesn't need to create filenodes at all
44 44 """
45 45
46 46 def __init__(self, current_paths, cs):
47 47 self.cs = cs
48 48 self.current_paths = current_paths
49 49
50 50 def __call__(self):
51 51 return [n for n in self]
52 52
53 53 def __getslice__(self, i, j):
54 54 for p in self.current_paths[i:j]:
55 55 yield self.cs.get_node(p)
56 56
57 57 def __len__(self):
58 58 return len(self.current_paths)
59 59
60 60 def __iter__(self):
61 61 for p in self.current_paths:
62 62 yield self.cs.get_node(p)
63 63
64 64
65 65 class AddedFileNodesGenerator(NodeGeneratorBase):
66 66 """
67 67 Class holding Added files for current changeset
68 68 """
69 69 pass
70 70
71 71
72 72 class ChangedFileNodesGenerator(NodeGeneratorBase):
73 73 """
74 74 Class holding Changed files for current changeset
75 75 """
76 76 pass
77 77
78 78
79 79 class RemovedFileNodesGenerator(NodeGeneratorBase):
80 80 """
81 81 Class holding removed files for current changeset
82 82 """
83 83 def __iter__(self):
84 84 for p in self.current_paths:
85 85 yield RemovedFileNode(path=p)
86 86
87 87 def __getslice__(self, i, j):
88 88 for p in self.current_paths[i:j]:
89 89 yield RemovedFileNode(path=p)
90 90
91 91
92 92 class Node(object):
93 93 """
94 94 Simplest class representing file or directory on repository. SCM backends
95 95 should use ``FileNode`` and ``DirNode`` subclasses rather than ``Node``
96 96 directly.
97 97
98 98 Node's ``path`` cannot start with slash as we operate on *relative* paths
99 99 only. Moreover, every single node is identified by the ``path`` attribute,
100 100 so it cannot end with slash, too. Otherwise, path could lead to mistakes.
101 101 """
102 102
103 103 def __init__(self, path, kind):
104 104 if path.startswith('/'):
105 105 raise NodeError("Cannot initialize Node objects with slash at "
106 106 "the beginning as only relative paths are supported")
107 107 self.path = path.rstrip('/')
108 108 if path == '' and kind != NodeKind.DIR:
109 109 raise NodeError("Only DirNode and its subclasses may be "
110 110 "initialized with empty path")
111 111 self.kind = kind
112 112 #self.dirs, self.files = [], []
113 113 if self.is_root() and not self.is_dir():
114 114 raise NodeError("Root node cannot be FILE kind")
115 115
116 116 @LazyProperty
117 117 def parent(self):
118 118 parent_path = self.get_parent_path()
119 119 if parent_path:
120 120 if self.changeset:
121 121 return self.changeset.get_node(parent_path)
122 122 return DirNode(parent_path)
123 123 return None
124 124
125 125 @LazyProperty
126 126 def unicode_path(self):
127 127 return safe_unicode(self.path)
128 128
129 129 @LazyProperty
130 130 def name(self):
131 131 """
132 132 Returns name of the node so if its path
133 133 then only last part is returned.
134 134 """
135 135 return safe_unicode(self.path.rstrip('/').split('/')[-1])
136 136
137 137 def _get_kind(self):
138 138 return self._kind
139 139
140 140 def _set_kind(self, kind):
141 141 if hasattr(self, '_kind'):
142 142 raise NodeError("Cannot change node's kind")
143 143 else:
144 144 self._kind = kind
145 145 # Post setter check (path's trailing slash)
146 146 if self.path.endswith('/'):
147 147 raise NodeError("Node's path cannot end with slash")
148 148
149 149 kind = property(_get_kind, _set_kind)
150 150
151 151 def __cmp__(self, other):
152 152 """
153 153 Comparator using name of the node, needed for quick list sorting.
154 154 """
155 155 kind_cmp = cmp(self.kind, other.kind)
156 156 if kind_cmp:
157 157 return kind_cmp
158 158 return cmp(self.name, other.name)
159 159
160 160 def __eq__(self, other):
161 161 for attr in ['name', 'path', 'kind']:
162 162 if getattr(self, attr) != getattr(other, attr):
163 163 return False
164 164 if self.is_file():
165 165 if self.content != other.content:
166 166 return False
167 167 else:
168 168 # For DirNode's check without entering each dir
169 169 self_nodes_paths = list(sorted(n.path for n in self.nodes))
170 170 other_nodes_paths = list(sorted(n.path for n in self.nodes))
171 171 if self_nodes_paths != other_nodes_paths:
172 172 return False
173 173 return True
174 174
175 175 def __nq__(self, other):
176 176 return not self.__eq__(other)
177 177
178 178 def __repr__(self):
179 179 return '<%s %r>' % (self.__class__.__name__, self.path)
180 180
181 181 def __str__(self):
182 182 return self.__repr__()
183 183
184 184 def __unicode__(self):
185 185 return self.name
186 186
187 187 def get_parent_path(self):
188 188 """
189 189 Returns node's parent path or empty string if node is root.
190 190 """
191 191 if self.is_root():
192 192 return ''
193 193 return posixpath.dirname(self.path.rstrip('/')) + '/'
194 194
195 195 def is_file(self):
196 196 """
197 197 Returns ``True`` if node's kind is ``NodeKind.FILE``, ``False``
198 198 otherwise.
199 199 """
200 200 return self.kind == NodeKind.FILE
201 201
202 202 def is_dir(self):
203 203 """
204 204 Returns ``True`` if node's kind is ``NodeKind.DIR``, ``False``
205 205 otherwise.
206 206 """
207 207 return self.kind == NodeKind.DIR
208 208
209 209 def is_root(self):
210 210 """
211 211 Returns ``True`` if node is a root node and ``False`` otherwise.
212 212 """
213 213 return self.kind == NodeKind.DIR and self.path == ''
214 214
215 215 def is_submodule(self):
216 216 """
217 217 Returns ``True`` if node's kind is ``NodeKind.SUBMODULE``, ``False``
218 218 otherwise.
219 219 """
220 220 return self.kind == NodeKind.SUBMODULE
221 221
222 222 @LazyProperty
223 223 def added(self):
224 224 return self.state is NodeState.ADDED
225 225
226 226 @LazyProperty
227 227 def changed(self):
228 228 return self.state is NodeState.CHANGED
229 229
230 230 @LazyProperty
231 231 def not_changed(self):
232 232 return self.state is NodeState.NOT_CHANGED
233 233
234 234 @LazyProperty
235 235 def removed(self):
236 236 return self.state is NodeState.REMOVED
237 237
238 238
239 239 class FileNode(Node):
240 240 """
241 241 Class representing file nodes.
242 242
243 243 :attribute: path: path to the node, relative to repostiory's root
244 244 :attribute: content: if given arbitrary sets content of the file
245 245 :attribute: changeset: if given, first time content is accessed, callback
246 246 :attribute: mode: octal stat mode for a node. Default is 0100644.
247 247 """
248 248
249 249 def __init__(self, path, content=None, changeset=None, mode=None):
250 250 """
251 251 Only one of ``content`` and ``changeset`` may be given. Passing both
252 252 would raise ``NodeError`` exception.
253 253
254 254 :param path: relative path to the node
255 255 :param content: content may be passed to constructor
256 256 :param changeset: if given, will use it to lazily fetch content
257 257 :param mode: octal representation of ST_MODE (i.e. 0100644)
258 258 """
259 259
260 260 if content and changeset:
261 261 raise NodeError("Cannot use both content and changeset")
262 262 super(FileNode, self).__init__(path, kind=NodeKind.FILE)
263 263 self.changeset = changeset
264 264 self._content = content
265 265 self._mode = mode or 0100644
266 266
267 267 @LazyProperty
268 268 def mode(self):
269 269 """
270 270 Returns lazily mode of the FileNode. If ``changeset`` is not set, would
271 271 use value given at initialization or 0100644 (default).
272 272 """
273 273 if self.changeset:
274 274 mode = self.changeset.get_file_mode(self.path)
275 275 else:
276 276 mode = self._mode
277 277 return mode
278 278
279 279 def _get_content(self):
280 280 if self.changeset:
281 281 content = self.changeset.get_file_content(self.path)
282 282 else:
283 283 content = self._content
284 284 return content
285 285
286 286 @property
287 287 def content(self):
288 288 """
289 289 Returns lazily content of the FileNode. If possible, would try to
290 290 decode content from UTF-8.
291 291 """
292 292 content = self._get_content()
293 293
294 294 if bool(content and '\0' in content):
295 295 return content
296 296 return safe_unicode(content)
297 297
298 298 @LazyProperty
299 299 def size(self):
300 300 if self.changeset:
301 301 return self.changeset.get_file_size(self.path)
302 302 raise NodeError("Cannot retrieve size of the file without related "
303 303 "changeset attribute")
304 304
305 305 @LazyProperty
306 306 def message(self):
307 307 if self.changeset:
308 308 return self.last_changeset.message
309 309 raise NodeError("Cannot retrieve message of the file without related "
310 310 "changeset attribute")
311 311
312 312 @LazyProperty
313 313 def last_changeset(self):
314 314 if self.changeset:
315 315 return self.changeset.get_file_changeset(self.path)
316 316 raise NodeError("Cannot retrieve last changeset of the file without "
317 317 "related changeset attribute")
318 318
319 319 def get_mimetype(self):
320 320 """
321 321 Mimetype is calculated based on the file's content. If ``_mimetype``
322 322 attribute is available, it will be returned (backends which store
323 323 mimetypes or can easily recognize them, should set this private
324 324 attribute to indicate that type should *NOT* be calculated).
325 325 """
326 326 if hasattr(self, '_mimetype'):
327 327 if (isinstance(self._mimetype, (tuple, list,)) and
328 328 len(self._mimetype) == 2):
329 329 return self._mimetype
330 330 else:
331 331 raise NodeError('given _mimetype attribute must be an 2 '
332 332 'element list or tuple')
333 333
334 334 mtype, encoding = mimetypes.guess_type(self.name)
335 335
336 336 if mtype is None:
337 337 if self.is_binary:
338 338 mtype = 'application/octet-stream'
339 339 encoding = None
340 340 else:
341 341 mtype = 'text/plain'
342 342 encoding = None
343 343 return mtype, encoding
344 344
345 345 @LazyProperty
346 346 def mimetype(self):
347 347 """
348 348 Wrapper around full mimetype info. It returns only type of fetched
349 349 mimetype without the encoding part. use get_mimetype function to fetch
350 350 full set of (type,encoding)
351 351 """
352 352 return self.get_mimetype()[0]
353 353
354 354 @LazyProperty
355 355 def mimetype_main(self):
356 356 return ['', '']
357 357 return self.mimetype.split('/')[0]
358 358
359 359 @LazyProperty
360 360 def lexer(self):
361 361 """
362 362 Returns pygment's lexer class. Would try to guess lexer taking file's
363 363 content, name and mimetype.
364 364 """
365 365 try:
366 366 lexer = lexers.guess_lexer_for_filename(self.name, self.content)
367 367 except lexers.ClassNotFound:
368 368 lexer = lexers.TextLexer()
369 369 # returns first alias
370 370 return lexer
371 371
372 372 @LazyProperty
373 373 def lexer_alias(self):
374 374 """
375 375 Returns first alias of the lexer guessed for this file.
376 376 """
377 377 return self.lexer.aliases[0]
378 378
379 379 @LazyProperty
380 380 def history(self):
381 381 """
382 382 Returns a list of changeset for this file in which the file was changed
383 383 """
384 384 if self.changeset is None:
385 385 raise NodeError('Unable to get changeset for this FileNode')
386 386 return self.changeset.get_file_history(self.path)
387 387
388 388 @LazyProperty
389 389 def annotate(self):
390 390 """
391 391 Returns a list of three element tuples with lineno,changeset and line
392 392 """
393 393 if self.changeset is None:
394 394 raise NodeError('Unable to get changeset for this FileNode')
395 395 return self.changeset.get_file_annotate(self.path)
396 396
397 397 @LazyProperty
398 398 def state(self):
399 399 if not self.changeset:
400 400 raise NodeError("Cannot check state of the node if it's not "
401 401 "linked with changeset")
402 402 elif self.path in (node.path for node in self.changeset.added):
403 403 return NodeState.ADDED
404 404 elif self.path in (node.path for node in self.changeset.changed):
405 405 return NodeState.CHANGED
406 406 else:
407 407 return NodeState.NOT_CHANGED
408 408
409 409 @property
410 410 def is_binary(self):
411 411 """
412 412 Returns True if file has binary content.
413 413 """
414 return False
415 414 _bin = '\0' in self._get_content()
416 415 return _bin
417 416
418 417 @LazyProperty
419 418 def extension(self):
420 419 """Returns filenode extension"""
421 420 return self.name.split('.')[-1]
422 421
423 422 def is_executable(self):
424 423 """
425 424 Returns ``True`` if file has executable flag turned on.
426 425 """
427 426 return bool(self.mode & stat.S_IXUSR)
428 427
429 428 def __repr__(self):
430 429 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
431 430 getattr(self.changeset, 'short_id', ''))
432 431
433 432
434 433 class RemovedFileNode(FileNode):
435 434 """
436 435 Dummy FileNode class - trying to access any public attribute except path,
437 436 name, kind or state (or methods/attributes checking those two) would raise
438 437 RemovedFileNodeError.
439 438 """
440 439 ALLOWED_ATTRIBUTES = [
441 440 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind',
442 441 'added', 'changed', 'not_changed', 'removed'
443 442 ]
444 443
445 444 def __init__(self, path):
446 445 """
447 446 :param path: relative path to the node
448 447 """
449 448 super(RemovedFileNode, self).__init__(path=path)
450 449
451 450 def __getattribute__(self, attr):
452 451 if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
453 452 return super(RemovedFileNode, self).__getattribute__(attr)
454 453 raise RemovedFileNodeError("Cannot access attribute %s on "
455 454 "RemovedFileNode" % attr)
456 455
457 456 @LazyProperty
458 457 def state(self):
459 458 return NodeState.REMOVED
460 459
461 460
462 461 class DirNode(Node):
463 462 """
464 463 DirNode stores list of files and directories within this node.
465 464 Nodes may be used standalone but within repository context they
466 465 lazily fetch data within same repositorty's changeset.
467 466 """
468 467
469 468 def __init__(self, path, nodes=(), changeset=None):
470 469 """
471 470 Only one of ``nodes`` and ``changeset`` may be given. Passing both
472 471 would raise ``NodeError`` exception.
473 472
474 473 :param path: relative path to the node
475 474 :param nodes: content may be passed to constructor
476 475 :param changeset: if given, will use it to lazily fetch content
477 476 :param size: always 0 for ``DirNode``
478 477 """
479 478 if nodes and changeset:
480 479 raise NodeError("Cannot use both nodes and changeset")
481 480 super(DirNode, self).__init__(path, NodeKind.DIR)
482 481 self.changeset = changeset
483 482 self._nodes = nodes
484 483
485 484 @LazyProperty
486 485 def content(self):
487 486 raise NodeError("%s represents a dir and has no ``content`` attribute"
488 487 % self)
489 488
490 489 @LazyProperty
491 490 def nodes(self):
492 491 if self.changeset:
493 492 nodes = self.changeset.get_nodes(self.path)
494 493 else:
495 494 nodes = self._nodes
496 495 self._nodes_dict = dict((node.path, node) for node in nodes)
497 496 return sorted(nodes)
498 497
499 498 @LazyProperty
500 499 def files(self):
501 500 return sorted((node for node in self.nodes if node.is_file()))
502 501
503 502 @LazyProperty
504 503 def dirs(self):
505 504 return sorted((node for node in self.nodes if node.is_dir()))
506 505
507 506 def __iter__(self):
508 507 for node in self.nodes:
509 508 yield node
510 509
511 510 def get_node(self, path):
512 511 """
513 512 Returns node from within this particular ``DirNode``, so it is now
514 513 allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
515 514 'docs'. In order to access deeper nodes one must fetch nodes between
516 515 them first - this would work::
517 516
518 517 docs = root.get_node('docs')
519 518 docs.get_node('api').get_node('index.rst')
520 519
521 520 :param: path - relative to the current node
522 521
523 522 .. note::
524 523 To access lazily (as in example above) node have to be initialized
525 524 with related changeset object - without it node is out of
526 525 context and may know nothing about anything else than nearest
527 526 (located at same level) nodes.
528 527 """
529 528 try:
530 529 path = path.rstrip('/')
531 530 if path == '':
532 531 raise NodeError("Cannot retrieve node without path")
533 532 self.nodes # access nodes first in order to set _nodes_dict
534 533 paths = path.split('/')
535 534 if len(paths) == 1:
536 535 if not self.is_root():
537 536 path = '/'.join((self.path, paths[0]))
538 537 else:
539 538 path = paths[0]
540 539 return self._nodes_dict[path]
541 540 elif len(paths) > 1:
542 541 if self.changeset is None:
543 542 raise NodeError("Cannot access deeper "
544 543 "nodes without changeset")
545 544 else:
546 545 path1, path2 = paths[0], '/'.join(paths[1:])
547 546 return self.get_node(path1).get_node(path2)
548 547 else:
549 548 raise KeyError
550 549 except KeyError:
551 550 raise NodeError("Node does not exist at %s" % path)
552 551
553 552 @LazyProperty
554 553 def state(self):
555 554 raise NodeError("Cannot access state of DirNode")
556 555
557 556 @LazyProperty
558 557 def size(self):
559 558 size = 0
560 559 for root, dirs, files in self.changeset.walk(self.path):
561 560 for f in files:
562 561 size += f.size
563 562
564 563 return size
565 564
566 565 def __repr__(self):
567 566 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
568 567 getattr(self.changeset, 'short_id', ''))
569 568
570 569
571 570 class RootNode(DirNode):
572 571 """
573 572 DirNode being the root node of the repository.
574 573 """
575 574
576 575 def __init__(self, nodes=(), changeset=None):
577 576 super(RootNode, self).__init__(path='', nodes=nodes,
578 577 changeset=changeset)
579 578
580 579 def __repr__(self):
581 580 return '<%s>' % self.__class__.__name__
582 581
583 582
584 583 class SubModuleNode(Node):
585 584 """
586 585 represents a SubModule of Git or SubRepo of Mercurial
587 586 """
588 587 is_binary = False
589 588 size = 0
590 589
591 590 def __init__(self, name, url=None, changeset=None, alias=None):
592 591 self.path = name
593 592 self.kind = NodeKind.SUBMODULE
594 593 self.alias = alias
595 594 # we have to use emptyChangeset here since this can point to svn/git/hg
596 595 # submodules we cannot get from repository
597 596 self.changeset = EmptyChangeset(str(changeset), alias=alias)
598 597 self.url = url or self._extract_submodule_url()
599 598
600 599 def __repr__(self):
601 600 return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
602 601 getattr(self.changeset, 'short_id', ''))
603 602
604 603 def _extract_submodule_url(self):
605 604 if self.alias == 'git':
606 605 #TODO: find a way to parse gits submodule file and extract the
607 606 # linking URL
608 607 return self.path
609 608 if self.alias == 'hg':
610 609 return self.path
611 610
612 611 @LazyProperty
613 612 def name(self):
614 613 """
615 614 Returns name of the node so if its path
616 615 then only last part is returned.
617 616 """
618 617 org = safe_unicode(self.path.rstrip('/').split('/')[-1])
619 618 return u'%s @ %s' % (org, self.changeset.short_id)
General Comments 0
You need to be logged in to leave comments. Login now