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