##// END OF EJS Templates
mercurial: use commit_ids for parents/children instead of revisions which could change when evolve is used.
marcink -
r3935:d6d0e7ad default
parent child Browse files
Show More
@@ -1,389 +1,389 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2019 RhodeCode GmbH
3 # Copyright (C) 2014-2019 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 HG commit module
22 HG commit module
23 """
23 """
24
24
25 import os
25 import os
26
26
27 from zope.cachedescriptors.property import Lazy as LazyProperty
27 from zope.cachedescriptors.property import Lazy as LazyProperty
28
28
29 from rhodecode.lib.datelib import utcdate_fromtimestamp
29 from rhodecode.lib.datelib import utcdate_fromtimestamp
30 from rhodecode.lib.utils import safe_str, safe_unicode
30 from rhodecode.lib.utils import safe_str, safe_unicode
31 from rhodecode.lib.vcs import path as vcspath
31 from rhodecode.lib.vcs import path as vcspath
32 from rhodecode.lib.vcs.backends import base
32 from rhodecode.lib.vcs.backends import base
33 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
33 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
34 from rhodecode.lib.vcs.exceptions import CommitError
34 from rhodecode.lib.vcs.exceptions import CommitError
35 from rhodecode.lib.vcs.nodes import (
35 from rhodecode.lib.vcs.nodes import (
36 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
36 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
37 NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode,
37 NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode,
38 LargeFileNode, LARGEFILE_PREFIX)
38 LargeFileNode, LARGEFILE_PREFIX)
39 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
39 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
40
40
41
41
42 class MercurialCommit(base.BaseCommit):
42 class MercurialCommit(base.BaseCommit):
43 """
43 """
44 Represents state of the repository at the single commit.
44 Represents state of the repository at the single commit.
45 """
45 """
46
46
47 _filter_pre_load = [
47 _filter_pre_load = [
48 # git specific property not supported here
48 # git specific property not supported here
49 "_commit",
49 "_commit",
50 ]
50 ]
51
51
52 def __init__(self, repository, raw_id, idx, pre_load=None):
52 def __init__(self, repository, raw_id, idx, pre_load=None):
53 raw_id = safe_str(raw_id)
53 raw_id = safe_str(raw_id)
54
54
55 self.repository = repository
55 self.repository = repository
56 self._remote = repository._remote
56 self._remote = repository._remote
57
57
58 self.raw_id = raw_id
58 self.raw_id = raw_id
59 self.idx = idx
59 self.idx = idx
60
60
61 self._set_bulk_properties(pre_load)
61 self._set_bulk_properties(pre_load)
62
62
63 # caches
63 # caches
64 self.nodes = {}
64 self.nodes = {}
65
65
66 def _set_bulk_properties(self, pre_load):
66 def _set_bulk_properties(self, pre_load):
67 if not pre_load:
67 if not pre_load:
68 return
68 return
69 pre_load = [entry for entry in pre_load
69 pre_load = [entry for entry in pre_load
70 if entry not in self._filter_pre_load]
70 if entry not in self._filter_pre_load]
71 if not pre_load:
71 if not pre_load:
72 return
72 return
73
73
74 result = self._remote.bulk_request(self.raw_id, pre_load)
74 result = self._remote.bulk_request(self.raw_id, pre_load)
75 for attr, value in result.items():
75 for attr, value in result.items():
76 if attr in ["author", "branch", "message"]:
76 if attr in ["author", "branch", "message"]:
77 value = safe_unicode(value)
77 value = safe_unicode(value)
78 elif attr == "affected_files":
78 elif attr == "affected_files":
79 value = map(safe_unicode, value)
79 value = map(safe_unicode, value)
80 elif attr == "date":
80 elif attr == "date":
81 value = utcdate_fromtimestamp(*value)
81 value = utcdate_fromtimestamp(*value)
82 elif attr in ["children", "parents"]:
82 elif attr in ["children", "parents"]:
83 value = self._make_commits(value)
83 value = self._make_commits(value)
84 elif attr in ["phase"]:
84 elif attr in ["phase"]:
85 value = self._get_phase_text(value)
85 value = self._get_phase_text(value)
86 self.__dict__[attr] = value
86 self.__dict__[attr] = value
87
87
88 @LazyProperty
88 @LazyProperty
89 def tags(self):
89 def tags(self):
90 tags = [name for name, commit_id in self.repository.tags.iteritems()
90 tags = [name for name, commit_id in self.repository.tags.iteritems()
91 if commit_id == self.raw_id]
91 if commit_id == self.raw_id]
92 return tags
92 return tags
93
93
94 @LazyProperty
94 @LazyProperty
95 def branch(self):
95 def branch(self):
96 return safe_unicode(self._remote.ctx_branch(self.raw_id))
96 return safe_unicode(self._remote.ctx_branch(self.raw_id))
97
97
98 @LazyProperty
98 @LazyProperty
99 def bookmarks(self):
99 def bookmarks(self):
100 bookmarks = [
100 bookmarks = [
101 name for name, commit_id in self.repository.bookmarks.iteritems()
101 name for name, commit_id in self.repository.bookmarks.iteritems()
102 if commit_id == self.raw_id]
102 if commit_id == self.raw_id]
103 return bookmarks
103 return bookmarks
104
104
105 @LazyProperty
105 @LazyProperty
106 def message(self):
106 def message(self):
107 return safe_unicode(self._remote.ctx_description(self.raw_id))
107 return safe_unicode(self._remote.ctx_description(self.raw_id))
108
108
109 @LazyProperty
109 @LazyProperty
110 def committer(self):
110 def committer(self):
111 return safe_unicode(self.author)
111 return safe_unicode(self.author)
112
112
113 @LazyProperty
113 @LazyProperty
114 def author(self):
114 def author(self):
115 return safe_unicode(self._remote.ctx_user(self.raw_id))
115 return safe_unicode(self._remote.ctx_user(self.raw_id))
116
116
117 @LazyProperty
117 @LazyProperty
118 def date(self):
118 def date(self):
119 return utcdate_fromtimestamp(*self._remote.ctx_date(self.raw_id))
119 return utcdate_fromtimestamp(*self._remote.ctx_date(self.raw_id))
120
120
121 @LazyProperty
121 @LazyProperty
122 def status(self):
122 def status(self):
123 """
123 """
124 Returns modified, added, removed, deleted files for current commit
124 Returns modified, added, removed, deleted files for current commit
125 """
125 """
126 return self._remote.ctx_status(self.raw_id)
126 return self._remote.ctx_status(self.raw_id)
127
127
128 @LazyProperty
128 @LazyProperty
129 def _file_paths(self):
129 def _file_paths(self):
130 return self._remote.ctx_list(self.raw_id)
130 return self._remote.ctx_list(self.raw_id)
131
131
132 @LazyProperty
132 @LazyProperty
133 def _dir_paths(self):
133 def _dir_paths(self):
134 p = list(set(get_dirs_for_path(*self._file_paths)))
134 p = list(set(get_dirs_for_path(*self._file_paths)))
135 p.insert(0, '')
135 p.insert(0, '')
136 return p
136 return p
137
137
138 @LazyProperty
138 @LazyProperty
139 def _paths(self):
139 def _paths(self):
140 return self._dir_paths + self._file_paths
140 return self._dir_paths + self._file_paths
141
141
142 @LazyProperty
142 @LazyProperty
143 def id(self):
143 def id(self):
144 if self.last:
144 if self.last:
145 return u'tip'
145 return u'tip'
146 return self.short_id
146 return self.short_id
147
147
148 @LazyProperty
148 @LazyProperty
149 def short_id(self):
149 def short_id(self):
150 return self.raw_id[:12]
150 return self.raw_id[:12]
151
151
152 def _make_commits(self, indexes, pre_load=None):
152 def _make_commits(self, commit_ids, pre_load=None):
153 return [self.repository.get_commit(commit_idx=idx, pre_load=pre_load)
153 return [self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
154 for idx in indexes if idx >= 0]
154 for commit_id in commit_ids]
155
155
156 @LazyProperty
156 @LazyProperty
157 def parents(self):
157 def parents(self):
158 """
158 """
159 Returns list of parent commits.
159 Returns list of parent commits.
160 """
160 """
161 parents = self._remote.ctx_parents(self.raw_id)
161 parents = self._remote.ctx_parents(self.raw_id)
162 return self._make_commits(parents)
162 return self._make_commits(parents)
163
163
164 def _get_phase_text(self, phase_id):
164 def _get_phase_text(self, phase_id):
165 return {
165 return {
166 0: 'public',
166 0: 'public',
167 1: 'draft',
167 1: 'draft',
168 2: 'secret',
168 2: 'secret',
169 }.get(phase_id) or ''
169 }.get(phase_id) or ''
170
170
171 @LazyProperty
171 @LazyProperty
172 def phase(self):
172 def phase(self):
173 phase_id = self._remote.ctx_phase(self.raw_id)
173 phase_id = self._remote.ctx_phase(self.raw_id)
174 phase_text = self._get_phase_text(phase_id)
174 phase_text = self._get_phase_text(phase_id)
175
175
176 return safe_unicode(phase_text)
176 return safe_unicode(phase_text)
177
177
178 @LazyProperty
178 @LazyProperty
179 def obsolete(self):
179 def obsolete(self):
180 obsolete = self._remote.ctx_obsolete(self.raw_id)
180 obsolete = self._remote.ctx_obsolete(self.raw_id)
181 return obsolete
181 return obsolete
182
182
183 @LazyProperty
183 @LazyProperty
184 def hidden(self):
184 def hidden(self):
185 hidden = self._remote.ctx_hidden(self.raw_id)
185 hidden = self._remote.ctx_hidden(self.raw_id)
186 return hidden
186 return hidden
187
187
188 @LazyProperty
188 @LazyProperty
189 def children(self):
189 def children(self):
190 """
190 """
191 Returns list of child commits.
191 Returns list of child commits.
192 """
192 """
193 children = self._remote.ctx_children(self.raw_id)
193 children = self._remote.ctx_children(self.raw_id)
194 return self._make_commits(children)
194 return self._make_commits(children)
195
195
196 def _fix_path(self, path):
196 def _fix_path(self, path):
197 """
197 """
198 Mercurial keeps filenodes as str so we need to encode from unicode
198 Mercurial keeps filenodes as str so we need to encode from unicode
199 to str.
199 to str.
200 """
200 """
201 return safe_str(super(MercurialCommit, self)._fix_path(path))
201 return safe_str(super(MercurialCommit, self)._fix_path(path))
202
202
203 def _get_kind(self, path):
203 def _get_kind(self, path):
204 path = self._fix_path(path)
204 path = self._fix_path(path)
205 if path in self._file_paths:
205 if path in self._file_paths:
206 return NodeKind.FILE
206 return NodeKind.FILE
207 elif path in self._dir_paths:
207 elif path in self._dir_paths:
208 return NodeKind.DIR
208 return NodeKind.DIR
209 else:
209 else:
210 raise CommitError(
210 raise CommitError(
211 "Node does not exist at the given path '%s'" % (path, ))
211 "Node does not exist at the given path '%s'" % (path, ))
212
212
213 def _get_filectx(self, path):
213 def _get_filectx(self, path):
214 path = self._fix_path(path)
214 path = self._fix_path(path)
215 if self._get_kind(path) != NodeKind.FILE:
215 if self._get_kind(path) != NodeKind.FILE:
216 raise CommitError(
216 raise CommitError(
217 "File does not exist for idx %s at '%s'" % (self.raw_id, path))
217 "File does not exist for idx %s at '%s'" % (self.raw_id, path))
218 return path
218 return path
219
219
220 def get_file_mode(self, path):
220 def get_file_mode(self, path):
221 """
221 """
222 Returns stat mode of the file at the given ``path``.
222 Returns stat mode of the file at the given ``path``.
223 """
223 """
224 path = self._get_filectx(path)
224 path = self._get_filectx(path)
225 if 'x' in self._remote.fctx_flags(self.raw_id, path):
225 if 'x' in self._remote.fctx_flags(self.raw_id, path):
226 return base.FILEMODE_EXECUTABLE
226 return base.FILEMODE_EXECUTABLE
227 else:
227 else:
228 return base.FILEMODE_DEFAULT
228 return base.FILEMODE_DEFAULT
229
229
230 def is_link(self, path):
230 def is_link(self, path):
231 path = self._get_filectx(path)
231 path = self._get_filectx(path)
232 return 'l' in self._remote.fctx_flags(self.raw_id, path)
232 return 'l' in self._remote.fctx_flags(self.raw_id, path)
233
233
234 def is_node_binary(self, path):
234 def is_node_binary(self, path):
235 path = self._get_filectx(path)
235 path = self._get_filectx(path)
236 return self._remote.is_binary(self.raw_id, path)
236 return self._remote.is_binary(self.raw_id, path)
237
237
238 def get_file_content(self, path):
238 def get_file_content(self, path):
239 """
239 """
240 Returns content of the file at given ``path``.
240 Returns content of the file at given ``path``.
241 """
241 """
242 path = self._get_filectx(path)
242 path = self._get_filectx(path)
243 return self._remote.fctx_node_data(self.raw_id, path)
243 return self._remote.fctx_node_data(self.raw_id, path)
244
244
245 def get_file_content_streamed(self, path):
245 def get_file_content_streamed(self, path):
246 path = self._get_filectx(path)
246 path = self._get_filectx(path)
247 stream_method = getattr(self._remote, 'stream:fctx_node_data')
247 stream_method = getattr(self._remote, 'stream:fctx_node_data')
248 return stream_method(self.raw_id, path)
248 return stream_method(self.raw_id, path)
249
249
250 def get_file_size(self, path):
250 def get_file_size(self, path):
251 """
251 """
252 Returns size of the file at given ``path``.
252 Returns size of the file at given ``path``.
253 """
253 """
254 path = self._get_filectx(path)
254 path = self._get_filectx(path)
255 return self._remote.fctx_size(self.raw_id, path)
255 return self._remote.fctx_size(self.raw_id, path)
256
256
257 def get_path_history(self, path, limit=None, pre_load=None):
257 def get_path_history(self, path, limit=None, pre_load=None):
258 """
258 """
259 Returns history of file as reversed list of `MercurialCommit` objects
259 Returns history of file as reversed list of `MercurialCommit` objects
260 for which file at given ``path`` has been modified.
260 for which file at given ``path`` has been modified.
261 """
261 """
262 path = self._get_filectx(path)
262 path = self._get_filectx(path)
263 hist = self._remote.node_history(self.raw_id, path, limit)
263 hist = self._remote.node_history(self.raw_id, path, limit)
264 return [
264 return [
265 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
265 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
266 for commit_id in hist]
266 for commit_id in hist]
267
267
268 def get_file_annotate(self, path, pre_load=None):
268 def get_file_annotate(self, path, pre_load=None):
269 """
269 """
270 Returns a generator of four element tuples with
270 Returns a generator of four element tuples with
271 lineno, commit_id, commit lazy loader and line
271 lineno, commit_id, commit lazy loader and line
272 """
272 """
273 result = self._remote.fctx_annotate(self.raw_id, path)
273 result = self._remote.fctx_annotate(self.raw_id, path)
274
274
275 for ln_no, commit_id, content in result:
275 for ln_no, commit_id, content in result:
276 yield (
276 yield (
277 ln_no, commit_id,
277 ln_no, commit_id,
278 lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load),
278 lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load),
279 content)
279 content)
280
280
281 def get_nodes(self, path):
281 def get_nodes(self, path):
282 """
282 """
283 Returns combined ``DirNode`` and ``FileNode`` objects list representing
283 Returns combined ``DirNode`` and ``FileNode`` objects list representing
284 state of commit at the given ``path``. If node at the given ``path``
284 state of commit at the given ``path``. If node at the given ``path``
285 is not instance of ``DirNode``, CommitError would be raised.
285 is not instance of ``DirNode``, CommitError would be raised.
286 """
286 """
287
287
288 if self._get_kind(path) != NodeKind.DIR:
288 if self._get_kind(path) != NodeKind.DIR:
289 raise CommitError(
289 raise CommitError(
290 "Directory does not exist for idx %s at '%s'" % (self.raw_id, path))
290 "Directory does not exist for idx %s at '%s'" % (self.raw_id, path))
291 path = self._fix_path(path)
291 path = self._fix_path(path)
292
292
293 filenodes = [
293 filenodes = [
294 FileNode(f, commit=self) for f in self._file_paths
294 FileNode(f, commit=self) for f in self._file_paths
295 if os.path.dirname(f) == path]
295 if os.path.dirname(f) == path]
296 # TODO: johbo: Check if this can be done in a more obvious way
296 # TODO: johbo: Check if this can be done in a more obvious way
297 dirs = path == '' and '' or [
297 dirs = path == '' and '' or [
298 d for d in self._dir_paths
298 d for d in self._dir_paths
299 if d and vcspath.dirname(d) == path]
299 if d and vcspath.dirname(d) == path]
300 dirnodes = [
300 dirnodes = [
301 DirNode(d, commit=self) for d in dirs
301 DirNode(d, commit=self) for d in dirs
302 if os.path.dirname(d) == path]
302 if os.path.dirname(d) == path]
303
303
304 alias = self.repository.alias
304 alias = self.repository.alias
305 for k, vals in self._submodules.iteritems():
305 for k, vals in self._submodules.iteritems():
306 if vcspath.dirname(k) == path:
306 if vcspath.dirname(k) == path:
307 loc = vals[0]
307 loc = vals[0]
308 commit = vals[1]
308 commit = vals[1]
309 dirnodes.append(SubModuleNode(k, url=loc, commit=commit, alias=alias))
309 dirnodes.append(SubModuleNode(k, url=loc, commit=commit, alias=alias))
310
310
311 nodes = dirnodes + filenodes
311 nodes = dirnodes + filenodes
312 for node in nodes:
312 for node in nodes:
313 if node.path not in self.nodes:
313 if node.path not in self.nodes:
314 self.nodes[node.path] = node
314 self.nodes[node.path] = node
315 nodes.sort()
315 nodes.sort()
316
316
317 return nodes
317 return nodes
318
318
319 def get_node(self, path, pre_load=None):
319 def get_node(self, path, pre_load=None):
320 """
320 """
321 Returns `Node` object from the given `path`. If there is no node at
321 Returns `Node` object from the given `path`. If there is no node at
322 the given `path`, `NodeDoesNotExistError` would be raised.
322 the given `path`, `NodeDoesNotExistError` would be raised.
323 """
323 """
324 path = self._fix_path(path)
324 path = self._fix_path(path)
325
325
326 if path not in self.nodes:
326 if path not in self.nodes:
327 if path in self._file_paths:
327 if path in self._file_paths:
328 node = FileNode(path, commit=self, pre_load=pre_load)
328 node = FileNode(path, commit=self, pre_load=pre_load)
329 elif path in self._dir_paths:
329 elif path in self._dir_paths:
330 if path == '':
330 if path == '':
331 node = RootNode(commit=self)
331 node = RootNode(commit=self)
332 else:
332 else:
333 node = DirNode(path, commit=self)
333 node = DirNode(path, commit=self)
334 else:
334 else:
335 raise self.no_node_at_path(path)
335 raise self.no_node_at_path(path)
336
336
337 # cache node
337 # cache node
338 self.nodes[path] = node
338 self.nodes[path] = node
339 return self.nodes[path]
339 return self.nodes[path]
340
340
341 def get_largefile_node(self, path):
341 def get_largefile_node(self, path):
342 pointer_spec = self._remote.is_large_file(self.raw_id, path)
342 pointer_spec = self._remote.is_large_file(self.raw_id, path)
343 if pointer_spec:
343 if pointer_spec:
344 # content of that file regular FileNode is the hash of largefile
344 # content of that file regular FileNode is the hash of largefile
345 file_id = self.get_file_content(path).strip()
345 file_id = self.get_file_content(path).strip()
346
346
347 if self._remote.in_largefiles_store(file_id):
347 if self._remote.in_largefiles_store(file_id):
348 lf_path = self._remote.store_path(file_id)
348 lf_path = self._remote.store_path(file_id)
349 return LargeFileNode(lf_path, commit=self, org_path=path)
349 return LargeFileNode(lf_path, commit=self, org_path=path)
350 elif self._remote.in_user_cache(file_id):
350 elif self._remote.in_user_cache(file_id):
351 lf_path = self._remote.store_path(file_id)
351 lf_path = self._remote.store_path(file_id)
352 self._remote.link(file_id, path)
352 self._remote.link(file_id, path)
353 return LargeFileNode(lf_path, commit=self, org_path=path)
353 return LargeFileNode(lf_path, commit=self, org_path=path)
354
354
355 @LazyProperty
355 @LazyProperty
356 def _submodules(self):
356 def _submodules(self):
357 """
357 """
358 Returns a dictionary with submodule information from substate file
358 Returns a dictionary with submodule information from substate file
359 of hg repository.
359 of hg repository.
360 """
360 """
361 return self._remote.ctx_substate(self.raw_id)
361 return self._remote.ctx_substate(self.raw_id)
362
362
363 @LazyProperty
363 @LazyProperty
364 def affected_files(self):
364 def affected_files(self):
365 """
365 """
366 Gets a fast accessible file changes for given commit
366 Gets a fast accessible file changes for given commit
367 """
367 """
368 return self._remote.ctx_files(self.raw_id)
368 return self._remote.ctx_files(self.raw_id)
369
369
370 @property
370 @property
371 def added(self):
371 def added(self):
372 """
372 """
373 Returns list of added ``FileNode`` objects.
373 Returns list of added ``FileNode`` objects.
374 """
374 """
375 return AddedFileNodesGenerator([n for n in self.status[1]], self)
375 return AddedFileNodesGenerator([n for n in self.status[1]], self)
376
376
377 @property
377 @property
378 def changed(self):
378 def changed(self):
379 """
379 """
380 Returns list of modified ``FileNode`` objects.
380 Returns list of modified ``FileNode`` objects.
381 """
381 """
382 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
382 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
383
383
384 @property
384 @property
385 def removed(self):
385 def removed(self):
386 """
386 """
387 Returns list of removed ``FileNode`` objects.
387 Returns list of removed ``FileNode`` objects.
388 """
388 """
389 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
389 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
General Comments 0
You need to be logged in to leave comments. Login now