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