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