##// END OF EJS Templates
vcs: access changeset status "struct" members - hg 5.3 will drop indexed access
Mads Kiilerich -
r8051:744b7e78 default
parent child Browse files
Show More
@@ -1,407 +1,407 b''
1 import os
1 import os
2 import posixpath
2 import posixpath
3
3
4 import mercurial.archival
4 import mercurial.archival
5 import mercurial.node
5 import mercurial.node
6 import mercurial.obsutil
6 import mercurial.obsutil
7
7
8 from kallithea.lib.vcs.backends.base import BaseChangeset
8 from kallithea.lib.vcs.backends.base import BaseChangeset
9 from kallithea.lib.vcs.conf import settings
9 from kallithea.lib.vcs.conf import settings
10 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
10 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
11 from kallithea.lib.vcs.nodes import (
11 from kallithea.lib.vcs.nodes import (
12 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode)
12 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode)
13 from kallithea.lib.vcs.utils import ascii_bytes, ascii_str, date_fromtimestamp, safe_bytes, safe_str, safe_unicode
13 from kallithea.lib.vcs.utils import ascii_bytes, ascii_str, date_fromtimestamp, safe_bytes, safe_str, safe_unicode
14 from kallithea.lib.vcs.utils.lazy import LazyProperty
14 from kallithea.lib.vcs.utils.lazy import LazyProperty
15 from kallithea.lib.vcs.utils.paths import get_dirs_for_path
15 from kallithea.lib.vcs.utils.paths import get_dirs_for_path
16
16
17
17
18 class MercurialChangeset(BaseChangeset):
18 class MercurialChangeset(BaseChangeset):
19 """
19 """
20 Represents state of the repository at a revision.
20 Represents state of the repository at a revision.
21 """
21 """
22
22
23 def __init__(self, repository, revision):
23 def __init__(self, repository, revision):
24 self.repository = repository
24 self.repository = repository
25 assert isinstance(revision, basestring), repr(revision)
25 assert isinstance(revision, basestring), repr(revision)
26 self._ctx = repository._repo[ascii_bytes(revision)]
26 self._ctx = repository._repo[ascii_bytes(revision)]
27 self.raw_id = ascii_str(self._ctx.hex())
27 self.raw_id = ascii_str(self._ctx.hex())
28 self.revision = self._ctx._rev
28 self.revision = self._ctx._rev
29 self.nodes = {}
29 self.nodes = {}
30
30
31 @LazyProperty
31 @LazyProperty
32 def tags(self):
32 def tags(self):
33 return [safe_unicode(tag) for tag in self._ctx.tags()]
33 return [safe_unicode(tag) for tag in self._ctx.tags()]
34
34
35 @LazyProperty
35 @LazyProperty
36 def branch(self):
36 def branch(self):
37 return safe_unicode(self._ctx.branch())
37 return safe_unicode(self._ctx.branch())
38
38
39 @LazyProperty
39 @LazyProperty
40 def branches(self):
40 def branches(self):
41 return [safe_unicode(self._ctx.branch())]
41 return [safe_unicode(self._ctx.branch())]
42
42
43 @LazyProperty
43 @LazyProperty
44 def closesbranch(self):
44 def closesbranch(self):
45 return self._ctx.closesbranch()
45 return self._ctx.closesbranch()
46
46
47 @LazyProperty
47 @LazyProperty
48 def obsolete(self):
48 def obsolete(self):
49 return self._ctx.obsolete()
49 return self._ctx.obsolete()
50
50
51 @LazyProperty
51 @LazyProperty
52 def bumped(self):
52 def bumped(self):
53 return self._ctx.phasedivergent()
53 return self._ctx.phasedivergent()
54
54
55 @LazyProperty
55 @LazyProperty
56 def divergent(self):
56 def divergent(self):
57 return self._ctx.contentdivergent()
57 return self._ctx.contentdivergent()
58
58
59 @LazyProperty
59 @LazyProperty
60 def extinct(self):
60 def extinct(self):
61 return self._ctx.extinct()
61 return self._ctx.extinct()
62
62
63 @LazyProperty
63 @LazyProperty
64 def unstable(self):
64 def unstable(self):
65 return self._ctx.orphan()
65 return self._ctx.orphan()
66
66
67 @LazyProperty
67 @LazyProperty
68 def phase(self):
68 def phase(self):
69 if(self._ctx.phase() == 1):
69 if(self._ctx.phase() == 1):
70 return 'Draft'
70 return 'Draft'
71 elif(self._ctx.phase() == 2):
71 elif(self._ctx.phase() == 2):
72 return 'Secret'
72 return 'Secret'
73 else:
73 else:
74 return ''
74 return ''
75
75
76 @LazyProperty
76 @LazyProperty
77 def successors(self):
77 def successors(self):
78 successors = mercurial.obsutil.successorssets(self._ctx._repo, self._ctx.node(), closest=True)
78 successors = mercurial.obsutil.successorssets(self._ctx._repo, self._ctx.node(), closest=True)
79 if successors:
79 if successors:
80 # flatten the list here handles both divergent (len > 1)
80 # flatten the list here handles both divergent (len > 1)
81 # and the usual case (len = 1)
81 # and the usual case (len = 1)
82 successors = [mercurial.node.hex(n)[:12] for sub in successors for n in sub if n != self._ctx.node()]
82 successors = [mercurial.node.hex(n)[:12] for sub in successors for n in sub if n != self._ctx.node()]
83
83
84 return successors
84 return successors
85
85
86 @LazyProperty
86 @LazyProperty
87 def predecessors(self):
87 def predecessors(self):
88 return [mercurial.node.hex(n)[:12] for n in mercurial.obsutil.closestpredecessors(self._ctx._repo, self._ctx.node())]
88 return [mercurial.node.hex(n)[:12] for n in mercurial.obsutil.closestpredecessors(self._ctx._repo, self._ctx.node())]
89
89
90 @LazyProperty
90 @LazyProperty
91 def bookmarks(self):
91 def bookmarks(self):
92 return [safe_unicode(bookmark) for bookmark in self._ctx.bookmarks()]
92 return [safe_unicode(bookmark) for bookmark in self._ctx.bookmarks()]
93
93
94 @LazyProperty
94 @LazyProperty
95 def message(self):
95 def message(self):
96 return safe_unicode(self._ctx.description())
96 return safe_unicode(self._ctx.description())
97
97
98 @LazyProperty
98 @LazyProperty
99 def committer(self):
99 def committer(self):
100 return safe_unicode(self.author)
100 return safe_unicode(self.author)
101
101
102 @LazyProperty
102 @LazyProperty
103 def author(self):
103 def author(self):
104 return safe_unicode(self._ctx.user())
104 return safe_unicode(self._ctx.user())
105
105
106 @LazyProperty
106 @LazyProperty
107 def date(self):
107 def date(self):
108 return date_fromtimestamp(*self._ctx.date())
108 return date_fromtimestamp(*self._ctx.date())
109
109
110 @LazyProperty
110 @LazyProperty
111 def _timestamp(self):
111 def _timestamp(self):
112 return self._ctx.date()[0]
112 return self._ctx.date()[0]
113
113
114 @LazyProperty
114 @LazyProperty
115 def status(self):
115 def status(self):
116 """
116 """
117 Returns modified, added, removed, deleted files for current changeset
117 Returns modified, added, removed, deleted files for current changeset
118 """
118 """
119 return self.repository._repo.status(self._ctx.p1().node(),
119 return self.repository._repo.status(self._ctx.p1().node(),
120 self._ctx.node())
120 self._ctx.node())
121
121
122 @LazyProperty
122 @LazyProperty
123 def _file_paths(self):
123 def _file_paths(self):
124 return list(self._ctx)
124 return list(self._ctx)
125
125
126 @LazyProperty
126 @LazyProperty
127 def _dir_paths(self):
127 def _dir_paths(self):
128 p = list(set(get_dirs_for_path(*self._file_paths)))
128 p = list(set(get_dirs_for_path(*self._file_paths)))
129 p.insert(0, '')
129 p.insert(0, '')
130 return p
130 return p
131
131
132 @LazyProperty
132 @LazyProperty
133 def _paths(self):
133 def _paths(self):
134 return self._dir_paths + self._file_paths
134 return self._dir_paths + self._file_paths
135
135
136 @LazyProperty
136 @LazyProperty
137 def short_id(self):
137 def short_id(self):
138 return self.raw_id[:12]
138 return self.raw_id[:12]
139
139
140 @LazyProperty
140 @LazyProperty
141 def parents(self):
141 def parents(self):
142 """
142 """
143 Returns list of parents changesets.
143 Returns list of parents changesets.
144 """
144 """
145 return [self.repository.get_changeset(parent.rev())
145 return [self.repository.get_changeset(parent.rev())
146 for parent in self._ctx.parents() if parent.rev() >= 0]
146 for parent in self._ctx.parents() if parent.rev() >= 0]
147
147
148 @LazyProperty
148 @LazyProperty
149 def children(self):
149 def children(self):
150 """
150 """
151 Returns list of children changesets.
151 Returns list of children changesets.
152 """
152 """
153 return [self.repository.get_changeset(child.rev())
153 return [self.repository.get_changeset(child.rev())
154 for child in self._ctx.children() if child.rev() >= 0]
154 for child in self._ctx.children() if child.rev() >= 0]
155
155
156 def next(self, branch=None):
156 def next(self, branch=None):
157 if branch and self.branch != branch:
157 if branch and self.branch != branch:
158 raise VCSError('Branch option used on changeset not belonging '
158 raise VCSError('Branch option used on changeset not belonging '
159 'to that branch')
159 'to that branch')
160
160
161 cs = self
161 cs = self
162 while True:
162 while True:
163 try:
163 try:
164 next_ = cs.repository.revisions.index(cs.raw_id) + 1
164 next_ = cs.repository.revisions.index(cs.raw_id) + 1
165 next_rev = cs.repository.revisions[next_]
165 next_rev = cs.repository.revisions[next_]
166 except IndexError:
166 except IndexError:
167 raise ChangesetDoesNotExistError
167 raise ChangesetDoesNotExistError
168 cs = cs.repository.get_changeset(next_rev)
168 cs = cs.repository.get_changeset(next_rev)
169
169
170 if not branch or branch == cs.branch:
170 if not branch or branch == cs.branch:
171 return cs
171 return cs
172
172
173 def prev(self, branch=None):
173 def prev(self, branch=None):
174 if branch and self.branch != branch:
174 if branch and self.branch != branch:
175 raise VCSError('Branch option used on changeset not belonging '
175 raise VCSError('Branch option used on changeset not belonging '
176 'to that branch')
176 'to that branch')
177
177
178 cs = self
178 cs = self
179 while True:
179 while True:
180 try:
180 try:
181 prev_ = cs.repository.revisions.index(cs.raw_id) - 1
181 prev_ = cs.repository.revisions.index(cs.raw_id) - 1
182 if prev_ < 0:
182 if prev_ < 0:
183 raise IndexError
183 raise IndexError
184 prev_rev = cs.repository.revisions[prev_]
184 prev_rev = cs.repository.revisions[prev_]
185 except IndexError:
185 except IndexError:
186 raise ChangesetDoesNotExistError
186 raise ChangesetDoesNotExistError
187 cs = cs.repository.get_changeset(prev_rev)
187 cs = cs.repository.get_changeset(prev_rev)
188
188
189 if not branch or branch == cs.branch:
189 if not branch or branch == cs.branch:
190 return cs
190 return cs
191
191
192 def diff(self):
192 def diff(self):
193 # Only used to feed diffstat
193 # Only used to feed diffstat
194 return b''.join(self._ctx.diff())
194 return b''.join(self._ctx.diff())
195
195
196 def _fix_path(self, path):
196 def _fix_path(self, path):
197 """
197 """
198 Paths are stored without trailing slash so we need to get rid off it if
198 Paths are stored without trailing slash so we need to get rid off it if
199 needed. Also mercurial keeps filenodes as str so we need to decode
199 needed. Also mercurial keeps filenodes as str so we need to decode
200 from unicode to str
200 from unicode to str
201 """
201 """
202 if path.endswith('/'):
202 if path.endswith('/'):
203 path = path.rstrip('/')
203 path = path.rstrip('/')
204
204
205 return safe_str(path)
205 return safe_str(path)
206
206
207 def _get_kind(self, path):
207 def _get_kind(self, path):
208 path = self._fix_path(path)
208 path = self._fix_path(path)
209 if path in self._file_paths:
209 if path in self._file_paths:
210 return NodeKind.FILE
210 return NodeKind.FILE
211 elif path in self._dir_paths:
211 elif path in self._dir_paths:
212 return NodeKind.DIR
212 return NodeKind.DIR
213 else:
213 else:
214 raise ChangesetError("Node does not exist at the given path '%s'"
214 raise ChangesetError("Node does not exist at the given path '%s'"
215 % (path))
215 % (path))
216
216
217 def _get_filectx(self, path):
217 def _get_filectx(self, path):
218 path = self._fix_path(path)
218 path = self._fix_path(path)
219 if self._get_kind(path) != NodeKind.FILE:
219 if self._get_kind(path) != NodeKind.FILE:
220 raise ChangesetError("File does not exist for revision %s at "
220 raise ChangesetError("File does not exist for revision %s at "
221 " '%s'" % (self.raw_id, path))
221 " '%s'" % (self.raw_id, path))
222 return self._ctx.filectx(safe_bytes(path))
222 return self._ctx.filectx(safe_bytes(path))
223
223
224 def _extract_submodules(self):
224 def _extract_submodules(self):
225 """
225 """
226 returns a dictionary with submodule information from substate file
226 returns a dictionary with submodule information from substate file
227 of hg repository
227 of hg repository
228 """
228 """
229 return self._ctx.substate
229 return self._ctx.substate
230
230
231 def get_file_mode(self, path):
231 def get_file_mode(self, path):
232 """
232 """
233 Returns stat mode of the file at the given ``path``.
233 Returns stat mode of the file at the given ``path``.
234 """
234 """
235 fctx = self._get_filectx(path)
235 fctx = self._get_filectx(path)
236 if b'x' in fctx.flags():
236 if b'x' in fctx.flags():
237 return 0o100755
237 return 0o100755
238 else:
238 else:
239 return 0o100644
239 return 0o100644
240
240
241 def get_file_content(self, path):
241 def get_file_content(self, path):
242 """
242 """
243 Returns content of the file at given ``path``.
243 Returns content of the file at given ``path``.
244 """
244 """
245 fctx = self._get_filectx(path)
245 fctx = self._get_filectx(path)
246 return fctx.data()
246 return fctx.data()
247
247
248 def get_file_size(self, path):
248 def get_file_size(self, path):
249 """
249 """
250 Returns size of the file at given ``path``.
250 Returns size of the file at given ``path``.
251 """
251 """
252 fctx = self._get_filectx(path)
252 fctx = self._get_filectx(path)
253 return fctx.size()
253 return fctx.size()
254
254
255 def get_file_changeset(self, path):
255 def get_file_changeset(self, path):
256 """
256 """
257 Returns last commit of the file at the given ``path``.
257 Returns last commit of the file at the given ``path``.
258 """
258 """
259 return self.get_file_history(path, limit=1)[0]
259 return self.get_file_history(path, limit=1)[0]
260
260
261 def get_file_history(self, path, limit=None):
261 def get_file_history(self, path, limit=None):
262 """
262 """
263 Returns history of file as reversed list of ``Changeset`` objects for
263 Returns history of file as reversed list of ``Changeset`` objects for
264 which file at given ``path`` has been modified.
264 which file at given ``path`` has been modified.
265 """
265 """
266 fctx = self._get_filectx(path)
266 fctx = self._get_filectx(path)
267 hist = []
267 hist = []
268 cnt = 0
268 cnt = 0
269 for cs in reversed([x for x in fctx.filelog()]):
269 for cs in reversed([x for x in fctx.filelog()]):
270 cnt += 1
270 cnt += 1
271 hist.append(mercurial.node.hex(fctx.filectx(cs).node()))
271 hist.append(mercurial.node.hex(fctx.filectx(cs).node()))
272 if limit is not None and cnt == limit:
272 if limit is not None and cnt == limit:
273 break
273 break
274
274
275 return [self.repository.get_changeset(node) for node in hist]
275 return [self.repository.get_changeset(node) for node in hist]
276
276
277 def get_file_annotate(self, path):
277 def get_file_annotate(self, path):
278 """
278 """
279 Returns a generator of four element tuples with
279 Returns a generator of four element tuples with
280 lineno, sha, changeset lazy loader and line
280 lineno, sha, changeset lazy loader and line
281 """
281 """
282 annotations = self._get_filectx(path).annotate()
282 annotations = self._get_filectx(path).annotate()
283 annotation_lines = [(annotateline.fctx, annotateline.text) for annotateline in annotations]
283 annotation_lines = [(annotateline.fctx, annotateline.text) for annotateline in annotations]
284 for i, (fctx, line) in enumerate(annotation_lines):
284 for i, (fctx, line) in enumerate(annotation_lines):
285 sha = ascii_str(fctx.hex())
285 sha = ascii_str(fctx.hex())
286 yield (i + 1, sha, lambda sha=sha: self.repository.get_changeset(sha), line)
286 yield (i + 1, sha, lambda sha=sha: self.repository.get_changeset(sha), line)
287
287
288 def fill_archive(self, stream=None, kind='tgz', prefix=None,
288 def fill_archive(self, stream=None, kind='tgz', prefix=None,
289 subrepos=False):
289 subrepos=False):
290 """
290 """
291 Fills up given stream.
291 Fills up given stream.
292
292
293 :param stream: file like object.
293 :param stream: file like object.
294 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
294 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
295 Default: ``tgz``.
295 Default: ``tgz``.
296 :param prefix: name of root directory in archive.
296 :param prefix: name of root directory in archive.
297 Default is repository name and changeset's raw_id joined with dash
297 Default is repository name and changeset's raw_id joined with dash
298 (``repo-tip.<KIND>``).
298 (``repo-tip.<KIND>``).
299 :param subrepos: include subrepos in this archive.
299 :param subrepos: include subrepos in this archive.
300
300
301 :raise ImproperArchiveTypeError: If given kind is wrong.
301 :raise ImproperArchiveTypeError: If given kind is wrong.
302 :raise VcsError: If given stream is None
302 :raise VcsError: If given stream is None
303 """
303 """
304 allowed_kinds = settings.ARCHIVE_SPECS
304 allowed_kinds = settings.ARCHIVE_SPECS
305 if kind not in allowed_kinds:
305 if kind not in allowed_kinds:
306 raise ImproperArchiveTypeError('Archive kind not supported use one'
306 raise ImproperArchiveTypeError('Archive kind not supported use one'
307 'of %s' % ' '.join(allowed_kinds))
307 'of %s' % ' '.join(allowed_kinds))
308
308
309 if stream is None:
309 if stream is None:
310 raise VCSError('You need to pass in a valid stream for filling'
310 raise VCSError('You need to pass in a valid stream for filling'
311 ' with archival data')
311 ' with archival data')
312
312
313 if prefix is None:
313 if prefix is None:
314 prefix = '%s-%s' % (self.repository.name, self.short_id)
314 prefix = '%s-%s' % (self.repository.name, self.short_id)
315 elif prefix.startswith('/'):
315 elif prefix.startswith('/'):
316 raise VCSError("Prefix cannot start with leading slash")
316 raise VCSError("Prefix cannot start with leading slash")
317 elif prefix.strip() == '':
317 elif prefix.strip() == '':
318 raise VCSError("Prefix cannot be empty")
318 raise VCSError("Prefix cannot be empty")
319
319
320 mercurial.archival.archive(self.repository._repo, stream, ascii_bytes(self.raw_id),
320 mercurial.archival.archive(self.repository._repo, stream, ascii_bytes(self.raw_id),
321 safe_bytes(kind), prefix=safe_bytes(prefix), subrepos=subrepos)
321 safe_bytes(kind), prefix=safe_bytes(prefix), subrepos=subrepos)
322
322
323 def get_nodes(self, path):
323 def get_nodes(self, path):
324 """
324 """
325 Returns combined ``DirNode`` and ``FileNode`` objects list representing
325 Returns combined ``DirNode`` and ``FileNode`` objects list representing
326 state of changeset at the given ``path``. If node at the given ``path``
326 state of changeset at the given ``path``. If node at the given ``path``
327 is not instance of ``DirNode``, ChangesetError would be raised.
327 is not instance of ``DirNode``, ChangesetError would be raised.
328 """
328 """
329
329
330 if self._get_kind(path) != NodeKind.DIR:
330 if self._get_kind(path) != NodeKind.DIR:
331 raise ChangesetError("Directory does not exist for revision %s at "
331 raise ChangesetError("Directory does not exist for revision %s at "
332 " '%s'" % (self.revision, path))
332 " '%s'" % (self.revision, path))
333 path = self._fix_path(path)
333 path = self._fix_path(path)
334
334
335 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
335 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
336 if os.path.dirname(f) == path]
336 if os.path.dirname(f) == path]
337 dirs = path == '' and '' or [d for d in self._dir_paths
337 dirs = path == '' and '' or [d for d in self._dir_paths
338 if d and posixpath.dirname(d) == path]
338 if d and posixpath.dirname(d) == path]
339 dirnodes = [DirNode(d, changeset=self) for d in dirs
339 dirnodes = [DirNode(d, changeset=self) for d in dirs
340 if os.path.dirname(d) == path]
340 if os.path.dirname(d) == path]
341
341
342 als = self.repository.alias
342 als = self.repository.alias
343 for k, vals in self._extract_submodules().iteritems():
343 for k, vals in self._extract_submodules().iteritems():
344 #vals = url,rev,type
344 #vals = url,rev,type
345 loc = vals[0]
345 loc = vals[0]
346 cs = vals[1]
346 cs = vals[1]
347 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
347 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
348 alias=als))
348 alias=als))
349 nodes = dirnodes + filenodes
349 nodes = dirnodes + filenodes
350 for node in nodes:
350 for node in nodes:
351 self.nodes[node.path] = node
351 self.nodes[node.path] = node
352 nodes.sort()
352 nodes.sort()
353 return nodes
353 return nodes
354
354
355 def get_node(self, path):
355 def get_node(self, path):
356 """
356 """
357 Returns ``Node`` object from the given ``path``. If there is no node at
357 Returns ``Node`` object from the given ``path``. If there is no node at
358 the given ``path``, ``ChangesetError`` would be raised.
358 the given ``path``, ``ChangesetError`` would be raised.
359 """
359 """
360 path = self._fix_path(path)
360 path = self._fix_path(path)
361 if path not in self.nodes:
361 if path not in self.nodes:
362 if path in self._file_paths:
362 if path in self._file_paths:
363 node = FileNode(path, changeset=self)
363 node = FileNode(path, changeset=self)
364 elif path in self._dir_paths or path in self._dir_paths:
364 elif path in self._dir_paths or path in self._dir_paths:
365 if path == '':
365 if path == '':
366 node = RootNode(changeset=self)
366 node = RootNode(changeset=self)
367 else:
367 else:
368 node = DirNode(path, changeset=self)
368 node = DirNode(path, changeset=self)
369 else:
369 else:
370 raise NodeDoesNotExistError("There is no file nor directory "
370 raise NodeDoesNotExistError("There is no file nor directory "
371 "at the given path: '%s' at revision %s"
371 "at the given path: '%s' at revision %s"
372 % (path, self.short_id))
372 % (path, self.short_id))
373 # cache node
373 # cache node
374 self.nodes[path] = node
374 self.nodes[path] = node
375 return self.nodes[path]
375 return self.nodes[path]
376
376
377 @LazyProperty
377 @LazyProperty
378 def affected_files(self):
378 def affected_files(self):
379 """
379 """
380 Gets a fast accessible file changes for given changeset
380 Gets a fast accessible file changes for given changeset
381 """
381 """
382 return self._ctx.files()
382 return self._ctx.files()
383
383
384 @property
384 @property
385 def added(self):
385 def added(self):
386 """
386 """
387 Returns list of added ``FileNode`` objects.
387 Returns list of added ``FileNode`` objects.
388 """
388 """
389 return AddedFileNodesGenerator([n for n in self.status[1]], self)
389 return AddedFileNodesGenerator([n for n in self.status.added], self)
390
390
391 @property
391 @property
392 def changed(self):
392 def changed(self):
393 """
393 """
394 Returns list of modified ``FileNode`` objects.
394 Returns list of modified ``FileNode`` objects.
395 """
395 """
396 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
396 return ChangedFileNodesGenerator([n for n in self.status.modified], self)
397
397
398 @property
398 @property
399 def removed(self):
399 def removed(self):
400 """
400 """
401 Returns list of removed ``FileNode`` objects.
401 Returns list of removed ``FileNode`` objects.
402 """
402 """
403 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
403 return RemovedFileNodesGenerator([n for n in self.status.removed], self)
404
404
405 @LazyProperty
405 @LazyProperty
406 def extra(self):
406 def extra(self):
407 return self._ctx.extra()
407 return self._ctx.extra()
General Comments 0
You need to be logged in to leave comments. Login now