##// END OF EJS Templates
Added children function for VCS for mercurial backend
marcink -
r2983:6d7f8856 beta
parent child Browse files
Show More
@@ -1,965 +1,972 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 vcs.backends.base
3 vcs.backends.base
4 ~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~
5
5
6 Base for all available scm backends
6 Base for all available scm backends
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
11
12
12
13 from itertools import chain
13 from itertools import chain
14 from rhodecode.lib.vcs.utils import author_name, author_email
14 from rhodecode.lib.vcs.utils import author_name, author_email
15 from rhodecode.lib.vcs.utils.lazy import LazyProperty
15 from rhodecode.lib.vcs.utils.lazy import LazyProperty
16 from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs
16 from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs
17 from rhodecode.lib.vcs.conf import settings
17 from rhodecode.lib.vcs.conf import settings
18
18
19 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
19 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
20 NodeAlreadyAddedError, NodeAlreadyChangedError, NodeAlreadyExistsError, \
20 NodeAlreadyAddedError, NodeAlreadyChangedError, NodeAlreadyExistsError, \
21 NodeAlreadyRemovedError, NodeDoesNotExistError, NodeNotChangedError, \
21 NodeAlreadyRemovedError, NodeDoesNotExistError, NodeNotChangedError, \
22 RepositoryError
22 RepositoryError
23
23
24
24
25 class BaseRepository(object):
25 class BaseRepository(object):
26 """
26 """
27 Base Repository for final backends
27 Base Repository for final backends
28
28
29 **Attributes**
29 **Attributes**
30
30
31 ``DEFAULT_BRANCH_NAME``
31 ``DEFAULT_BRANCH_NAME``
32 name of default branch (i.e. "trunk" for svn, "master" for git etc.
32 name of default branch (i.e. "trunk" for svn, "master" for git etc.
33
33
34 ``scm``
34 ``scm``
35 alias of scm, i.e. *git* or *hg*
35 alias of scm, i.e. *git* or *hg*
36
36
37 ``repo``
37 ``repo``
38 object from external api
38 object from external api
39
39
40 ``revisions``
40 ``revisions``
41 list of all available revisions' ids, in ascending order
41 list of all available revisions' ids, in ascending order
42
42
43 ``changesets``
43 ``changesets``
44 storage dict caching returned changesets
44 storage dict caching returned changesets
45
45
46 ``path``
46 ``path``
47 absolute path to the repository
47 absolute path to the repository
48
48
49 ``branches``
49 ``branches``
50 branches as list of changesets
50 branches as list of changesets
51
51
52 ``tags``
52 ``tags``
53 tags as list of changesets
53 tags as list of changesets
54 """
54 """
55 scm = None
55 scm = None
56 DEFAULT_BRANCH_NAME = None
56 DEFAULT_BRANCH_NAME = None
57 EMPTY_CHANGESET = '0' * 40
57 EMPTY_CHANGESET = '0' * 40
58
58
59 def __init__(self, repo_path, create=False, **kwargs):
59 def __init__(self, repo_path, create=False, **kwargs):
60 """
60 """
61 Initializes repository. Raises RepositoryError if repository could
61 Initializes repository. Raises RepositoryError if repository could
62 not be find at the given ``repo_path`` or directory at ``repo_path``
62 not be find at the given ``repo_path`` or directory at ``repo_path``
63 exists and ``create`` is set to True.
63 exists and ``create`` is set to True.
64
64
65 :param repo_path: local path of the repository
65 :param repo_path: local path of the repository
66 :param create=False: if set to True, would try to craete repository.
66 :param create=False: if set to True, would try to craete repository.
67 :param src_url=None: if set, should be proper url from which repository
67 :param src_url=None: if set, should be proper url from which repository
68 would be cloned; requires ``create`` parameter to be set to True -
68 would be cloned; requires ``create`` parameter to be set to True -
69 raises RepositoryError if src_url is set and create evaluates to
69 raises RepositoryError if src_url is set and create evaluates to
70 False
70 False
71 """
71 """
72 raise NotImplementedError
72 raise NotImplementedError
73
73
74 def __str__(self):
74 def __str__(self):
75 return '<%s at %s>' % (self.__class__.__name__, self.path)
75 return '<%s at %s>' % (self.__class__.__name__, self.path)
76
76
77 def __repr__(self):
77 def __repr__(self):
78 return self.__str__()
78 return self.__str__()
79
79
80 def __len__(self):
80 def __len__(self):
81 return self.count()
81 return self.count()
82
82
83 @LazyProperty
83 @LazyProperty
84 def alias(self):
84 def alias(self):
85 for k, v in settings.BACKENDS.items():
85 for k, v in settings.BACKENDS.items():
86 if v.split('.')[-1] == str(self.__class__.__name__):
86 if v.split('.')[-1] == str(self.__class__.__name__):
87 return k
87 return k
88
88
89 @LazyProperty
89 @LazyProperty
90 def name(self):
90 def name(self):
91 raise NotImplementedError
91 raise NotImplementedError
92
92
93 @LazyProperty
93 @LazyProperty
94 def owner(self):
94 def owner(self):
95 raise NotImplementedError
95 raise NotImplementedError
96
96
97 @LazyProperty
97 @LazyProperty
98 def description(self):
98 def description(self):
99 raise NotImplementedError
99 raise NotImplementedError
100
100
101 @LazyProperty
101 @LazyProperty
102 def size(self):
102 def size(self):
103 """
103 """
104 Returns combined size in bytes for all repository files
104 Returns combined size in bytes for all repository files
105 """
105 """
106
106
107 size = 0
107 size = 0
108 try:
108 try:
109 tip = self.get_changeset()
109 tip = self.get_changeset()
110 for topnode, dirs, files in tip.walk('/'):
110 for topnode, dirs, files in tip.walk('/'):
111 for f in files:
111 for f in files:
112 size += tip.get_file_size(f.path)
112 size += tip.get_file_size(f.path)
113 for dir in dirs:
113 for dir in dirs:
114 for f in files:
114 for f in files:
115 size += tip.get_file_size(f.path)
115 size += tip.get_file_size(f.path)
116
116
117 except RepositoryError, e:
117 except RepositoryError, e:
118 pass
118 pass
119 return size
119 return size
120
120
121 def is_valid(self):
121 def is_valid(self):
122 """
122 """
123 Validates repository.
123 Validates repository.
124 """
124 """
125 raise NotImplementedError
125 raise NotImplementedError
126
126
127 def get_last_change(self):
127 def get_last_change(self):
128 self.get_changesets()
128 self.get_changesets()
129
129
130 #==========================================================================
130 #==========================================================================
131 # CHANGESETS
131 # CHANGESETS
132 #==========================================================================
132 #==========================================================================
133
133
134 def get_changeset(self, revision=None):
134 def get_changeset(self, revision=None):
135 """
135 """
136 Returns instance of ``Changeset`` class. If ``revision`` is None, most
136 Returns instance of ``Changeset`` class. If ``revision`` is None, most
137 recent changeset is returned.
137 recent changeset is returned.
138
138
139 :raises ``EmptyRepositoryError``: if there are no revisions
139 :raises ``EmptyRepositoryError``: if there are no revisions
140 """
140 """
141 raise NotImplementedError
141 raise NotImplementedError
142
142
143 def __iter__(self):
143 def __iter__(self):
144 """
144 """
145 Allows Repository objects to be iterated.
145 Allows Repository objects to be iterated.
146
146
147 *Requires* implementation of ``__getitem__`` method.
147 *Requires* implementation of ``__getitem__`` method.
148 """
148 """
149 for revision in self.revisions:
149 for revision in self.revisions:
150 yield self.get_changeset(revision)
150 yield self.get_changeset(revision)
151
151
152 def get_changesets(self, start=None, end=None, start_date=None,
152 def get_changesets(self, start=None, end=None, start_date=None,
153 end_date=None, branch_name=None, reverse=False):
153 end_date=None, branch_name=None, reverse=False):
154 """
154 """
155 Returns iterator of ``MercurialChangeset`` objects from start to end
155 Returns iterator of ``MercurialChangeset`` objects from start to end
156 not inclusive This should behave just like a list, ie. end is not
156 not inclusive This should behave just like a list, ie. end is not
157 inclusive
157 inclusive
158
158
159 :param start: None or str
159 :param start: None or str
160 :param end: None or str
160 :param end: None or str
161 :param start_date:
161 :param start_date:
162 :param end_date:
162 :param end_date:
163 :param branch_name:
163 :param branch_name:
164 :param reversed:
164 :param reversed:
165 """
165 """
166 raise NotImplementedError
166 raise NotImplementedError
167
167
168 def __getslice__(self, i, j):
168 def __getslice__(self, i, j):
169 """
169 """
170 Returns a iterator of sliced repository
170 Returns a iterator of sliced repository
171 """
171 """
172 for rev in self.revisions[i:j]:
172 for rev in self.revisions[i:j]:
173 yield self.get_changeset(rev)
173 yield self.get_changeset(rev)
174
174
175 def __getitem__(self, key):
175 def __getitem__(self, key):
176 return self.get_changeset(key)
176 return self.get_changeset(key)
177
177
178 def count(self):
178 def count(self):
179 return len(self.revisions)
179 return len(self.revisions)
180
180
181 def tag(self, name, user, revision=None, message=None, date=None, **opts):
181 def tag(self, name, user, revision=None, message=None, date=None, **opts):
182 """
182 """
183 Creates and returns a tag for the given ``revision``.
183 Creates and returns a tag for the given ``revision``.
184
184
185 :param name: name for new tag
185 :param name: name for new tag
186 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
186 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
187 :param revision: changeset id for which new tag would be created
187 :param revision: changeset id for which new tag would be created
188 :param message: message of the tag's commit
188 :param message: message of the tag's commit
189 :param date: date of tag's commit
189 :param date: date of tag's commit
190
190
191 :raises TagAlreadyExistError: if tag with same name already exists
191 :raises TagAlreadyExistError: if tag with same name already exists
192 """
192 """
193 raise NotImplementedError
193 raise NotImplementedError
194
194
195 def remove_tag(self, name, user, message=None, date=None):
195 def remove_tag(self, name, user, message=None, date=None):
196 """
196 """
197 Removes tag with the given ``name``.
197 Removes tag with the given ``name``.
198
198
199 :param name: name of the tag to be removed
199 :param name: name of the tag to be removed
200 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
200 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
201 :param message: message of the tag's removal commit
201 :param message: message of the tag's removal commit
202 :param date: date of tag's removal commit
202 :param date: date of tag's removal commit
203
203
204 :raises TagDoesNotExistError: if tag with given name does not exists
204 :raises TagDoesNotExistError: if tag with given name does not exists
205 """
205 """
206 raise NotImplementedError
206 raise NotImplementedError
207
207
208 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
208 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
209 context=3):
209 context=3):
210 """
210 """
211 Returns (git like) *diff*, as plain text. Shows changes introduced by
211 Returns (git like) *diff*, as plain text. Shows changes introduced by
212 ``rev2`` since ``rev1``.
212 ``rev2`` since ``rev1``.
213
213
214 :param rev1: Entry point from which diff is shown. Can be
214 :param rev1: Entry point from which diff is shown. Can be
215 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
215 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
216 the changes since empty state of the repository until ``rev2``
216 the changes since empty state of the repository until ``rev2``
217 :param rev2: Until which revision changes should be shown.
217 :param rev2: Until which revision changes should be shown.
218 :param ignore_whitespace: If set to ``True``, would not show whitespace
218 :param ignore_whitespace: If set to ``True``, would not show whitespace
219 changes. Defaults to ``False``.
219 changes. Defaults to ``False``.
220 :param context: How many lines before/after changed lines should be
220 :param context: How many lines before/after changed lines should be
221 shown. Defaults to ``3``.
221 shown. Defaults to ``3``.
222 """
222 """
223 raise NotImplementedError
223 raise NotImplementedError
224
224
225 # ========== #
225 # ========== #
226 # COMMIT API #
226 # COMMIT API #
227 # ========== #
227 # ========== #
228
228
229 @LazyProperty
229 @LazyProperty
230 def in_memory_changeset(self):
230 def in_memory_changeset(self):
231 """
231 """
232 Returns ``InMemoryChangeset`` object for this repository.
232 Returns ``InMemoryChangeset`` object for this repository.
233 """
233 """
234 raise NotImplementedError
234 raise NotImplementedError
235
235
236 def add(self, filenode, **kwargs):
236 def add(self, filenode, **kwargs):
237 """
237 """
238 Commit api function that will add given ``FileNode`` into this
238 Commit api function that will add given ``FileNode`` into this
239 repository.
239 repository.
240
240
241 :raises ``NodeAlreadyExistsError``: if there is a file with same path
241 :raises ``NodeAlreadyExistsError``: if there is a file with same path
242 already in repository
242 already in repository
243 :raises ``NodeAlreadyAddedError``: if given node is already marked as
243 :raises ``NodeAlreadyAddedError``: if given node is already marked as
244 *added*
244 *added*
245 """
245 """
246 raise NotImplementedError
246 raise NotImplementedError
247
247
248 def remove(self, filenode, **kwargs):
248 def remove(self, filenode, **kwargs):
249 """
249 """
250 Commit api function that will remove given ``FileNode`` into this
250 Commit api function that will remove given ``FileNode`` into this
251 repository.
251 repository.
252
252
253 :raises ``EmptyRepositoryError``: if there are no changesets yet
253 :raises ``EmptyRepositoryError``: if there are no changesets yet
254 :raises ``NodeDoesNotExistError``: if there is no file with given path
254 :raises ``NodeDoesNotExistError``: if there is no file with given path
255 """
255 """
256 raise NotImplementedError
256 raise NotImplementedError
257
257
258 def commit(self, message, **kwargs):
258 def commit(self, message, **kwargs):
259 """
259 """
260 Persists current changes made on this repository and returns newly
260 Persists current changes made on this repository and returns newly
261 created changeset.
261 created changeset.
262
262
263 :raises ``NothingChangedError``: if no changes has been made
263 :raises ``NothingChangedError``: if no changes has been made
264 """
264 """
265 raise NotImplementedError
265 raise NotImplementedError
266
266
267 def get_state(self):
267 def get_state(self):
268 """
268 """
269 Returns dictionary with ``added``, ``changed`` and ``removed`` lists
269 Returns dictionary with ``added``, ``changed`` and ``removed`` lists
270 containing ``FileNode`` objects.
270 containing ``FileNode`` objects.
271 """
271 """
272 raise NotImplementedError
272 raise NotImplementedError
273
273
274 def get_config_value(self, section, name, config_file=None):
274 def get_config_value(self, section, name, config_file=None):
275 """
275 """
276 Returns configuration value for a given [``section``] and ``name``.
276 Returns configuration value for a given [``section``] and ``name``.
277
277
278 :param section: Section we want to retrieve value from
278 :param section: Section we want to retrieve value from
279 :param name: Name of configuration we want to retrieve
279 :param name: Name of configuration we want to retrieve
280 :param config_file: A path to file which should be used to retrieve
280 :param config_file: A path to file which should be used to retrieve
281 configuration from (might also be a list of file paths)
281 configuration from (might also be a list of file paths)
282 """
282 """
283 raise NotImplementedError
283 raise NotImplementedError
284
284
285 def get_user_name(self, config_file=None):
285 def get_user_name(self, config_file=None):
286 """
286 """
287 Returns user's name from global configuration file.
287 Returns user's name from global configuration file.
288
288
289 :param config_file: A path to file which should be used to retrieve
289 :param config_file: A path to file which should be used to retrieve
290 configuration from (might also be a list of file paths)
290 configuration from (might also be a list of file paths)
291 """
291 """
292 raise NotImplementedError
292 raise NotImplementedError
293
293
294 def get_user_email(self, config_file=None):
294 def get_user_email(self, config_file=None):
295 """
295 """
296 Returns user's email from global configuration file.
296 Returns user's email from global configuration file.
297
297
298 :param config_file: A path to file which should be used to retrieve
298 :param config_file: A path to file which should be used to retrieve
299 configuration from (might also be a list of file paths)
299 configuration from (might also be a list of file paths)
300 """
300 """
301 raise NotImplementedError
301 raise NotImplementedError
302
302
303 # =========== #
303 # =========== #
304 # WORKDIR API #
304 # WORKDIR API #
305 # =========== #
305 # =========== #
306
306
307 @LazyProperty
307 @LazyProperty
308 def workdir(self):
308 def workdir(self):
309 """
309 """
310 Returns ``Workdir`` instance for this repository.
310 Returns ``Workdir`` instance for this repository.
311 """
311 """
312 raise NotImplementedError
312 raise NotImplementedError
313
313
314
314
315 class BaseChangeset(object):
315 class BaseChangeset(object):
316 """
316 """
317 Each backend should implement it's changeset representation.
317 Each backend should implement it's changeset representation.
318
318
319 **Attributes**
319 **Attributes**
320
320
321 ``repository``
321 ``repository``
322 repository object within which changeset exists
322 repository object within which changeset exists
323
323
324 ``id``
324 ``id``
325 may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
325 may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
326
326
327 ``raw_id``
327 ``raw_id``
328 raw changeset representation (i.e. full 40 length sha for git
328 raw changeset representation (i.e. full 40 length sha for git
329 backend)
329 backend)
330
330
331 ``short_id``
331 ``short_id``
332 shortened (if apply) version of ``raw_id``; it would be simple
332 shortened (if apply) version of ``raw_id``; it would be simple
333 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
333 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
334 as ``raw_id`` for subversion
334 as ``raw_id`` for subversion
335
335
336 ``revision``
336 ``revision``
337 revision number as integer
337 revision number as integer
338
338
339 ``files``
339 ``files``
340 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
340 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
341
341
342 ``dirs``
342 ``dirs``
343 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
343 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
344
344
345 ``nodes``
345 ``nodes``
346 combined list of ``Node`` objects
346 combined list of ``Node`` objects
347
347
348 ``author``
348 ``author``
349 author of the changeset, as unicode
349 author of the changeset, as unicode
350
350
351 ``message``
351 ``message``
352 message of the changeset, as unicode
352 message of the changeset, as unicode
353
353
354 ``parents``
354 ``parents``
355 list of parent changesets
355 list of parent changesets
356
356
357 ``last``
357 ``last``
358 ``True`` if this is last changeset in repository, ``False``
358 ``True`` if this is last changeset in repository, ``False``
359 otherwise; trying to access this attribute while there is no
359 otherwise; trying to access this attribute while there is no
360 changesets would raise ``EmptyRepositoryError``
360 changesets would raise ``EmptyRepositoryError``
361 """
361 """
362 def __str__(self):
362 def __str__(self):
363 return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
363 return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
364 self.short_id)
364 self.short_id)
365
365
366 def __repr__(self):
366 def __repr__(self):
367 return self.__str__()
367 return self.__str__()
368
368
369 def __unicode__(self):
369 def __unicode__(self):
370 return u'%s:%s' % (self.revision, self.short_id)
370 return u'%s:%s' % (self.revision, self.short_id)
371
371
372 def __eq__(self, other):
372 def __eq__(self, other):
373 return self.raw_id == other.raw_id
373 return self.raw_id == other.raw_id
374
374
375 def __json__(self):
375 def __json__(self):
376 return dict(
376 return dict(
377 short_id=self.short_id,
377 short_id=self.short_id,
378 raw_id=self.raw_id,
378 raw_id=self.raw_id,
379 message=self.message,
379 message=self.message,
380 date=self.date,
380 date=self.date,
381 author=self.author,
381 author=self.author,
382 )
382 )
383
383
384 @LazyProperty
384 @LazyProperty
385 def last(self):
385 def last(self):
386 if self.repository is None:
386 if self.repository is None:
387 raise ChangesetError("Cannot check if it's most recent revision")
387 raise ChangesetError("Cannot check if it's most recent revision")
388 return self.raw_id == self.repository.revisions[-1]
388 return self.raw_id == self.repository.revisions[-1]
389
389
390 @LazyProperty
390 @LazyProperty
391 def parents(self):
391 def parents(self):
392 """
392 """
393 Returns list of parents changesets.
393 Returns list of parents changesets.
394 """
394 """
395 raise NotImplementedError
395 raise NotImplementedError
396
396
397 @LazyProperty
397 @LazyProperty
398 def children(self):
399 """
400 Returns list of children changesets.
401 """
402 raise NotImplementedError
403
404 @LazyProperty
398 def id(self):
405 def id(self):
399 """
406 """
400 Returns string identifying this changeset.
407 Returns string identifying this changeset.
401 """
408 """
402 raise NotImplementedError
409 raise NotImplementedError
403
410
404 @LazyProperty
411 @LazyProperty
405 def raw_id(self):
412 def raw_id(self):
406 """
413 """
407 Returns raw string identifying this changeset.
414 Returns raw string identifying this changeset.
408 """
415 """
409 raise NotImplementedError
416 raise NotImplementedError
410
417
411 @LazyProperty
418 @LazyProperty
412 def short_id(self):
419 def short_id(self):
413 """
420 """
414 Returns shortened version of ``raw_id`` attribute, as string,
421 Returns shortened version of ``raw_id`` attribute, as string,
415 identifying this changeset, useful for web representation.
422 identifying this changeset, useful for web representation.
416 """
423 """
417 raise NotImplementedError
424 raise NotImplementedError
418
425
419 @LazyProperty
426 @LazyProperty
420 def revision(self):
427 def revision(self):
421 """
428 """
422 Returns integer identifying this changeset.
429 Returns integer identifying this changeset.
423
430
424 """
431 """
425 raise NotImplementedError
432 raise NotImplementedError
426
433
427 @LazyProperty
434 @LazyProperty
428 def author(self):
435 def author(self):
429 """
436 """
430 Returns Author for given commit
437 Returns Author for given commit
431 """
438 """
432
439
433 raise NotImplementedError
440 raise NotImplementedError
434
441
435 @LazyProperty
442 @LazyProperty
436 def author_name(self):
443 def author_name(self):
437 """
444 """
438 Returns Author name for given commit
445 Returns Author name for given commit
439 """
446 """
440
447
441 return author_name(self.author)
448 return author_name(self.author)
442
449
443 @LazyProperty
450 @LazyProperty
444 def author_email(self):
451 def author_email(self):
445 """
452 """
446 Returns Author email address for given commit
453 Returns Author email address for given commit
447 """
454 """
448
455
449 return author_email(self.author)
456 return author_email(self.author)
450
457
451 def get_file_mode(self, path):
458 def get_file_mode(self, path):
452 """
459 """
453 Returns stat mode of the file at the given ``path``.
460 Returns stat mode of the file at the given ``path``.
454 """
461 """
455 raise NotImplementedError
462 raise NotImplementedError
456
463
457 def get_file_content(self, path):
464 def get_file_content(self, path):
458 """
465 """
459 Returns content of the file at the given ``path``.
466 Returns content of the file at the given ``path``.
460 """
467 """
461 raise NotImplementedError
468 raise NotImplementedError
462
469
463 def get_file_size(self, path):
470 def get_file_size(self, path):
464 """
471 """
465 Returns size of the file at the given ``path``.
472 Returns size of the file at the given ``path``.
466 """
473 """
467 raise NotImplementedError
474 raise NotImplementedError
468
475
469 def get_file_changeset(self, path):
476 def get_file_changeset(self, path):
470 """
477 """
471 Returns last commit of the file at the given ``path``.
478 Returns last commit of the file at the given ``path``.
472 """
479 """
473 raise NotImplementedError
480 raise NotImplementedError
474
481
475 def get_file_history(self, path):
482 def get_file_history(self, path):
476 """
483 """
477 Returns history of file as reversed list of ``Changeset`` objects for
484 Returns history of file as reversed list of ``Changeset`` objects for
478 which file at given ``path`` has been modified.
485 which file at given ``path`` has been modified.
479 """
486 """
480 raise NotImplementedError
487 raise NotImplementedError
481
488
482 def get_nodes(self, path):
489 def get_nodes(self, path):
483 """
490 """
484 Returns combined ``DirNode`` and ``FileNode`` objects list representing
491 Returns combined ``DirNode`` and ``FileNode`` objects list representing
485 state of changeset at the given ``path``.
492 state of changeset at the given ``path``.
486
493
487 :raises ``ChangesetError``: if node at the given ``path`` is not
494 :raises ``ChangesetError``: if node at the given ``path`` is not
488 instance of ``DirNode``
495 instance of ``DirNode``
489 """
496 """
490 raise NotImplementedError
497 raise NotImplementedError
491
498
492 def get_node(self, path):
499 def get_node(self, path):
493 """
500 """
494 Returns ``Node`` object from the given ``path``.
501 Returns ``Node`` object from the given ``path``.
495
502
496 :raises ``NodeDoesNotExistError``: if there is no node at the given
503 :raises ``NodeDoesNotExistError``: if there is no node at the given
497 ``path``
504 ``path``
498 """
505 """
499 raise NotImplementedError
506 raise NotImplementedError
500
507
501 def fill_archive(self, stream=None, kind='tgz', prefix=None):
508 def fill_archive(self, stream=None, kind='tgz', prefix=None):
502 """
509 """
503 Fills up given stream.
510 Fills up given stream.
504
511
505 :param stream: file like object.
512 :param stream: file like object.
506 :param kind: one of following: ``zip``, ``tar``, ``tgz``
513 :param kind: one of following: ``zip``, ``tar``, ``tgz``
507 or ``tbz2``. Default: ``tgz``.
514 or ``tbz2``. Default: ``tgz``.
508 :param prefix: name of root directory in archive.
515 :param prefix: name of root directory in archive.
509 Default is repository name and changeset's raw_id joined with dash.
516 Default is repository name and changeset's raw_id joined with dash.
510
517
511 repo-tip.<kind>
518 repo-tip.<kind>
512 """
519 """
513
520
514 raise NotImplementedError
521 raise NotImplementedError
515
522
516 def get_chunked_archive(self, **kwargs):
523 def get_chunked_archive(self, **kwargs):
517 """
524 """
518 Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
525 Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
519
526
520 :param chunk_size: extra parameter which controls size of returned
527 :param chunk_size: extra parameter which controls size of returned
521 chunks. Default:8k.
528 chunks. Default:8k.
522 """
529 """
523
530
524 chunk_size = kwargs.pop('chunk_size', 8192)
531 chunk_size = kwargs.pop('chunk_size', 8192)
525 stream = kwargs.get('stream')
532 stream = kwargs.get('stream')
526 self.fill_archive(**kwargs)
533 self.fill_archive(**kwargs)
527 while True:
534 while True:
528 data = stream.read(chunk_size)
535 data = stream.read(chunk_size)
529 if not data:
536 if not data:
530 break
537 break
531 yield data
538 yield data
532
539
533 @LazyProperty
540 @LazyProperty
534 def root(self):
541 def root(self):
535 """
542 """
536 Returns ``RootNode`` object for this changeset.
543 Returns ``RootNode`` object for this changeset.
537 """
544 """
538 return self.get_node('')
545 return self.get_node('')
539
546
540 def next(self, branch=None):
547 def next(self, branch=None):
541 """
548 """
542 Returns next changeset from current, if branch is gives it will return
549 Returns next changeset from current, if branch is gives it will return
543 next changeset belonging to this branch
550 next changeset belonging to this branch
544
551
545 :param branch: show changesets within the given named branch
552 :param branch: show changesets within the given named branch
546 """
553 """
547 raise NotImplementedError
554 raise NotImplementedError
548
555
549 def prev(self, branch=None):
556 def prev(self, branch=None):
550 """
557 """
551 Returns previous changeset from current, if branch is gives it will
558 Returns previous changeset from current, if branch is gives it will
552 return previous changeset belonging to this branch
559 return previous changeset belonging to this branch
553
560
554 :param branch: show changesets within the given named branch
561 :param branch: show changesets within the given named branch
555 """
562 """
556 raise NotImplementedError
563 raise NotImplementedError
557
564
558 @LazyProperty
565 @LazyProperty
559 def added(self):
566 def added(self):
560 """
567 """
561 Returns list of added ``FileNode`` objects.
568 Returns list of added ``FileNode`` objects.
562 """
569 """
563 raise NotImplementedError
570 raise NotImplementedError
564
571
565 @LazyProperty
572 @LazyProperty
566 def changed(self):
573 def changed(self):
567 """
574 """
568 Returns list of modified ``FileNode`` objects.
575 Returns list of modified ``FileNode`` objects.
569 """
576 """
570 raise NotImplementedError
577 raise NotImplementedError
571
578
572 @LazyProperty
579 @LazyProperty
573 def removed(self):
580 def removed(self):
574 """
581 """
575 Returns list of removed ``FileNode`` objects.
582 Returns list of removed ``FileNode`` objects.
576 """
583 """
577 raise NotImplementedError
584 raise NotImplementedError
578
585
579 @LazyProperty
586 @LazyProperty
580 def size(self):
587 def size(self):
581 """
588 """
582 Returns total number of bytes from contents of all filenodes.
589 Returns total number of bytes from contents of all filenodes.
583 """
590 """
584 return sum((node.size for node in self.get_filenodes_generator()))
591 return sum((node.size for node in self.get_filenodes_generator()))
585
592
586 def walk(self, topurl=''):
593 def walk(self, topurl=''):
587 """
594 """
588 Similar to os.walk method. Insted of filesystem it walks through
595 Similar to os.walk method. Insted of filesystem it walks through
589 changeset starting at given ``topurl``. Returns generator of tuples
596 changeset starting at given ``topurl``. Returns generator of tuples
590 (topnode, dirnodes, filenodes).
597 (topnode, dirnodes, filenodes).
591 """
598 """
592 topnode = self.get_node(topurl)
599 topnode = self.get_node(topurl)
593 yield (topnode, topnode.dirs, topnode.files)
600 yield (topnode, topnode.dirs, topnode.files)
594 for dirnode in topnode.dirs:
601 for dirnode in topnode.dirs:
595 for tup in self.walk(dirnode.path):
602 for tup in self.walk(dirnode.path):
596 yield tup
603 yield tup
597
604
598 def get_filenodes_generator(self):
605 def get_filenodes_generator(self):
599 """
606 """
600 Returns generator that yields *all* file nodes.
607 Returns generator that yields *all* file nodes.
601 """
608 """
602 for topnode, dirs, files in self.walk():
609 for topnode, dirs, files in self.walk():
603 for node in files:
610 for node in files:
604 yield node
611 yield node
605
612
606 def as_dict(self):
613 def as_dict(self):
607 """
614 """
608 Returns dictionary with changeset's attributes and their values.
615 Returns dictionary with changeset's attributes and their values.
609 """
616 """
610 data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
617 data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
611 'revision', 'date', 'message'])
618 'revision', 'date', 'message'])
612 data['author'] = {'name': self.author_name, 'email': self.author_email}
619 data['author'] = {'name': self.author_name, 'email': self.author_email}
613 data['added'] = [node.path for node in self.added]
620 data['added'] = [node.path for node in self.added]
614 data['changed'] = [node.path for node in self.changed]
621 data['changed'] = [node.path for node in self.changed]
615 data['removed'] = [node.path for node in self.removed]
622 data['removed'] = [node.path for node in self.removed]
616 return data
623 return data
617
624
618
625
619 class BaseWorkdir(object):
626 class BaseWorkdir(object):
620 """
627 """
621 Working directory representation of single repository.
628 Working directory representation of single repository.
622
629
623 :attribute: repository: repository object of working directory
630 :attribute: repository: repository object of working directory
624 """
631 """
625
632
626 def __init__(self, repository):
633 def __init__(self, repository):
627 self.repository = repository
634 self.repository = repository
628
635
629 def get_branch(self):
636 def get_branch(self):
630 """
637 """
631 Returns name of current branch.
638 Returns name of current branch.
632 """
639 """
633 raise NotImplementedError
640 raise NotImplementedError
634
641
635 def get_changeset(self):
642 def get_changeset(self):
636 """
643 """
637 Returns current changeset.
644 Returns current changeset.
638 """
645 """
639 raise NotImplementedError
646 raise NotImplementedError
640
647
641 def get_added(self):
648 def get_added(self):
642 """
649 """
643 Returns list of ``FileNode`` objects marked as *new* in working
650 Returns list of ``FileNode`` objects marked as *new* in working
644 directory.
651 directory.
645 """
652 """
646 raise NotImplementedError
653 raise NotImplementedError
647
654
648 def get_changed(self):
655 def get_changed(self):
649 """
656 """
650 Returns list of ``FileNode`` objects *changed* in working directory.
657 Returns list of ``FileNode`` objects *changed* in working directory.
651 """
658 """
652 raise NotImplementedError
659 raise NotImplementedError
653
660
654 def get_removed(self):
661 def get_removed(self):
655 """
662 """
656 Returns list of ``RemovedFileNode`` objects marked as *removed* in
663 Returns list of ``RemovedFileNode`` objects marked as *removed* in
657 working directory.
664 working directory.
658 """
665 """
659 raise NotImplementedError
666 raise NotImplementedError
660
667
661 def get_untracked(self):
668 def get_untracked(self):
662 """
669 """
663 Returns list of ``FileNode`` objects which are present within working
670 Returns list of ``FileNode`` objects which are present within working
664 directory however are not tracked by repository.
671 directory however are not tracked by repository.
665 """
672 """
666 raise NotImplementedError
673 raise NotImplementedError
667
674
668 def get_status(self):
675 def get_status(self):
669 """
676 """
670 Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
677 Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
671 lists.
678 lists.
672 """
679 """
673 raise NotImplementedError
680 raise NotImplementedError
674
681
675 def commit(self, message, **kwargs):
682 def commit(self, message, **kwargs):
676 """
683 """
677 Commits local (from working directory) changes and returns newly
684 Commits local (from working directory) changes and returns newly
678 created
685 created
679 ``Changeset``. Updates repository's ``revisions`` list.
686 ``Changeset``. Updates repository's ``revisions`` list.
680
687
681 :raises ``CommitError``: if any error occurs while committing
688 :raises ``CommitError``: if any error occurs while committing
682 """
689 """
683 raise NotImplementedError
690 raise NotImplementedError
684
691
685 def update(self, revision=None):
692 def update(self, revision=None):
686 """
693 """
687 Fetches content of the given revision and populates it within working
694 Fetches content of the given revision and populates it within working
688 directory.
695 directory.
689 """
696 """
690 raise NotImplementedError
697 raise NotImplementedError
691
698
692 def checkout_branch(self, branch=None):
699 def checkout_branch(self, branch=None):
693 """
700 """
694 Checks out ``branch`` or the backend's default branch.
701 Checks out ``branch`` or the backend's default branch.
695
702
696 Raises ``BranchDoesNotExistError`` if the branch does not exist.
703 Raises ``BranchDoesNotExistError`` if the branch does not exist.
697 """
704 """
698 raise NotImplementedError
705 raise NotImplementedError
699
706
700
707
701 class BaseInMemoryChangeset(object):
708 class BaseInMemoryChangeset(object):
702 """
709 """
703 Represents differences between repository's state (most recent head) and
710 Represents differences between repository's state (most recent head) and
704 changes made *in place*.
711 changes made *in place*.
705
712
706 **Attributes**
713 **Attributes**
707
714
708 ``repository``
715 ``repository``
709 repository object for this in-memory-changeset
716 repository object for this in-memory-changeset
710
717
711 ``added``
718 ``added``
712 list of ``FileNode`` objects marked as *added*
719 list of ``FileNode`` objects marked as *added*
713
720
714 ``changed``
721 ``changed``
715 list of ``FileNode`` objects marked as *changed*
722 list of ``FileNode`` objects marked as *changed*
716
723
717 ``removed``
724 ``removed``
718 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
725 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
719 *removed*
726 *removed*
720
727
721 ``parents``
728 ``parents``
722 list of ``Changeset`` representing parents of in-memory changeset.
729 list of ``Changeset`` representing parents of in-memory changeset.
723 Should always be 2-element sequence.
730 Should always be 2-element sequence.
724
731
725 """
732 """
726
733
727 def __init__(self, repository):
734 def __init__(self, repository):
728 self.repository = repository
735 self.repository = repository
729 self.added = []
736 self.added = []
730 self.changed = []
737 self.changed = []
731 self.removed = []
738 self.removed = []
732 self.parents = []
739 self.parents = []
733
740
734 def add(self, *filenodes):
741 def add(self, *filenodes):
735 """
742 """
736 Marks given ``FileNode`` objects as *to be committed*.
743 Marks given ``FileNode`` objects as *to be committed*.
737
744
738 :raises ``NodeAlreadyExistsError``: if node with same path exists at
745 :raises ``NodeAlreadyExistsError``: if node with same path exists at
739 latest changeset
746 latest changeset
740 :raises ``NodeAlreadyAddedError``: if node with same path is already
747 :raises ``NodeAlreadyAddedError``: if node with same path is already
741 marked as *added*
748 marked as *added*
742 """
749 """
743 # Check if not already marked as *added* first
750 # Check if not already marked as *added* first
744 for node in filenodes:
751 for node in filenodes:
745 if node.path in (n.path for n in self.added):
752 if node.path in (n.path for n in self.added):
746 raise NodeAlreadyAddedError("Such FileNode %s is already "
753 raise NodeAlreadyAddedError("Such FileNode %s is already "
747 "marked for addition" % node.path)
754 "marked for addition" % node.path)
748 for node in filenodes:
755 for node in filenodes:
749 self.added.append(node)
756 self.added.append(node)
750
757
751 def change(self, *filenodes):
758 def change(self, *filenodes):
752 """
759 """
753 Marks given ``FileNode`` objects to be *changed* in next commit.
760 Marks given ``FileNode`` objects to be *changed* in next commit.
754
761
755 :raises ``EmptyRepositoryError``: if there are no changesets yet
762 :raises ``EmptyRepositoryError``: if there are no changesets yet
756 :raises ``NodeAlreadyExistsError``: if node with same path is already
763 :raises ``NodeAlreadyExistsError``: if node with same path is already
757 marked to be *changed*
764 marked to be *changed*
758 :raises ``NodeAlreadyRemovedError``: if node with same path is already
765 :raises ``NodeAlreadyRemovedError``: if node with same path is already
759 marked to be *removed*
766 marked to be *removed*
760 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
767 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
761 changeset
768 changeset
762 :raises ``NodeNotChangedError``: if node hasn't really be changed
769 :raises ``NodeNotChangedError``: if node hasn't really be changed
763 """
770 """
764 for node in filenodes:
771 for node in filenodes:
765 if node.path in (n.path for n in self.removed):
772 if node.path in (n.path for n in self.removed):
766 raise NodeAlreadyRemovedError("Node at %s is already marked "
773 raise NodeAlreadyRemovedError("Node at %s is already marked "
767 "as removed" % node.path)
774 "as removed" % node.path)
768 try:
775 try:
769 self.repository.get_changeset()
776 self.repository.get_changeset()
770 except EmptyRepositoryError:
777 except EmptyRepositoryError:
771 raise EmptyRepositoryError("Nothing to change - try to *add* new "
778 raise EmptyRepositoryError("Nothing to change - try to *add* new "
772 "nodes rather than changing them")
779 "nodes rather than changing them")
773 for node in filenodes:
780 for node in filenodes:
774 if node.path in (n.path for n in self.changed):
781 if node.path in (n.path for n in self.changed):
775 raise NodeAlreadyChangedError("Node at '%s' is already "
782 raise NodeAlreadyChangedError("Node at '%s' is already "
776 "marked as changed" % node.path)
783 "marked as changed" % node.path)
777 self.changed.append(node)
784 self.changed.append(node)
778
785
779 def remove(self, *filenodes):
786 def remove(self, *filenodes):
780 """
787 """
781 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
788 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
782 *removed* in next commit.
789 *removed* in next commit.
783
790
784 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
791 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
785 be *removed*
792 be *removed*
786 :raises ``NodeAlreadyChangedError``: if node has been already marked to
793 :raises ``NodeAlreadyChangedError``: if node has been already marked to
787 be *changed*
794 be *changed*
788 """
795 """
789 for node in filenodes:
796 for node in filenodes:
790 if node.path in (n.path for n in self.removed):
797 if node.path in (n.path for n in self.removed):
791 raise NodeAlreadyRemovedError("Node is already marked to "
798 raise NodeAlreadyRemovedError("Node is already marked to "
792 "for removal at %s" % node.path)
799 "for removal at %s" % node.path)
793 if node.path in (n.path for n in self.changed):
800 if node.path in (n.path for n in self.changed):
794 raise NodeAlreadyChangedError("Node is already marked to "
801 raise NodeAlreadyChangedError("Node is already marked to "
795 "be changed at %s" % node.path)
802 "be changed at %s" % node.path)
796 # We only mark node as *removed* - real removal is done by
803 # We only mark node as *removed* - real removal is done by
797 # commit method
804 # commit method
798 self.removed.append(node)
805 self.removed.append(node)
799
806
800 def reset(self):
807 def reset(self):
801 """
808 """
802 Resets this instance to initial state (cleans ``added``, ``changed``
809 Resets this instance to initial state (cleans ``added``, ``changed``
803 and ``removed`` lists).
810 and ``removed`` lists).
804 """
811 """
805 self.added = []
812 self.added = []
806 self.changed = []
813 self.changed = []
807 self.removed = []
814 self.removed = []
808 self.parents = []
815 self.parents = []
809
816
810 def get_ipaths(self):
817 def get_ipaths(self):
811 """
818 """
812 Returns generator of paths from nodes marked as added, changed or
819 Returns generator of paths from nodes marked as added, changed or
813 removed.
820 removed.
814 """
821 """
815 for node in chain(self.added, self.changed, self.removed):
822 for node in chain(self.added, self.changed, self.removed):
816 yield node.path
823 yield node.path
817
824
818 def get_paths(self):
825 def get_paths(self):
819 """
826 """
820 Returns list of paths from nodes marked as added, changed or removed.
827 Returns list of paths from nodes marked as added, changed or removed.
821 """
828 """
822 return list(self.get_ipaths())
829 return list(self.get_ipaths())
823
830
824 def check_integrity(self, parents=None):
831 def check_integrity(self, parents=None):
825 """
832 """
826 Checks in-memory changeset's integrity. Also, sets parents if not
833 Checks in-memory changeset's integrity. Also, sets parents if not
827 already set.
834 already set.
828
835
829 :raises CommitError: if any error occurs (i.e.
836 :raises CommitError: if any error occurs (i.e.
830 ``NodeDoesNotExistError``).
837 ``NodeDoesNotExistError``).
831 """
838 """
832 if not self.parents:
839 if not self.parents:
833 parents = parents or []
840 parents = parents or []
834 if len(parents) == 0:
841 if len(parents) == 0:
835 try:
842 try:
836 parents = [self.repository.get_changeset(), None]
843 parents = [self.repository.get_changeset(), None]
837 except EmptyRepositoryError:
844 except EmptyRepositoryError:
838 parents = [None, None]
845 parents = [None, None]
839 elif len(parents) == 1:
846 elif len(parents) == 1:
840 parents += [None]
847 parents += [None]
841 self.parents = parents
848 self.parents = parents
842
849
843 # Local parents, only if not None
850 # Local parents, only if not None
844 parents = [p for p in self.parents if p]
851 parents = [p for p in self.parents if p]
845
852
846 # Check nodes marked as added
853 # Check nodes marked as added
847 for p in parents:
854 for p in parents:
848 for node in self.added:
855 for node in self.added:
849 try:
856 try:
850 p.get_node(node.path)
857 p.get_node(node.path)
851 except NodeDoesNotExistError:
858 except NodeDoesNotExistError:
852 pass
859 pass
853 else:
860 else:
854 raise NodeAlreadyExistsError("Node at %s already exists "
861 raise NodeAlreadyExistsError("Node at %s already exists "
855 "at %s" % (node.path, p))
862 "at %s" % (node.path, p))
856
863
857 # Check nodes marked as changed
864 # Check nodes marked as changed
858 missing = set(self.changed)
865 missing = set(self.changed)
859 not_changed = set(self.changed)
866 not_changed = set(self.changed)
860 if self.changed and not parents:
867 if self.changed and not parents:
861 raise NodeDoesNotExistError(str(self.changed[0].path))
868 raise NodeDoesNotExistError(str(self.changed[0].path))
862 for p in parents:
869 for p in parents:
863 for node in self.changed:
870 for node in self.changed:
864 try:
871 try:
865 old = p.get_node(node.path)
872 old = p.get_node(node.path)
866 missing.remove(node)
873 missing.remove(node)
867 if old.content != node.content:
874 if old.content != node.content:
868 not_changed.remove(node)
875 not_changed.remove(node)
869 except NodeDoesNotExistError:
876 except NodeDoesNotExistError:
870 pass
877 pass
871 if self.changed and missing:
878 if self.changed and missing:
872 raise NodeDoesNotExistError("Node at %s is missing "
879 raise NodeDoesNotExistError("Node at %s is missing "
873 "(parents: %s)" % (node.path, parents))
880 "(parents: %s)" % (node.path, parents))
874
881
875 if self.changed and not_changed:
882 if self.changed and not_changed:
876 raise NodeNotChangedError("Node at %s wasn't actually changed "
883 raise NodeNotChangedError("Node at %s wasn't actually changed "
877 "since parents' changesets: %s" % (not_changed.pop().path,
884 "since parents' changesets: %s" % (not_changed.pop().path,
878 parents)
885 parents)
879 )
886 )
880
887
881 # Check nodes marked as removed
888 # Check nodes marked as removed
882 if self.removed and not parents:
889 if self.removed and not parents:
883 raise NodeDoesNotExistError("Cannot remove node at %s as there "
890 raise NodeDoesNotExistError("Cannot remove node at %s as there "
884 "were no parents specified" % self.removed[0].path)
891 "were no parents specified" % self.removed[0].path)
885 really_removed = set()
892 really_removed = set()
886 for p in parents:
893 for p in parents:
887 for node in self.removed:
894 for node in self.removed:
888 try:
895 try:
889 p.get_node(node.path)
896 p.get_node(node.path)
890 really_removed.add(node)
897 really_removed.add(node)
891 except ChangesetError:
898 except ChangesetError:
892 pass
899 pass
893 not_removed = set(self.removed) - really_removed
900 not_removed = set(self.removed) - really_removed
894 if not_removed:
901 if not_removed:
895 raise NodeDoesNotExistError("Cannot remove node at %s from "
902 raise NodeDoesNotExistError("Cannot remove node at %s from "
896 "following parents: %s" % (not_removed[0], parents))
903 "following parents: %s" % (not_removed[0], parents))
897
904
898 def commit(self, message, author, parents=None, branch=None, date=None,
905 def commit(self, message, author, parents=None, branch=None, date=None,
899 **kwargs):
906 **kwargs):
900 """
907 """
901 Performs in-memory commit (doesn't check workdir in any way) and
908 Performs in-memory commit (doesn't check workdir in any way) and
902 returns newly created ``Changeset``. Updates repository's
909 returns newly created ``Changeset``. Updates repository's
903 ``revisions``.
910 ``revisions``.
904
911
905 .. note::
912 .. note::
906 While overriding this method each backend's should call
913 While overriding this method each backend's should call
907 ``self.check_integrity(parents)`` in the first place.
914 ``self.check_integrity(parents)`` in the first place.
908
915
909 :param message: message of the commit
916 :param message: message of the commit
910 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
917 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
911 :param parents: single parent or sequence of parents from which commit
918 :param parents: single parent or sequence of parents from which commit
912 would be derieved
919 would be derieved
913 :param date: ``datetime.datetime`` instance. Defaults to
920 :param date: ``datetime.datetime`` instance. Defaults to
914 ``datetime.datetime.now()``.
921 ``datetime.datetime.now()``.
915 :param branch: branch name, as string. If none given, default backend's
922 :param branch: branch name, as string. If none given, default backend's
916 branch would be used.
923 branch would be used.
917
924
918 :raises ``CommitError``: if any error occurs while committing
925 :raises ``CommitError``: if any error occurs while committing
919 """
926 """
920 raise NotImplementedError
927 raise NotImplementedError
921
928
922
929
923 class EmptyChangeset(BaseChangeset):
930 class EmptyChangeset(BaseChangeset):
924 """
931 """
925 An dummy empty changeset. It's possible to pass hash when creating
932 An dummy empty changeset. It's possible to pass hash when creating
926 an EmptyChangeset
933 an EmptyChangeset
927 """
934 """
928
935
929 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
936 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
930 alias=None, message='', author='', date=''):
937 alias=None, message='', author='', date=''):
931 self._empty_cs = cs
938 self._empty_cs = cs
932 self.revision = -1
939 self.revision = -1
933 self.message = message
940 self.message = message
934 self.author = author
941 self.author = author
935 self.date = date
942 self.date = date
936 self.repository = repo
943 self.repository = repo
937 self.requested_revision = requested_revision
944 self.requested_revision = requested_revision
938 self.alias = alias
945 self.alias = alias
939
946
940 @LazyProperty
947 @LazyProperty
941 def raw_id(self):
948 def raw_id(self):
942 """
949 """
943 Returns raw string identifying this changeset, useful for web
950 Returns raw string identifying this changeset, useful for web
944 representation.
951 representation.
945 """
952 """
946
953
947 return self._empty_cs
954 return self._empty_cs
948
955
949 @LazyProperty
956 @LazyProperty
950 def branch(self):
957 def branch(self):
951 from rhodecode.lib.vcs.backends import get_backend
958 from rhodecode.lib.vcs.backends import get_backend
952 return get_backend(self.alias).DEFAULT_BRANCH_NAME
959 return get_backend(self.alias).DEFAULT_BRANCH_NAME
953
960
954 @LazyProperty
961 @LazyProperty
955 def short_id(self):
962 def short_id(self):
956 return self.raw_id[:12]
963 return self.raw_id[:12]
957
964
958 def get_file_changeset(self, path):
965 def get_file_changeset(self, path):
959 return self
966 return self
960
967
961 def get_file_content(self, path):
968 def get_file_content(self, path):
962 return u''
969 return u''
963
970
964 def get_file_size(self, path):
971 def get_file_size(self, path):
965 return 0
972 return 0
@@ -1,365 +1,373 b''
1 import os
1 import os
2 import posixpath
2 import posixpath
3
3
4 from rhodecode.lib.vcs.backends.base import BaseChangeset
4 from rhodecode.lib.vcs.backends.base import BaseChangeset
5 from rhodecode.lib.vcs.conf import settings
5 from rhodecode.lib.vcs.conf import settings
6 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError, \
6 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError, \
7 ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
7 ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
8 from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, \
8 from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, \
9 ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, \
9 ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, \
10 RemovedFileNodesGenerator, RootNode, SubModuleNode
10 RemovedFileNodesGenerator, RootNode, SubModuleNode
11
11
12 from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
12 from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
13 from rhodecode.lib.vcs.utils.lazy import LazyProperty
13 from rhodecode.lib.vcs.utils.lazy import LazyProperty
14 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
14 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
15 from rhodecode.lib.vcs.utils.hgcompat import archival, hex
15 from rhodecode.lib.vcs.utils.hgcompat import archival, hex
16
16
17
17
18 class MercurialChangeset(BaseChangeset):
18 class MercurialChangeset(BaseChangeset):
19 """
19 """
20 Represents state of the repository at the single revision.
20 Represents state of the repository at the single 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 self.raw_id = revision
25 self.raw_id = revision
26 self._ctx = repository._repo[revision]
26 self._ctx = repository._repo[revision]
27 self.revision = self._ctx._rev
27 self.revision = self._ctx._rev
28 self.nodes = {}
28 self.nodes = {}
29
29
30 @LazyProperty
30 @LazyProperty
31 def tags(self):
31 def tags(self):
32 return map(safe_unicode, self._ctx.tags())
32 return map(safe_unicode, self._ctx.tags())
33
33
34 @LazyProperty
34 @LazyProperty
35 def branch(self):
35 def branch(self):
36 return safe_unicode(self._ctx.branch())
36 return safe_unicode(self._ctx.branch())
37
37
38 @LazyProperty
38 @LazyProperty
39 def bookmarks(self):
39 def bookmarks(self):
40 return map(safe_unicode, self._ctx.bookmarks())
40 return map(safe_unicode, self._ctx.bookmarks())
41
41
42 @LazyProperty
42 @LazyProperty
43 def message(self):
43 def message(self):
44 return safe_unicode(self._ctx.description())
44 return safe_unicode(self._ctx.description())
45
45
46 @LazyProperty
46 @LazyProperty
47 def author(self):
47 def author(self):
48 return safe_unicode(self._ctx.user())
48 return safe_unicode(self._ctx.user())
49
49
50 @LazyProperty
50 @LazyProperty
51 def date(self):
51 def date(self):
52 return date_fromtimestamp(*self._ctx.date())
52 return date_fromtimestamp(*self._ctx.date())
53
53
54 @LazyProperty
54 @LazyProperty
55 def _timestamp(self):
55 def _timestamp(self):
56 return self._ctx.date()[0]
56 return self._ctx.date()[0]
57
57
58 @LazyProperty
58 @LazyProperty
59 def status(self):
59 def status(self):
60 """
60 """
61 Returns modified, added, removed, deleted files for current changeset
61 Returns modified, added, removed, deleted files for current changeset
62 """
62 """
63 return self.repository._repo.status(self._ctx.p1().node(),
63 return self.repository._repo.status(self._ctx.p1().node(),
64 self._ctx.node())
64 self._ctx.node())
65
65
66 @LazyProperty
66 @LazyProperty
67 def _file_paths(self):
67 def _file_paths(self):
68 return list(self._ctx)
68 return list(self._ctx)
69
69
70 @LazyProperty
70 @LazyProperty
71 def _dir_paths(self):
71 def _dir_paths(self):
72 p = list(set(get_dirs_for_path(*self._file_paths)))
72 p = list(set(get_dirs_for_path(*self._file_paths)))
73 p.insert(0, '')
73 p.insert(0, '')
74 return p
74 return p
75
75
76 @LazyProperty
76 @LazyProperty
77 def _paths(self):
77 def _paths(self):
78 return self._dir_paths + self._file_paths
78 return self._dir_paths + self._file_paths
79
79
80 @LazyProperty
80 @LazyProperty
81 def id(self):
81 def id(self):
82 if self.last:
82 if self.last:
83 return u'tip'
83 return u'tip'
84 return self.short_id
84 return self.short_id
85
85
86 @LazyProperty
86 @LazyProperty
87 def short_id(self):
87 def short_id(self):
88 return self.raw_id[:12]
88 return self.raw_id[:12]
89
89
90 @LazyProperty
90 @LazyProperty
91 def parents(self):
91 def parents(self):
92 """
92 """
93 Returns list of parents changesets.
93 Returns list of parents changesets.
94 """
94 """
95 return [self.repository.get_changeset(parent.rev())
95 return [self.repository.get_changeset(parent.rev())
96 for parent in self._ctx.parents() if parent.rev() >= 0]
96 for parent in self._ctx.parents() if parent.rev() >= 0]
97
97
98 @LazyProperty
99 def children(self):
100 """
101 Returns list of children changesets.
102 """
103 return [self.repository.get_changeset(child.rev())
104 for child in self._ctx.children() if child.rev() >= 0]
105
98 def next(self, branch=None):
106 def next(self, branch=None):
99
107
100 if branch and self.branch != branch:
108 if branch and self.branch != branch:
101 raise VCSError('Branch option used on changeset not belonging '
109 raise VCSError('Branch option used on changeset not belonging '
102 'to that branch')
110 'to that branch')
103
111
104 def _next(changeset, branch):
112 def _next(changeset, branch):
105 try:
113 try:
106 next_ = changeset.revision + 1
114 next_ = changeset.revision + 1
107 next_rev = changeset.repository.revisions[next_]
115 next_rev = changeset.repository.revisions[next_]
108 except IndexError:
116 except IndexError:
109 raise ChangesetDoesNotExistError
117 raise ChangesetDoesNotExistError
110 cs = changeset.repository.get_changeset(next_rev)
118 cs = changeset.repository.get_changeset(next_rev)
111
119
112 if branch and branch != cs.branch:
120 if branch and branch != cs.branch:
113 return _next(cs, branch)
121 return _next(cs, branch)
114
122
115 return cs
123 return cs
116
124
117 return _next(self, branch)
125 return _next(self, branch)
118
126
119 def prev(self, branch=None):
127 def prev(self, branch=None):
120 if branch and self.branch != branch:
128 if branch and self.branch != branch:
121 raise VCSError('Branch option used on changeset not belonging '
129 raise VCSError('Branch option used on changeset not belonging '
122 'to that branch')
130 'to that branch')
123
131
124 def _prev(changeset, branch):
132 def _prev(changeset, branch):
125 try:
133 try:
126 prev_ = changeset.revision - 1
134 prev_ = changeset.revision - 1
127 if prev_ < 0:
135 if prev_ < 0:
128 raise IndexError
136 raise IndexError
129 prev_rev = changeset.repository.revisions[prev_]
137 prev_rev = changeset.repository.revisions[prev_]
130 except IndexError:
138 except IndexError:
131 raise ChangesetDoesNotExistError
139 raise ChangesetDoesNotExistError
132
140
133 cs = changeset.repository.get_changeset(prev_rev)
141 cs = changeset.repository.get_changeset(prev_rev)
134
142
135 if branch and branch != cs.branch:
143 if branch and branch != cs.branch:
136 return _prev(cs, branch)
144 return _prev(cs, branch)
137
145
138 return cs
146 return cs
139
147
140 return _prev(self, branch)
148 return _prev(self, branch)
141
149
142 def diff(self, ignore_whitespace=True, context=3):
150 def diff(self, ignore_whitespace=True, context=3):
143 return ''.join(self._ctx.diff(git=True,
151 return ''.join(self._ctx.diff(git=True,
144 ignore_whitespace=ignore_whitespace,
152 ignore_whitespace=ignore_whitespace,
145 context=context))
153 context=context))
146
154
147 def _fix_path(self, path):
155 def _fix_path(self, path):
148 """
156 """
149 Paths are stored without trailing slash so we need to get rid off it if
157 Paths are stored without trailing slash so we need to get rid off it if
150 needed. Also mercurial keeps filenodes as str so we need to decode
158 needed. Also mercurial keeps filenodes as str so we need to decode
151 from unicode to str
159 from unicode to str
152 """
160 """
153 if path.endswith('/'):
161 if path.endswith('/'):
154 path = path.rstrip('/')
162 path = path.rstrip('/')
155
163
156 return safe_str(path)
164 return safe_str(path)
157
165
158 def _get_kind(self, path):
166 def _get_kind(self, path):
159 path = self._fix_path(path)
167 path = self._fix_path(path)
160 if path in self._file_paths:
168 if path in self._file_paths:
161 return NodeKind.FILE
169 return NodeKind.FILE
162 elif path in self._dir_paths:
170 elif path in self._dir_paths:
163 return NodeKind.DIR
171 return NodeKind.DIR
164 else:
172 else:
165 raise ChangesetError("Node does not exist at the given path %r"
173 raise ChangesetError("Node does not exist at the given path %r"
166 % (path))
174 % (path))
167
175
168 def _get_filectx(self, path):
176 def _get_filectx(self, path):
169 path = self._fix_path(path)
177 path = self._fix_path(path)
170 if self._get_kind(path) != NodeKind.FILE:
178 if self._get_kind(path) != NodeKind.FILE:
171 raise ChangesetError("File does not exist for revision %r at "
179 raise ChangesetError("File does not exist for revision %r at "
172 " %r" % (self.revision, path))
180 " %r" % (self.revision, path))
173 return self._ctx.filectx(path)
181 return self._ctx.filectx(path)
174
182
175 def _extract_submodules(self):
183 def _extract_submodules(self):
176 """
184 """
177 returns a dictionary with submodule information from substate file
185 returns a dictionary with submodule information from substate file
178 of hg repository
186 of hg repository
179 """
187 """
180 return self._ctx.substate
188 return self._ctx.substate
181
189
182 def get_file_mode(self, path):
190 def get_file_mode(self, path):
183 """
191 """
184 Returns stat mode of the file at the given ``path``.
192 Returns stat mode of the file at the given ``path``.
185 """
193 """
186 fctx = self._get_filectx(path)
194 fctx = self._get_filectx(path)
187 if 'x' in fctx.flags():
195 if 'x' in fctx.flags():
188 return 0100755
196 return 0100755
189 else:
197 else:
190 return 0100644
198 return 0100644
191
199
192 def get_file_content(self, path):
200 def get_file_content(self, path):
193 """
201 """
194 Returns content of the file at given ``path``.
202 Returns content of the file at given ``path``.
195 """
203 """
196 fctx = self._get_filectx(path)
204 fctx = self._get_filectx(path)
197 return fctx.data()
205 return fctx.data()
198
206
199 def get_file_size(self, path):
207 def get_file_size(self, path):
200 """
208 """
201 Returns size of the file at given ``path``.
209 Returns size of the file at given ``path``.
202 """
210 """
203 fctx = self._get_filectx(path)
211 fctx = self._get_filectx(path)
204 return fctx.size()
212 return fctx.size()
205
213
206 def get_file_changeset(self, path):
214 def get_file_changeset(self, path):
207 """
215 """
208 Returns last commit of the file at the given ``path``.
216 Returns last commit of the file at the given ``path``.
209 """
217 """
210 node = self.get_node(path)
218 node = self.get_node(path)
211 return node.history[0]
219 return node.history[0]
212
220
213 def get_file_history(self, path):
221 def get_file_history(self, path):
214 """
222 """
215 Returns history of file as reversed list of ``Changeset`` objects for
223 Returns history of file as reversed list of ``Changeset`` objects for
216 which file at given ``path`` has been modified.
224 which file at given ``path`` has been modified.
217 """
225 """
218 fctx = self._get_filectx(path)
226 fctx = self._get_filectx(path)
219 nodes = [fctx.filectx(x).node() for x in fctx.filelog()]
227 nodes = [fctx.filectx(x).node() for x in fctx.filelog()]
220 changesets = [self.repository.get_changeset(hex(node))
228 changesets = [self.repository.get_changeset(hex(node))
221 for node in reversed(nodes)]
229 for node in reversed(nodes)]
222 return changesets
230 return changesets
223
231
224 def get_file_annotate(self, path):
232 def get_file_annotate(self, path):
225 """
233 """
226 Returns a list of three element tuples with lineno,changeset and line
234 Returns a list of three element tuples with lineno,changeset and line
227 """
235 """
228 fctx = self._get_filectx(path)
236 fctx = self._get_filectx(path)
229 annotate = []
237 annotate = []
230 for i, annotate_data in enumerate(fctx.annotate()):
238 for i, annotate_data in enumerate(fctx.annotate()):
231 ln_no = i + 1
239 ln_no = i + 1
232 annotate.append((ln_no, self.repository\
240 annotate.append((ln_no, self.repository\
233 .get_changeset(hex(annotate_data[0].node())),
241 .get_changeset(hex(annotate_data[0].node())),
234 annotate_data[1],))
242 annotate_data[1],))
235
243
236 return annotate
244 return annotate
237
245
238 def fill_archive(self, stream=None, kind='tgz', prefix=None,
246 def fill_archive(self, stream=None, kind='tgz', prefix=None,
239 subrepos=False):
247 subrepos=False):
240 """
248 """
241 Fills up given stream.
249 Fills up given stream.
242
250
243 :param stream: file like object.
251 :param stream: file like object.
244 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
252 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
245 Default: ``tgz``.
253 Default: ``tgz``.
246 :param prefix: name of root directory in archive.
254 :param prefix: name of root directory in archive.
247 Default is repository name and changeset's raw_id joined with dash
255 Default is repository name and changeset's raw_id joined with dash
248 (``repo-tip.<KIND>``).
256 (``repo-tip.<KIND>``).
249 :param subrepos: include subrepos in this archive.
257 :param subrepos: include subrepos in this archive.
250
258
251 :raise ImproperArchiveTypeError: If given kind is wrong.
259 :raise ImproperArchiveTypeError: If given kind is wrong.
252 :raise VcsError: If given stream is None
260 :raise VcsError: If given stream is None
253 """
261 """
254
262
255 allowed_kinds = settings.ARCHIVE_SPECS.keys()
263 allowed_kinds = settings.ARCHIVE_SPECS.keys()
256 if kind not in allowed_kinds:
264 if kind not in allowed_kinds:
257 raise ImproperArchiveTypeError('Archive kind not supported use one'
265 raise ImproperArchiveTypeError('Archive kind not supported use one'
258 'of %s', allowed_kinds)
266 'of %s', allowed_kinds)
259
267
260 if stream is None:
268 if stream is None:
261 raise VCSError('You need to pass in a valid stream for filling'
269 raise VCSError('You need to pass in a valid stream for filling'
262 ' with archival data')
270 ' with archival data')
263
271
264 if prefix is None:
272 if prefix is None:
265 prefix = '%s-%s' % (self.repository.name, self.short_id)
273 prefix = '%s-%s' % (self.repository.name, self.short_id)
266 elif prefix.startswith('/'):
274 elif prefix.startswith('/'):
267 raise VCSError("Prefix cannot start with leading slash")
275 raise VCSError("Prefix cannot start with leading slash")
268 elif prefix.strip() == '':
276 elif prefix.strip() == '':
269 raise VCSError("Prefix cannot be empty")
277 raise VCSError("Prefix cannot be empty")
270
278
271 archival.archive(self.repository._repo, stream, self.raw_id,
279 archival.archive(self.repository._repo, stream, self.raw_id,
272 kind, prefix=prefix, subrepos=subrepos)
280 kind, prefix=prefix, subrepos=subrepos)
273
281
274 if stream.closed and hasattr(stream, 'name'):
282 if stream.closed and hasattr(stream, 'name'):
275 stream = open(stream.name, 'rb')
283 stream = open(stream.name, 'rb')
276 elif hasattr(stream, 'mode') and 'r' not in stream.mode:
284 elif hasattr(stream, 'mode') and 'r' not in stream.mode:
277 stream = open(stream.name, 'rb')
285 stream = open(stream.name, 'rb')
278 else:
286 else:
279 stream.seek(0)
287 stream.seek(0)
280
288
281 def get_nodes(self, path):
289 def get_nodes(self, path):
282 """
290 """
283 Returns combined ``DirNode`` and ``FileNode`` objects list representing
291 Returns combined ``DirNode`` and ``FileNode`` objects list representing
284 state of changeset at the given ``path``. If node at the given ``path``
292 state of changeset at the given ``path``. If node at the given ``path``
285 is not instance of ``DirNode``, ChangesetError would be raised.
293 is not instance of ``DirNode``, ChangesetError would be raised.
286 """
294 """
287
295
288 if self._get_kind(path) != NodeKind.DIR:
296 if self._get_kind(path) != NodeKind.DIR:
289 raise ChangesetError("Directory does not exist for revision %r at "
297 raise ChangesetError("Directory does not exist for revision %r at "
290 " %r" % (self.revision, path))
298 " %r" % (self.revision, path))
291 path = self._fix_path(path)
299 path = self._fix_path(path)
292
300
293 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
301 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
294 if os.path.dirname(f) == path]
302 if os.path.dirname(f) == path]
295 dirs = path == '' and '' or [d for d in self._dir_paths
303 dirs = path == '' and '' or [d for d in self._dir_paths
296 if d and posixpath.dirname(d) == path]
304 if d and posixpath.dirname(d) == path]
297 dirnodes = [DirNode(d, changeset=self) for d in dirs
305 dirnodes = [DirNode(d, changeset=self) for d in dirs
298 if os.path.dirname(d) == path]
306 if os.path.dirname(d) == path]
299
307
300 als = self.repository.alias
308 als = self.repository.alias
301 for k, vals in self._extract_submodules().iteritems():
309 for k, vals in self._extract_submodules().iteritems():
302 #vals = url,rev,type
310 #vals = url,rev,type
303 loc = vals[0]
311 loc = vals[0]
304 cs = vals[1]
312 cs = vals[1]
305 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
313 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
306 alias=als))
314 alias=als))
307 nodes = dirnodes + filenodes
315 nodes = dirnodes + filenodes
308 # cache nodes
316 # cache nodes
309 for node in nodes:
317 for node in nodes:
310 self.nodes[node.path] = node
318 self.nodes[node.path] = node
311 nodes.sort()
319 nodes.sort()
312
320
313 return nodes
321 return nodes
314
322
315 def get_node(self, path):
323 def get_node(self, path):
316 """
324 """
317 Returns ``Node`` object from the given ``path``. If there is no node at
325 Returns ``Node`` object from the given ``path``. If there is no node at
318 the given ``path``, ``ChangesetError`` would be raised.
326 the given ``path``, ``ChangesetError`` would be raised.
319 """
327 """
320
328
321 path = self._fix_path(path)
329 path = self._fix_path(path)
322
330
323 if not path in self.nodes:
331 if not path in self.nodes:
324 if path in self._file_paths:
332 if path in self._file_paths:
325 node = FileNode(path, changeset=self)
333 node = FileNode(path, changeset=self)
326 elif path in self._dir_paths or path in self._dir_paths:
334 elif path in self._dir_paths or path in self._dir_paths:
327 if path == '':
335 if path == '':
328 node = RootNode(changeset=self)
336 node = RootNode(changeset=self)
329 else:
337 else:
330 node = DirNode(path, changeset=self)
338 node = DirNode(path, changeset=self)
331 else:
339 else:
332 raise NodeDoesNotExistError("There is no file nor directory "
340 raise NodeDoesNotExistError("There is no file nor directory "
333 "at the given path: %r at revision %r"
341 "at the given path: %r at revision %r"
334 % (path, self.short_id))
342 % (path, self.short_id))
335 # cache node
343 # cache node
336 self.nodes[path] = node
344 self.nodes[path] = node
337 return self.nodes[path]
345 return self.nodes[path]
338
346
339 @LazyProperty
347 @LazyProperty
340 def affected_files(self):
348 def affected_files(self):
341 """
349 """
342 Get's a fast accessible file changes for given changeset
350 Get's a fast accessible file changes for given changeset
343 """
351 """
344 return self._ctx.files()
352 return self._ctx.files()
345
353
346 @property
354 @property
347 def added(self):
355 def added(self):
348 """
356 """
349 Returns list of added ``FileNode`` objects.
357 Returns list of added ``FileNode`` objects.
350 """
358 """
351 return AddedFileNodesGenerator([n for n in self.status[1]], self)
359 return AddedFileNodesGenerator([n for n in self.status[1]], self)
352
360
353 @property
361 @property
354 def changed(self):
362 def changed(self):
355 """
363 """
356 Returns list of modified ``FileNode`` objects.
364 Returns list of modified ``FileNode`` objects.
357 """
365 """
358 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
366 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
359
367
360 @property
368 @property
361 def removed(self):
369 def removed(self):
362 """
370 """
363 Returns list of removed ``FileNode`` objects.
371 Returns list of removed ``FileNode`` objects.
364 """
372 """
365 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
373 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
General Comments 0
You need to be logged in to leave comments. Login now