1
# arch-tag: david@allouche.net - 2003-11-17 15:23:30 332029000
2
# Copyright (C) 2003 David Allouche <david@allouche.net>
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
Internal module providing top-level arch package names
21
This module implements the top-level public interface for the
22
arch_ package. But for convenience reasons the author prefers
23
to store this code in a file separate from ``__init__.py``.
25
.. _arch: arch-module.html
27
This module is strictly internal and should never be used.
29
:var tla_tells_empty_meta_info: The back-end can to tell empty archive
32
The behaviour of ``tla`` is controlled by the _presence_ of some
33
archive meta-info files, but many releases of tla gave no CLI
34
feature to test for existence of meta-info file: the
35
``archive-meta-info`` command only gave the content of the file if
36
it was present, and could not differenciate between empty and
39
There is no cheap way to autodetect what is the behaviour of the
40
currently installed command-line executable, so the behaviour of
41
`arch.Archive` objects is configured by this variables.
43
:type tla_tells_empty_meta_info: bool
45
:var backend: See `arch.backend`
47
:var _arch: Internal deprecated interface to the backend
50
### Package docstring ###
53
High level bindings for the Arch revision control system
55
Archive Namespace Class Hierarchy
56
---------------------------------
58
:group Namespace Classes: Archive, Category, Branch, Version, Revision
60
:group Abstract Namespace Classes: NamespaceObject, Setupable, Package,
61
CategoryIterable, BranchIterable, VersionIterable, RevisionIterable,
62
ArchiveItem, CategoryItem, BranchItem, VersionItem
64
:group Archive-Related Classes: RevisionFile, NameParser
66
The archive namespace classes form a complex hierarchy which
67
cannot be appropriately described in individual classes.
69
The `Archive`, `Category`, `Branch`, `Version` and `Revision` classes
70
model the Arch namespace. Namespace objects can be created without the
71
corresponding archive structure being available.
73
Since they form a hierarchy of containers with shared methods and
74
properties in both directions, but do not have any subclass
75
relationship, they are defined using a collection of mixin classes.
77
The `RevisionIterable`, `VersionIterable`, `BranchIterable` and
78
`CategoryIterable` classes define the features which are inherited by
79
enclosing archive containers. Many methods in that hierarchy are
80
defined abstract (they raise UnimplementedError). They are always
81
overriden and are required to prevent legitimate PyChecker warnings.
83
The `ArchiveItem`, `CategoryItem`, `BranchItem` and `VersionItem`
84
classes provides features which are inherited by enclosed archive
85
items. The `NamespaceObject`, `Setupable` and `Package` classes
86
provide miscellaneous features and define aspects which do not fit
87
within the rest of the hierarchy.
91
,--------------------------------> NamespaceObject
94
| CategoryIterable <---- Archive ----' |
95
| | ArchiveItem <-----.
98
| BranchIterable <------ Category -----------+-------> Setupable
101
| | ,---------------+---------> Package
103
| VersionIterable <----- Branch ------> CategoryItem | |
106
| | ,---------------+------------' |
108
| RevisionIterable <---- Version -----> BranchItem |
112
| Revision ----> VersionItem |
114
`---------------------------------------------'
117
This admittedly complicated hierarchy minimizes code duplication and
118
provides abstract classes which can be used for testing objects for
119
features instead of testing for concrete types.
122
:group Source Tree Classes: SourceTree, ForeignTree, ArchSourceTree,
123
LibraryTree, WorkingTree
125
:group Changeset and Log Classes: Changeset, Patchlog, LogMessage
127
:group Incremental Ouput: ChangesetCreation, ChangesetApplication,
128
Chatter, TreeChange, FileAddition, FileDeletion, FileModification,
129
FilePermissionsChange, FileRename, SymlinkModification,
130
MergeOutcome, PatchConflict
132
:group Archive Functions: archives, iter_archives, make_archive,
133
register_archive, get, get_patch, make_continuation
135
:group Source Tree Functions: init_tree, in_source_tree, tree_root
137
:group User Functions: default_archive, my_id, set_my_id
139
:group Changeset Generation Functions: changeset, delta, iter_delta
141
:group Pika Escaping Functions: name_escape, name_unescape
143
:group Revision Library Functions: register_revision_library,
144
unregister_revision_library, iter_revision_libraries, library_archives,
145
iter_library_archives
147
:group Incremental Output Functions: classify_chatter,
148
classify_changeset_creation, classify_changeset_application
150
:group Obsolete Utility Functions: filter_archive_logs, filter_revisions,
151
grep_summary, grep_summary_interactive, last_revision, map_name_id,
152
revision_which_created, revisions_merging, suspected_move, temphack
154
:var backend: Backend controller.
156
This object is used to configure the backend system: name of the
157
executable, process handling strategy and command-line logging.
159
:type backend: `backends.commandline.CommandLineBackend`
162
# Configuration option.
163
# If True, expect archive-meta-info exit(1) for missing meta-info.
164
# If False, expect it exit(0) and output nothing.
165
tla_tells_empty_meta_info = True
171
from sets import ImmutableSet
174
from pathname import PathName, FileName, DirName
175
from deprecation import deprecated_usage, deprecated_callable
176
from _escaping import *
177
from _output import *
182
__all__.extend(names)
185
### Backend abstraction and compatibility
187
class _BackendProxy(object):
188
"""Internal hack for compatibility with _arch"""
189
def __getattribute__(self, name):
190
return getattr(backend._module, name)
191
def __setattr__(self, name, value):
192
setattr(backend._module, name, value)
194
_arch = _BackendProxy()
196
from backends import commandline
198
backend = commandline.default_backend()
203
### Internal helpers for namespace sanity checks
205
class _unsafe(tuple):
206
"""Used internally to disable namespace sanity checks"""
209
def _archive_param(archive):
210
"""Internal argument checking utility"""
211
if isinstance(archive, Archive):
214
if not NameParser.is_archive_name(archive):
215
raise errors.NamespaceError(archive, 'archive name')
218
def _registered_archive_param(archive):
219
"""Internal argument checking utility"""
220
archive = _archive_param(archive)
221
if not _arch.archive_registered(archive):
222
raise errors.ArchiveNotRegistered(archive)
225
def _archive_limit_param(archive, item):
226
"""Internal argument checking utility"""
227
if isinstance(item, ArchiveItem):
228
if archive != item.archive.name:
229
message = "%s is not an item of %s" % (item.fullname, archive)
230
raise ValueError(message)
234
if not p.has_category():
235
raise errors.NamespaceError(item, 'archive limit')
236
if not p.has_archive():
238
elif archive != p.get_archive():
239
message = "%s is not an item of %s" % (item.fullname, archive)
240
raise ValueError(message)
242
return p.get_nonarch()
244
def _version_param(vsn):
245
"""Internal argument checking utility"""
246
if isinstance(vsn, Version):
250
if not p.has_archive() or not p.is_version():
251
raise errors.NamespaceError(vsn, 'fully-qualified version')
254
def _revision_param(rev):
255
"""Internal argument checking utility."""
256
if isinstance(rev, Revision):
260
if not p.has_archive() or not p.has_patchlevel():
261
raise errors.NamespaceError(rev, 'fully-qualified revision')
264
def _version_revision_param(vsn_rev):
265
"""Internal argument checking utility"""
266
if isinstance(vsn_rev, BranchItem):
267
return vsn_rev.fullname
269
p = NameParser(vsn_rev)
270
if not p.has_archive() or not p.has_version():
271
raise errors.NamespaceError(vsn_rev,
272
'fully-qualified version or revision')
275
def _package_revision_param(pkg_rev):
276
"""Internal argument checking utility"""
277
if isinstance(pkg_rev, CategoryItem):
278
return pkg_rev.fullname
280
p = NameParser(pkg_rev)
281
if not p.has_archive() or not p.has_package():
282
raise errors.NamespaceError(pkg_rev,
283
'fully-qualified package or revision')
286
def _check_version_param(param, name):
287
"""Internal argument checking utility"""
288
if not isinstance(param, Version):
289
raise TypeError("Parameter \"%r\" must be a Version, but was: %r" \
292
def _check_revision_param(param, name):
293
"""Internal argument checking utility"""
294
if not isinstance(param, Revision):
295
raise TypeError("Parameter \"%s\" must be a Revision"
296
" but was: %r" % (name, param))
298
def _check_relname_param(param, name):
299
"""Internal argument checking utility"""
300
if not isinstance(param, basestring):
302
elif os.path.isabs(param):
303
exc_type = ValueError
306
raise exc_type("Parameter \"%s\" must be a relative path (string)"
307
" but was: %r" % (name, param))
310
### Base class for all namespace classes ###
312
public('NamespaceObject')
314
class NamespaceObject(object):
316
"""Base class for all archive objects."""
318
def get_fullname(self):
321
Fully qualified name of this namespace object.
324
:see: `NamespaceObject.fullname`
326
raise NotImplementedError
328
fullname = property(get_fullname, doc = """
329
Fully qualfied name of this namespace object.
335
"""Does this namespace exists?
337
Within the Arch model, history cannot be changed: created archive
338
entries cannot be deleted. However, it is possible to ``unregister`` an
339
archive, or to find references to archives whose location is not known.
340
Thus, existence cannot always be decided. Testing for the existence of
341
a name in a non-registered archive raises
342
`errors.ArchiveNotRegistered`.
344
:return: whether this namespace object exists.
346
:raise errors.ArchiveNotRegistered: the archive name is not registered,
347
so existence cannot be decided.
348
:raise errors.ExecProblem: there was a problem accessing the archive.
350
raise NotImplementedError
353
"""Fully-qualified name in angle brackets.
357
return '<%s>' % self.fullname
360
"""Fully-qualified name.
362
Returns the value of the fullname attribute.
369
"""Compare types and fully-qualified names.
371
:return: wether objects have the same types and names.
374
return type(self) is type(x) \
375
and self.__class__ is x.__class__ \
376
and self.fullname == x.fullname
379
"""Compare types and fully-qualified names.
381
:return: whether objects have different types or names.
384
return type(self) is not type(x) \
385
or self.__class__ is not x.__class__ \
386
or self.fullname != x.fullname
389
### Mixins for archive iteration aspect ###
398
class RevisionIterable(NamespaceObject):
400
"""Abstract class for namespace classes above Revision.
402
RevisionIterable provides features which are common to all objects
403
containing revisions.
406
def iter_revisions(self, reverse=False):
407
"""Iterate over archive revisions.
409
:param reverse: reverse order, recent revisions first.
411
:return: all existing revisions in this namespace.
412
:rtype: iterable of `Revision`
414
:precondition: `self.exists()` returns ``True``.
416
raise NotImplementedError
418
def iter_library_revisions(self, reverse=False):
419
"""Iterate over library revisions.
421
:param reverse: reverse order, recent revisions first.
423
:return: revisions in this namespace which are present in the
425
:rtype: iterable of `Revision`
427
raise NotImplementedError
430
class VersionIterable(RevisionIterable):
432
"""Abstract class for archive classes above Version.
434
VersionIterable provides features which are common to all objects
438
def iter_versions(self, reverse=False):
439
"""Iterate over archive versions.
441
:param reverse: reverse order, higher versions first.
443
:return: all existing versions in this namespace.
444
:rtype: iterable of `Version`
446
:precondition: `self.exists()` returns ``True``.
448
raise NotImplementedError
450
def iter_library_versions(self, reverse=False):
451
"""Iterate over library revisions.
453
:param reverse: reverse order, higher versions first.
455
:return: versions in this namespace which are present in the
457
:rtype: iterable of `Version`
459
raise NotImplementedError
461
def iter_revisions(self, reverse=False):
462
for v in self.iter_versions(reverse):
463
for r in v.iter_revisions(reverse): yield r
465
def iter_library_revisions(self, reverse=False):
466
for v in self.iter_library_versions(reverse):
467
for r in v.iter_library_revisions(reverse): yield r
470
class BranchIterable(VersionIterable):
472
"""Base class for archive classes above Branch.
474
BranchIterable provides features which are common to all objects
478
def iter_branches(self):
479
"""Iterate over archive branches.
481
:return: all existing branches in this namespace.
482
:rtype: iterable of `Branch`
483
:precondition: `self.exists()` returns ``True``.
485
raise NotImplementedError
487
def iter_library_branches(self):
488
"""Iterate over library branches.
490
:return: branches in this namespace which are present in the
492
:rtype: iterable of `Branch`
494
raise NotImplementedError
496
def iter_versions(self, reverse=False):
497
for b in self.iter_branches():
498
for v in b.iter_versions(reverse): yield v
500
def iter_library_versions(self, reverse=False):
501
for b in self.iter_library_branches():
502
for v in b.iter_library_versions(reverse): yield v
505
class CategoryIterable(BranchIterable):
507
"""Base class for Archive.
509
CategoryIterable provides features for the aspect of Archive wich
510
relates to its containing other archive objects.
513
def iter_categories(self):
514
"""Iterate over archive categories.
516
:return: all existing categories in this namespace.
517
:rtype: iterable of `Category`
518
:precondition: `self.exists()` returns ``True``.
520
raise NotImplementedError
522
def iter_library_categories(self):
523
"""Iterate over library categories.
525
:return: categories in this namespace which are present in the
527
:rtype: iterable of `Category`
529
raise NotImplementedError
531
def iter_branches(self):
532
for c in self.iter_categories():
533
for b in c.iter_branches(): yield b
535
def iter_library_branches(self):
536
for c in self.iter_library_categories():
537
for b in c.iter_library_branches(): yield b
540
### Base classes for archive containment aspect ###
549
class ArchiveItem(NamespaceObject):
551
"""Base class for all archive components classes.
553
ArchiveItem provides features common to all objects which are
554
structural components of Archive.
557
def __init__(self, archive, nonarch):
558
self._archive = archive
559
self._nonarch = nonarch
561
def get_archive(self):
564
Archive which contains this namespace object.
567
:see: `ArchiveItem.archive`
569
deprecated_callable(self.get_archive, (type(self), 'archive'))
572
def _get_archive(self):
573
return Archive(_unsafe((self._archive,)))
575
archive = property(_get_archive, doc="""
576
Archive which contains this namespace object.
581
def get_nonarch(self):
584
Non-arch part of this namespace name.
587
:see: `ArchiveItem.nonarch`
589
deprecated_callable(self.get_nonarch, (type(self), 'nonarch'))
592
def _get_nonarch(self):
595
nonarch = property(_get_nonarch, doc="""
596
Non-arch part of this namespace name.
601
def get_fullname(self):
602
deprecated_callable(self.get_fullname, (type(self), 'fullname'))
605
def _get_fullname(self):
606
return '%s/%s' % (self._archive, self._nonarch)
608
fullname = property(_get_fullname, doc=NamespaceObject.fullname.__doc__)
611
class CategoryItem(ArchiveItem):
613
"""Base class for archive classes below Category.
615
CategoryItem provides features common to all objects which are
616
structural components of Category.
619
def get_category(self):
622
Category which contains this namespace object.
625
:see: `CategoryItem.category`
627
deprecated_callable(self.get_category, (type(self), 'category'))
630
def _get_category(self):
631
return Category(_unsafe((self._archive, self._nonarch.split('--')[0])))
633
category = property(_get_category, doc="""
634
Category which contains this object.
640
class BranchItem(CategoryItem):
642
"""Base class for archive classes Version and Revision.
644
BranchItem provides features common to all objects which are
645
structural components of Branch.
648
def get_branch(self):
651
Branch which contains this object.
654
:see: `BranchItem.branch`
656
deprecated_callable(self.get_branch, (type(self), 'branch'))
659
def _get_branch(self):
660
assert isinstance(self, Version)
661
package = '--'.join(self._nonarch.split('--')[0:-1])
662
return Branch(_unsafe((self._archive, package)))
664
branch = property(_get_branch, doc="""
665
Branch which contains this namespace object.
671
class VersionItem(BranchItem):
673
"""Base class for Revision.
675
VersionItem provides features for the aspect of Revision which
676
relate to its containment within other archive objects.
679
def __init__(self, archive, version, patchlevel):
680
BranchItem.__init__(self, archive, "%s--%s" % (version, patchlevel))
681
self._version, self._patchlevel = version, patchlevel
683
def get_branch(self):
684
deprecated_callable(self.get_branch, (type(self), 'branch'))
687
def _get_branch(self):
688
assert isinstance(self, Revision)
689
package = '--'.join(self._version.split('--')[0:-1])
690
return Branch(_unsafe((self._archive, package)))
692
branch = property(_get_branch, doc=BranchItem.branch.__doc__)
694
def get_version(self):
697
Version which contains this revision.
700
:see: `VersionItem.version`
702
deprecated_callable(self.get_version, (type(self), 'version'))
705
def _get_version(self):
706
return Version(_unsafe((self._archive, self._version)))
708
version = property(_get_version, doc="""
709
Version which contains this revision.
714
def get_patchlevel(self):
717
Patch-level part of this object's name.
720
:see: `VersionItem.patchlevel`
722
deprecated_callable(self.get_patchlevel, (type(self), 'patchlevel'))
723
return self.patchlevel
725
def _get_patchlevel(self):
726
return self._patchlevel
728
patchlevel = property(_get_patchlevel, doc="""
729
Patch-level part of this object's name.
735
### Mixins for misc. features of archive objects ###
737
public('Setupable', 'Package')
739
class Setupable(ArchiveItem):
741
"""Base class for container archive objects."""
744
"""Create this namespace in the archive.
746
:precondition: self.archive.exists()
747
:postcondition: `self.exists()`
748
:raise errors.ExecProblem: the archive is not registered or
749
there was a problem accessing the archive
751
# FIXME: do not do the right thing for anonymous branches
752
_arch.archive_setup(self.fullname)
755
class Package(Setupable, RevisionIterable):
757
"""Base class for ordered container archive objects."""
759
def as_revision(self):
762
Latest revision in this package.
765
:precondition: `self.exists()` returns ``True``
766
:precondition: `self.iter_revisions()` yields at least one object.
767
:raises StopIteration: this package contains no revision
768
:see: `Package.latest_revision`
770
deprecated_callable(self.as_revision, self.latest_revision)
771
return self.iter_revisions(reverse=True).next()
773
def latest_revision(self):
774
"""Latest revision in this package.
777
:precondition: `self.exists()` returns ``True``
778
:precondition: `self.iter_revisions()` yields at least one object.
779
:raises ValueError: the archive is not registered, or this
780
package does not exist, or it contains no revision.
783
return self.iter_revisions(reverse=True).next()
784
except errors.ExecProblem:
786
self_exists = self.exists()
787
except errors.ArchiveNotRegistered:
788
raise ValueError('Archive is not registered: %s'
791
raise ValueError('Package does not exist: %s' % self)
793
except StopIteration:
794
raise ValueError('Package contains no revision: %s' % self)
797
### Archive classes ###
799
public('Archive', 'Category', 'Branch', 'Version', 'Revision')
801
class Archive(CategoryIterable):
803
"""Arch archive namespace object.
805
In the Arch revision control system, archives are the units of
806
storage. They store revisions organized in categories, branches
807
and versions, and are associated to a `name` and a `location`.
809
:see: `Category`, `Branch`, `Version`, `Revision`
812
def __init__(self, name):
813
"""Create an archive object from its registered name.
815
:param name: archive name, like "jdoe@example.com--2003"
817
:raise errors.NamespaceError: invalid archive name.
819
if isinstance(name, Archive):
821
if isinstance(name, _unsafe):
822
assert len(name) == 1
824
elif not NameParser.is_archive_name(name):
825
raise errors.NamespaceError(name, 'archive name')
831
Logical name of the archive.
836
deprecated_callable(Archive.get_name, (Archive, 'name'))
842
name = property(_get_name, doc="""
843
Logical name of the archive.
848
def get_fullname(self):
849
deprecated_callable(Archive.get_fullname, (Archive, 'fullname'))
852
fullname = property(_get_name, doc=NamespaceObject.fullname.__doc__)
854
def get_location(self):
857
URI of the archive, specifies location and access method.
860
:see: `Archive.location`
862
deprecated_callable(Archive.get_location, (Archive, 'location'))
865
def _get_location(self):
866
return _arch.whereis_archive(self.name)
868
location = property(_get_location, doc="""
869
URI of the archive, specifies location and access method.
871
For example 'http://ddaa.net/arch/2004', or
872
'sftp://user@sourcecontrol.net/public_html/2004'.
877
def _meta_info(self, key):
878
return _arch.archive_meta_info(self.name, key)
880
def _has_meta_info(self, key):
881
if tla_tells_empty_meta_info:
883
_arch.archive_meta_info(self.name, key)
888
return _arch.archive_meta_info(self.name, key) is not None
890
def get_is_mirror(self):
893
Is this archive registration a mirror?
896
:see: Archive.is_mirror
898
deprecated_callable(Archive.get_is_mirror, (Archive, 'is_mirror'))
899
return self.is_mirror
901
def _get_is_mirror(self):
902
return self._has_meta_info('mirror')
904
is_mirror = property(_get_is_mirror, doc="""
905
Is this archive registration a mirror?
910
def get_official_name(self):
913
Official archive name of this archive registration.
916
:see: `Archive.official_name`
918
deprecated_callable(Archive.get_official_name,
919
(Archive, 'official_name'))
920
return self.official_name
922
def _get_official_name(self):
923
return _arch.archive_meta_info(self.name, 'name')
925
official_name = property(_get_official_name, doc="""
926
Official archive name of this archive registration.
931
def get_is_signed(self):
934
Is the archive GPG-signed?
937
:see: `Archive.is_signed`
939
deprecated_callable(Archive.get_is_signed, (Archive, 'is_signed'))
940
return self.is_signed
942
def _get_is_signed(self):
943
return self._has_meta_info('signed-archive')
945
is_signed = property(_get_is_signed, doc="""
946
Is the archive GPG-signed?
951
def get_has_listings(self):
954
Does the archive provide .listing file for http access?
957
:see: `Archive.has_listings`
959
deprecated_callable(Archive.get_has_listings,
960
(Archive, 'has_listings'))
961
return self.has_listings
963
def _get_has_listings(self):
964
return self._has_meta_info('http-blows')
966
has_listings = property(_get_has_listings, doc="""
967
Does the archive provide .listing file for http access?
972
def _get_version_string(self):
973
location = '/'.join((self.location, '.archive-version'))
975
version_file = urllib.urlopen(location)
977
ret = version_file.read()
982
version_string = property(_get_version_string, doc="""
983
Version string of the archive.
985
Contents of the ``.archive-version`` file at the root of the archive.
990
def __getitem__(self, category):
991
"""Instanciate a Category belonging to this archive.
993
:param category: unqualified category name
997
if not NameParser.is_category_name(category):
998
raise errors.NamespaceError(category, 'unqualified category name')
999
return Category(_unsafe((self.name, category)))
1002
if _arch.archive_registered(self.name):
1005
raise errors.ArchiveNotRegistered(self.name)
1007
def is_registered(self):
1008
"""Is this archive registered?
1010
:return: Whether the location associated to this registration name is
1013
:see: `register_archive`, `Archive.unregister`
1015
return _arch.archive_registered(self.name)
1017
def iter_categories(self):
1018
for c in _arch.categories(self.name):
1019
yield Category(_unsafe((self.name, c)))
1021
def iter_library_categories(self):
1022
for c in _arch.library_categories(self.name):
1023
yield Category(_unsafe((self.name, c)))
1025
def get_categories(self):
1028
Categories in this archive.
1030
:rtype: tuple of `Category`
1031
:see: `iter_categories`
1033
deprecated_callable(Archive.get_categories, Archive.iter_categories)
1034
return tuple(self.iter_categories())
1036
def _get_categories(self):
1037
deprecated_callable((Archive, 'categories'), Archive.iter_categories)
1038
return tuple(self.iter_categories())
1040
categories = property(_get_categories, doc="""
1043
Categories in this archive.
1045
:type: tuple of `Category`
1046
:see: `iter_categories`
1049
def get_library_categories(self):
1052
Categories in this archive present in the library.
1054
:rtype: tuple of `Category`
1055
:see: `iter_library_categories`
1057
deprecated_callable(Archive.get_library_categories,
1058
Archive.iter_library_categories)
1059
return tuple(self.iter_library_categories())
1061
def _get_library_categories(self):
1062
deprecated_callable((Archive, 'library_categories'),
1063
Archive.iter_library_categories)
1064
return tuple(self.iter_library_categories())
1066
library_categories = property(_get_library_categories, doc="""
1069
Categories in this archive present in the library.
1071
:type; tuple of `Category`
1072
:see: `iter_library_categories`
1075
def unregister(self):
1076
"""Unregister this archive.
1078
:precondition: `self.is_registered()`
1079
:postcondition: not `self.is_registered()`
1080
:see: `register_archive`
1082
return _arch.unregister_archive(self.name)
1084
def make_mirror(self, name, location, signed=False, listing=False):
1085
"""Create and register a new mirror of this archive.
1087
:param name: name of the new mirror (for example
1088
'david@allouche.net--2003b-MIRROR').
1090
:param location: writeable URI were to create the archive mirror.
1092
:param signed: create GPG signatures for the mirror contents
1094
:param listing: maintains ''.listing'' files to enable HTTP access.
1097
:return: object for the newly created archive mirror.
1100
:precondition: `self.is_registered()`
1101
:precondition: ``name`` is not a registered archive name
1102
:precondition: ``location`` does not exist and can be created
1103
:postcondition: Archive(name).is_registered()
1105
:raise errors.NamespaceError: ``name`` is not a valid archive name.
1107
a = Archive(name) # check archive name validity first
1108
_arch.make_archive_mirror(self.official_name, name, location,
1112
def mirror(self, limit=None, fromto=None,
1113
no_cached=False, cached_tags=False):
1114
"""Update an archive mirror.
1116
:param limit: restrict mirrorring to those archive items. All items
1117
must belong to this archive.
1118
:type limit: iterable of at least one ArchiveItem or str
1120
:param fromto: update the mirror specified by the second item with the
1121
contents of the archive specified by the first item.
1122
:type fromto: sequence of exactly two Archive or str.
1124
:precondition: If ``fromto`` is provided, both items must be registered
1125
archives names whose official name is this archive.
1127
:param no_cached: do not copy cached revisions.
1128
:type no_cached: bool
1130
:param cached_tags: copy only cachedrevs for tags to other archives.
1131
:type cached_tags: bool
1134
limit = map(lambda x: _archive_limit_param(self.name, x), limit)
1136
_arch.archive_mirror(self.name, None, limit=limit,
1137
no_cached=no_cached, cached_tags=cached_tags)
1138
if fromto is not None:
1139
from_, to = map(_registered_archive_param, fromto)
1140
_arch.archive_mirror(from_, to, limit=limit,
1141
no_cached=no_cached, cached_tags=cached_tags)
1144
class Category(Setupable, BranchIterable):
1146
"""Arch category namespace object.
1148
:see: `Archive`, `Branch`, `Version`, `Revision`
1151
def __init__(self, name):
1152
"""Create a category object from its name.
1154
:param name: fully-qualified category name, like
1155
"jdoe@example.com/frob"
1157
:raise errors.NamespaceError: ``name`` is not a valid category name.
1159
if isinstance(name, Category):
1160
name = (name._archive, name._nonarch)
1161
elif not isinstance(name, _unsafe):
1162
p = NameParser(name)
1163
if not p.has_archive() or not p.is_category():
1164
raise errors.NamespaceError(name, 'fully-qualified category')
1165
name = (p.get_archive(), p.get_nonarch())
1166
ArchiveItem.__init__(self, *name)
1168
def __getitem__(self, b):
1169
"""Instanciate a branch belonging to this category.
1171
For example ``Category('jdoe@example.com/frob')['devel']`` is
1172
equivalent to ``Branch('jdoe@example.com/frob--devel')``.
1174
:param b: branch id.
1178
:raise NamespaceError: argument is not a valid branch id.
1180
if b is not None and not NameParser.is_branch_name(b):
1181
raise errors.NamespaceError(b, 'unqualified branch name')
1182
if b is None: # nameless branch
1183
return Branch(_unsafe((self._archive, self._nonarch)))
1184
else: # named branch
1185
return Branch(_unsafe((self._archive,
1186
"%s--%s" % (self._nonarch, b))))
1189
return _arch.category_exists(self._archive, self._nonarch)
1191
def iter_branches(self):
1192
for b in _arch.branches(self.fullname):
1193
yield Branch(_unsafe((self._archive, b)))
1195
def iter_library_branches(self):
1196
for b in _arch.library_branches(self.fullname):
1197
yield Branch(_unsafe((self._archive, b)))
1199
def get_branches(self):
1202
Branches in this category.
1204
:rtype: tuple of `Branch`
1205
:see: `iter_branches`
1207
deprecated_callable(self.get_branches, self.iter_branches)
1208
return tuple(self.iter_branches())
1210
def _get_branches(self):
1211
deprecated_callable((type(self), 'branches'), self.iter_branches)
1212
return tuple(self.iter_branches())
1214
branches = property(_get_branches, doc="""
1217
Branches in this category.
1219
:type: tuple of `Branch`
1220
:see: `Category.iter_branches`
1223
def get_library_branches(self):
1226
Branches in this category present in the library.
1228
:rtype: tuple of `Branch`
1229
:see: `iter_library_branches`
1231
deprecated_callable(self.get_library_branches,
1232
self.iter_library_branches)
1233
return tuple(self.iter_library_branches())
1235
def _get_library_branches(self):
1236
deprecated_callable((Category, 'library_branches'),
1237
self.iter_library_branches)
1238
return tuple(self.iter_library_branches())
1240
library_branches = property(_get_library_branches, doc="""
1243
Branches in this category present in the library.
1245
:type: tuple of `Branch`
1246
:see: `Category.iter_branches`
1250
class Branch(CategoryItem, Package, VersionIterable):
1252
"""Arch branch namespace object.
1254
:see: `Archive`, `Category`, `Version`, `Revision`
1257
def __init__(self, name):
1258
"""Create a Branch object from its name.
1260
:param name: fully-qualified branch name, like
1261
"jdoe@example.com--2004/frob--devo" or
1262
"jdoe@example.com--2004/frob".
1264
:raise errors.NamespaceError: ``name`` is not a valid branch name.
1266
if isinstance(name, Branch):
1267
name = (name._archive, name._nonarch)
1268
elif not isinstance(name, _unsafe):
1269
p = NameParser(name)
1270
if not p.has_archive() or not p.is_package():
1271
raise errors.NamespaceError(name, 'fully-qualified branch')
1272
assert p.is_package()
1273
name = (p.get_archive(), p.get_nonarch())
1274
CategoryItem.__init__(self, *name)
1276
def __getitem__(self, v):
1277
"""Instanciate a version belonging to this branch.
1279
For example ``Branch('jdoe@example.com/frob--devel')['0']`` is
1280
equivalent to ``Branch('jdoe@example.com/frob--devel--0')``.
1282
:param v: branch id.
1286
:raise NamespaceError: argument is not a valid version id.
1288
if not NameParser.is_version_id(v):
1289
raise errors.NamespaceError(v, 'version id')
1290
return Version(_unsafe((self._archive, "%s--%s" % (self._nonarch, v))))
1293
return _arch.branch_exists(self._archive, self._nonarch)
1295
def iter_versions(self, reverse=False):
1296
for v in _arch.versions(self.fullname, reverse):
1297
yield Version(_unsafe((self._archive, v)))
1299
def iter_library_versions(self, reverse=False):
1300
for v in _arch.library_versions(self.fullname, reverse):
1301
yield Version(_unsafe((self._archive, v)))
1303
def get_versions(self, reverse=False):
1306
Versions in this branch.
1308
:rtype: tuple of `Version`
1309
:see: `iter_versions`
1311
deprecated_callable(self.get_versions, self.iter_versions)
1312
return tuple(self.iter_versions(reverse))
1314
def _get_versions(self):
1315
deprecated_callable((Branch, 'versions'), self.iter_versions)
1316
return tuple(self.iter_versions(reverse=False))
1318
versions = property(_get_versions, doc="""
1321
Versions in this branch.
1323
:type: tuple of `Version`
1324
:see: `iter_versions`
1327
def get_library_versions(self, reverse=False):
1330
Versions in this branch present in the library.
1332
:rtype: tuple of `Version`
1333
:see: `iter_library_versions`
1335
deprecated_callable(self.get_library_versions,
1336
self.iter_library_versions)
1337
return tuple(self.iter_library_versions(reverse))
1339
def _get_library_versions(self):
1340
deprecated_callable((Branch, 'library_versions'),
1341
self.iter_library_versions)
1342
return tuple(self.iter_library_versions(reverse=False))
1344
library_versions = property(_get_library_versions, doc="""
1347
Versions in this branch present in the library.
1349
:type: tuple of `Version`
1350
:see: `iter_library_versions`
1353
def as_version(self):
1356
Latest version in this branch.
1359
:precondition: `self.exists()` returns ``True``
1360
:precondition: `self.iter_versions` yields at least one object.
1361
:raise IndexError: this branch is empty.
1362
:see: `latest_version`
1364
deprecated_callable(self.as_version, self.latest_version)
1365
return list(self.iter_versions(reverse=True))[0]
1367
def latest_version(self):
1368
"""Latest version in this branch.
1371
:precondition: `self.exists()` returns ``True``
1372
:precondition: `self.iter_versions` yields at least one object.
1373
:raise ValueError: the archive is not registered, or this branch does
1374
not exist, or it contains no version.
1377
return self.iter_versions(reverse=True).next()
1378
except errors.ExecProblem:
1380
self_exists = self.exists()
1381
except errors.ArchiveNotRegistered:
1382
raise ValueError('Archive is not registered: %s'
1385
raise ValueError('Branch does not exist: %s' % self)
1387
except StopIteration:
1388
raise ValueError('Branch contains no version: %s' % self)
1392
class Version(BranchItem, Package, RevisionIterable):
1394
"""Arch version namespace object.
1396
:see: `Archive`, `Category`, `Branch`, `Revision`
1399
def __init__(self, name):
1400
"""Create a Version object from its name.
1402
:param name: fully-qualified version name, like
1403
"jdoe@example.com--2004/frob--devo--1.2".
1406
:note: Nameless branches have no "branch" part in their name.
1408
if isinstance(name, Version):
1409
name = (name._archive, name._nonarch)
1410
elif not isinstance(name, _unsafe):
1411
p = NameParser(name)
1412
if not p.has_archive() or not p.is_version():
1413
raise errors.NamespaceError(name, 'fully-qualified version')
1414
name = (p.get_archive(), p.get_nonarch())
1415
BranchItem.__init__(self, *name)
1417
def __getitem__(self, lvl):
1418
"""Instanciate a revision belonging to this version.
1420
For example ``Version('jdoe@example.com/frob--devel--0')['patch-1']``
1422
``Revision('jdoe@example.com/frob--devel--0--patch1')``.
1424
:param lvl: patch level.
1428
:raise NamespaceError: argument is not a valid version patchlevel.
1430
if not NameParser.is_patchlevel(lvl):
1431
raise errors.NamespaceError(lvl, 'unqualified patchlevel')
1432
return Revision(_unsafe((self._archive, self._nonarch, lvl)))
1434
def as_version(self):
1441
deprecated_callable(self.as_version, because='Foolish consistency.')
1445
return _arch.version_exists(self._archive, self._nonarch)
1447
def iter_revisions(self, reverse=False):
1448
for lvl in _arch.revisions(self.fullname, reverse):
1449
yield Revision(_unsafe((self._archive, self._nonarch, lvl)))
1451
def iter_library_revisions(self, reverse=False):
1452
for lvl in _arch.library_revisions(self.fullname, reverse):
1453
yield Revision(_unsafe((self._archive, self._nonarch, lvl)))
1455
def get_revisions(self, reverse=False):
1458
Revisions in this version.
1460
:rtype: tuple of `Revision`
1461
:see: `iter_revisions`
1463
deprecated_callable(self.get_revisions, self.iter_revisions)
1464
return tuple(self.iter_revisions(reverse))
1466
def _get_revisions(self):
1467
deprecated_callable((Version, 'revisions'), self.iter_revisions)
1468
return tuple(self.iter_revisions(reverse=False))
1470
revisions = property(_get_revisions, doc="""
1473
Revisions in this version.
1475
:type: tuple of `Revision`
1476
:see: `iter_revisions`
1479
def get_library_revisions(self, reverse=False):
1482
Revisions in this version present in the library.
1484
:rtype: tuple of `Revision`
1485
:see: `iter_library_revisions`
1487
deprecated_callable(self.get_library_revisions,
1488
self.iter_library_revisions)
1489
return tuple(self.iter_library_revisions(reverse))
1491
def _get_library_revisions(self):
1492
deprecated_callable((Version, 'library_revisions'),
1493
self.iter_library_revisions)
1494
return tuple(self.iter_library_revisions(reverse=False))
1496
library_revisions = property(_get_library_revisions, doc="""
1499
Revisions in this version present in the library.
1501
:type: tuple of `Revision`
1502
:see: `iter_library_revisions`
1505
def iter_merges(self, other=None, reverse=False, metoo=True):
1506
"""Iterate over merge points in this version.
1508
This method is mostly useful to save multiple invocations of
1509
the command-line tool and multiple connection to a remote
1510
archives when building an ancestry graph. Ideally, it would
1511
not be present and the desired merge graph traversal would be
1512
done using the new_patches and merges properties of Patchlog
1515
:param other: list merges with that version.
1516
:type other: `Version`
1517
:param reverse: reverse order, recent revisions first.
1519
:param metoo: do not report the presence of a patch within itself
1522
:return: Iterator of tuples (R, T) where R are revisions in this
1523
version and T are iterable of revisions in the ``other`` version.
1524
:rtype: iterable of `Revision`
1526
if other is None: othername = None
1527
else: othername = other.fullname
1528
flow = _arch.iter_merges(self.fullname, othername, reverse, metoo)
1531
for target, source in flow:
1532
if last_target is None:
1533
last_target = target
1534
if target == last_target:
1535
sources.append(source)
1537
yield self[last_target], itertools.imap(Revision, sources)
1538
last_target = target
1540
yield self[last_target], itertools.imap(Revision, sources)
1542
def iter_cachedrevs(self):
1543
"""Iterate over the cached revisions in this version.
1545
:rtype: iterator of `Revision`
1547
for rev in _arch.cachedrevs(self.fullname):
1548
lvl = rev.split('--')[-1]
1552
class Revision(VersionItem):
1554
"""Arch revision namespace object.
1556
:see: `Archive`, `Category`, `Branch`, `Version`
1557
:group Libray Methods: library_add, library_remove, library_find
1558
:group History Methods: get_ancestor, get_previous, iter_ancestors
1561
def __init__(self, name):
1562
"""Create a Revision object from its name.
1564
:param name: fully-qualified revision, like
1565
"jdoe@example.com--2004/frob--devo--1.2--patch-2".
1568
:note: Nameless branches have no "branch" part in their name.
1570
if isinstance(name, Revision):
1571
name = (name._archive, name._version, name._patchlevel)
1572
elif not isinstance(name, _unsafe):
1573
p = NameParser(name)
1574
if not p.has_archive() or not p.has_patchlevel():
1575
raise errors.NamespaceError(name, 'fully-qualified revision')
1576
name = (p.get_archive(), p.get_package_version(),
1578
VersionItem.__init__(self, *name)
1579
self.__patchlog = Patchlog(self)
1581
def as_revision(self):
1584
Returns this revision. For consistency with `Package.as_revision()`.
1588
deprecated_callable(self.as_revision, because='Foolish consistency.')
1592
return _arch.revision_exists \
1593
(self._archive, self._version, self._patchlevel)
1595
def get(self, dir, link=False):
1596
"""Construct a project tree for this revision.
1598
Extract this revision from the archive.
1600
:param dir: path of the project tree to create. Must not
1603
:param link: hardlink files to revision library instead of copying
1605
:return: newly created project tree.
1606
:rtype: `WorkingTree`
1608
_arch.get(self.fullname, dir, link)
1609
return WorkingTree(dir)
1611
def get_patch(self, dir):
1612
"""Fetch the changeset associated to this revision.
1614
:param dir: name of the changeset directory to create. Must
1617
:return: changeset associated to this revision.
1620
_arch.get_patch(self.fullname, dir)
1621
return Changeset(dir)
1623
def get_patchlog(self):
1626
Patchlog associated to this revision.
1629
:see: `Revision.patchlog`
1631
deprecated_callable(self.get_patchlog, (Revision, 'patchlog'))
1632
return self.patchlog
1634
def _get_patchlog(self):
1635
return self.__patchlog
1637
patchlog = property(_get_patchlog, doc="""
1638
Patchlog associated to this revision.
1640
The `Patchlog` object is created in `__init__`, since log parsing is
1641
deferred that has little overhead and avoid parsing the log for a given
1642
revision several times. The patchlog data is read from the archive.
1645
:see: `ArchSourceTree.iter_logs`
1648
def make_continuation(self, target):
1649
"""Create a continuation of this revision in the target version.
1651
:param target: version to create a continuation into. If it does not
1652
exist yet, it is created.
1653
:type target: Version
1655
_check_version_param(target, 'target')
1656
target_name = target.fullname
1657
_arch.archive_setup(target_name)
1658
_arch.tag(self.fullname, target_name)
1660
def library_add(self, library=None):
1661
"""Add this revision to the library.
1663
:postcondition: self in self.version.iter_library_revisions()
1665
_arch.library_add(self.fullname, library)
1667
def library_remove(self):
1668
"""Remove this revision from the library.
1670
:precondition: self in self.version.iter_library_revisions()
1671
:postcondition: self not in self.version.iter_library_revisions()
1673
_arch.library_remove(self.fullname)
1675
def library_find(self):
1676
"""The copy of this revision in the library.
1678
:rtype: `LibraryTree`
1679
:precondition: self in self.version.iter_library_revisions()
1681
return LibraryTree(_arch.library_find(self.fullname))
1683
#def library_file(self, file):
1684
# raise NotImplementedError, "library_file is not yet implemented"
1686
def get_ancestor(self):
1692
- The previous namespace revision, if this revision is regular
1694
- The tag origin, if this revision is a continuation
1695
- ``None`` if this revision is an import.
1697
:rtype: `Revision` or None
1698
:see: `Revision.ancestor`
1700
deprecated_callable(self.get_ancestor, (Revision, 'ancestor'))
1701
return self.ancestor
1703
def _get_ancestor(self):
1704
rvsn = _arch.ancestor(self.fullname)
1708
return Revision(rvsn)
1710
ancestor = property(_get_ancestor, doc="""
1713
- The previous namespace revision, if this revision is regular commit.
1714
- The tag origin, if this revision is a continuation
1715
- ``None`` if this revision is an import.
1717
:type: `Revision` or None
1720
def get_previous(self):
1723
Previous namespace revision.
1725
:return: the previous revision in the same version, or None if this
1726
revision is a ``base-0``.
1727
:rtype: `Revision` or None
1728
:see: `Revision.previous`
1730
deprecated_callable(self.get_previous, (Revision, 'previous'))
1731
return self.previous
1733
def _get_previous(self):
1734
rvsn = _arch.previous(self.fullname)
1735
if rvsn is None: return None
1736
else: return Revision(rvsn)
1738
previous = property(_get_previous, doc="""
1739
Previous namespace revision.
1741
The previous revision in the same version, or None if this revision is a
1744
:type: `Revision` or None
1747
def iter_ancestors(self, metoo=False):
1748
"""Ancestor revisions.
1750
:param metoo: yield ``self`` as the first revision.
1752
:return: all the revisions in that line of development.
1753
:rtype: iterator of `Revision`
1755
i = _arch.iter_ancestry_graph(self.fullname)
1756
if not metoo: i.next() # the first revision is self
1757
for ancest, merge in i:
1758
yield Revision(ancest)
1760
def cache(self, cache=None):
1761
"""Cache a full source tree for this revision in its archive.
1763
:param cache: cache root for trees with pristines.
1766
_arch.cacherev(self.fullname, cache)
1769
"""Remove the cached tree of this revision from its archive."""
1770
_arch.uncacherev(self.fullname)
1772
def _file_url(self, name):
1773
return '/'.join([self.archive.location, self.category.nonarch,
1774
self.branch.nonarch, self.version.nonarch,
1775
self.patchlevel, name])
1777
_checksum_regex = re.compile(
1778
"^Signature-for: (.*)/(.*)\n"
1779
"([a-zA-Z0-9]+ [^/\\s]+ [a-fA-F0-9]+\n)*",
1782
def _parse_checksum(self, text):
1783
match = self._checksum_regex.search(text)
1784
checksum_body = match.group()
1785
checksum_lines = checksum_body.strip().split('\n')
1786
checksum_words = map(lambda x: x.split(), checksum_lines)
1787
assert checksum_words[0] == ['Signature-for:', self.fullname]
1788
del checksum_words[0]
1790
for algo, name, value in checksum_words:
1791
name_sums = checksums.setdefault(name, dict())
1792
name_sums[algo] = value
1795
def iter_files(self):
1796
"""Files stored in the archive for that revision.
1798
:rtype: iterable of `RevisionFile`
1801
for checksum_name, can_fail in \
1802
(('checksum', False), ('checksum.cacherev', True)):
1804
checksum_file = urllib.urlopen(self._file_url(checksum_name))
1806
if can_fail: continue
1809
checksum_data = checksum_file.read()
1811
checksum_file.close()
1812
yield RevisionFile(self, checksum_name, {})
1813
checksums = self._parse_checksum(checksum_data)
1814
for name, name_sums in checksums.items():
1815
yield RevisionFile(self, name, name_sums)
1818
public('RevisionFile')
1820
class RevisionFile(object):
1822
"""File component of an archived revision.
1824
:ivar revision: revision this file belongs to.
1825
:type revision: `Revision`
1826
:ivar name: name of that file.
1828
:ivar checksums: dictionnary whose keys are checksum algorithms
1829
(e.g. ``"md5"``, ``"sha1"``) and whose values are checksum
1830
values (strings of hexadecimal digits).
1831
:type checksums: dict of str to str
1834
def __init__(self, revision, name, checksums):
1835
self.revision = revision
1837
self.checksums = checksums
1839
def _get_data(self):
1841
my_file = urllib.urlopen(self.revision._file_url(self.name))
1843
ret = my_file.read()
1848
data = property(_get_data,
1849
"""Content of of that file.
1859
import email.Generator
1863
class Patchlog(object):
1865
"""Log entry associated to a revision.
1867
May be produced by `Revision.patchlog` or `ArchSourceTree.iter_logs()`. It
1868
provides an extensive summary of the associated changeset, a natural
1869
language description of the changes, and any number of user-defined
1872
Patchlogs are formatted according to RFC-822, and are parsed using the
1873
standard email-handling facilities.
1875
Note that the patchlog text is not actually parsed before it is needed.
1876
That deferred evaluation feature is implemented in the `_parse` method.
1878
The fundamental accessors are `__getitem__`, which give the text of a named
1879
patchlog header, and the `description` property which gives the text of the
1880
patchlog body, that is anything after the headers.
1882
Additional properties provide appropriate standard conversion of the
1886
def __init__(self, revision, tree=None, fromlib=False):
1887
"""Patchlog associated to the given revision.
1889
The patchlog may be retrieved from the provided ``tree``, from the
1890
revision library if ``fromlib`` is set, or from the archive.
1892
:param tree: source tree to retrieve the patchlog from.
1893
:type tree: `ArchSourceTree`, None
1894
:param fromlib: retrieve the patchlog from the revision library.
1895
:type fromblib: bool
1897
if isinstance(revision, Revision):
1898
self.__revision = revision.fullname
1899
elif isinstance(revision, _unsafe):
1900
self.__revision, = revision
1902
p = NameParser(revision)
1903
if not p.has_archive() or not p.has_patchlevel:
1904
raise errors.NamespaceError(revision,
1905
'fully-qualified revision')
1906
self.__revision = revision
1908
if tree is not None: self.tree = str(tree)
1909
self.fromlib = bool(fromlib)
1913
if self.tree is not None:
1914
return 'Patchlog(%s)' % repr(self.__revision)
1916
return 'Patchlog(%s, tree=%s)' % (repr(self.__revision),
1920
"""Deferred parsing of the log text."""
1921
if self.__email is not None:
1923
if self.tree is not None:
1924
s = _arch.cat_log(self.tree, self.__revision)
1926
s = _arch.library_log(self.__revision)
1928
s = _arch.cat_archive_log(self.__revision)
1929
self.__email = email.Parser.Parser().parsestr(s)
1932
def __getitem__(self, header):
1933
"""Text of a patchlog header by name.
1935
:param header: name of a patchlog header.
1937
:return: text of the header, or None if the header is not present.
1940
return self._parse()[header]
1943
"""List of 2-tuples containing all headers and values.
1945
:rtype: list of 2-tuple of str
1947
return self._parse().items()
1949
def get_revision(self):
1952
Revision associated to this patchlog.
1955
:see: `Patchlog.revision`
1957
deprecated_callable(self.get_revision, (Patchlog, 'revision'))
1958
return self.revision
1960
def _get_revision(self):
1961
assert self.__revision == self['Archive']+'/'+self['Revision']
1962
return Revision(self.__revision)
1964
revision = property(_get_revision, doc="""
1965
Revision associated to this patchlog.
1970
def get_summary(self):
1973
Patchlog summary, a one-line natural language description.
1976
:see: `Patchlog.summary`
1978
deprecated_callable(self.get_summary, (Patchlog, 'summary'))
1981
def _get_summary(self):
1982
return self['Summary']
1984
summary = property(_get_summary, doc="""
1985
Patchlog summary, a one-line natural language description.
1990
def get_description(self):
1993
Patchlog body, a long natural language description.
1996
:see: `Patchlog.description`
1998
deprecated_callable(self.get_description, (Patchlog, 'description'))
1999
return self.description
2001
def _get_description(self):
2003
assert not m.is_multipart()
2004
return m.get_payload()
2006
description = property(_get_description, doc="""
2007
Patchlog body, a long natural language description.
2015
For the description of the local time tuple, see the
2016
documentation of the `time` module.
2018
:rtype: local time tuple
2019
:see: `Patchlog.date`
2021
deprecated_callable(self.get_date, (Patchlog, 'date'))
2024
def _get_date(self):
2025
d = self['Standard-date']
2026
from time import strptime
2027
return strptime(d, '%Y-%m-%d %H:%M:%S %Z')
2029
date = property(_get_date, doc="""
2030
Time of the associated revision.
2032
For the description of the local time tuple, see the documentation
2033
of the `time` module.
2035
:type: local time tuple
2038
def get_creator(self):
2041
User id of the the creator of the associated revision.
2044
:see: `Patchlog.creator`
2046
deprecated_callable(self.get_creator, (Patchlog, 'creator'))
2049
def _get_creator(self):
2050
return self['Creator']
2052
creator = property(_get_creator, doc="""
2053
User id of the the creator of the associated revision.
2058
def get_continuation(self):
2061
Ancestor of tag revisions.
2062
None for commit and import revisions.
2064
:rtype: `Revision`, None.
2065
:see: `Patchlog.continuation_of`
2067
deprecated_callable(
2068
self.get_continuation, (Patchlog, 'continuation_of'))
2069
return self.continuation_of
2071
def _get_continuation(self):
2072
deprecated_callable(
2073
(Patchlog, 'continuation'), (Patchlog, 'continuation_of'))
2074
return self.continuation_of
2076
continuation = property(_get_continuation, doc="""
2079
Ancestor of tag revisions.
2080
None for commit and import revisions.
2082
:type: `Revision`, None.
2083
:see: `Patchlog.continuation_of`
2086
def _get_continuation_of(self):
2087
c = self['Continuation-of']
2092
continuation_of = property(_get_continuation_of, doc="""
2093
Ancestor of tag revisions.
2094
None for commit and import revisions.
2096
:type: `Revision`, None.
2099
def new_merge_revision(self, revision_name):
2100
"""Helper to produce a correct output even on broken baz logs
2102
:param revision_name: The name of a revision to produce
2103
:type revision_name: str
2105
if revision_name == "!!!!!nothing-should-depend-on-this":
2106
return self.revision
2108
return Revision(revision_name)
2110
def get_new_patches(self):
2113
New-patches header as an iterable of Revision.
2115
:rtype: iterable of `Revision`
2116
:see: `Patchlog.new_patches`
2118
deprecated_callable(
2119
Patchlog.get_new_patches, (Patchlog, 'new_patches'))
2120
return self.new_patches
2122
def _get_new_patches(self):
2123
return map(self.new_merge_revision, self['New-patches'].split())
2125
new_patches = property(_get_new_patches, doc="""
2126
New-patches header as an iterable of Revision.
2128
Patchlogs added in this revision.
2130
:type: iterable of `Revision`
2133
def get_merged_patches(self):
2136
Revisions merged in this revision. That is the revisions
2137
listed in the New-patches header except the revision of the
2140
:rtype: iterable of `Revision`
2141
:see: `Patchlog.merged_patches`
2143
deprecated_callable(
2144
Patchlog.get_merged_patches, (Patchlog, 'merged_patches'))
2145
return self.merged_patches
2147
def _get_merged_patches(self):
2148
rvsn = self.revision.fullname
2149
return filter(lambda(r): r.fullname != rvsn, self.new_patches)
2151
merged_patches = property(_get_merged_patches, doc="""
2152
Revisions merged in this revision.
2154
That is the revisions listed in the New-patches header except the
2155
revision of the patchlog.
2157
:type: iterable of `Revision`
2160
def get_new_files(self):
2163
Source files added in the associated revision.
2165
:rtype: iterable of `FileName`
2166
:see: `Patchlog.new_files`
2168
deprecated_callable(self.get_new_files, (Patchlog, 'new_files'))
2169
return self.new_files
2171
def _get_new_files(self):
2172
s = self['New-files']
2173
if s is None: return []
2174
return [ FileName(name_unescape(f)) for f in s.split() ]
2176
new_files = property(_get_new_files, doc="""
2177
Source files added in the associated revision.
2179
:type: iterable of `FileName`
2182
def get_modified_files(self):
2185
Names of source files modified in the associated revision.
2187
:rtype: iterable of `FileName`
2189
deprecated_callable(
2190
self.get_modified_files, (Patchlog, 'modified_files'))
2191
return self.modified_files
2193
def _get_modified_files(self):
2194
s = self['Modified-files']
2195
if s is None: return []
2196
return [ FileName(name_unescape(f)) for f in s.split() ]
2198
modified_files = property(_get_modified_files, doc="""
2199
Names of source files modified in the associated revision.
2201
:type: iterable of `FileName`
2204
def get_renamed_files(self):
2207
Source files renames in the associated revision.
2209
Dictionnary whose keys are old names and whose values are the
2210
corresponding new names. Explicit file ids are listed in
2211
addition to their associated source file.
2215
deprecated_callable(
2216
self.get_renamed_files, (Patchlog, 'renamed_files'))
2217
return self.renamed_files
2219
def _get_renamed_files(self):
2220
s = self['Renamed-files']
2225
for i in range(len(s)/2):
2226
renames[s[i*2]] = s[i*2+1]
2229
renamed_files = property(_get_renamed_files, doc="""
2230
Source files renames in the associated revision.
2232
Dictionnary whose keys are old names and whose values are the
2233
corresponding new names. Explicit file ids are listed in
2234
addition to their associated source file.
2239
def get_removed_files(self):
2242
Names of source files removed in the associated revision.
2244
:rtype: iterable of `FileName`
2246
deprecated_callable(
2247
self.get_removed_files, (Patchlog, 'removed_files'))
2248
return self.removed_files
2250
def _get_removed_files(self):
2251
s = self['Removed-files']
2252
if s is None: return []
2253
return [ FileName(name_unescape(f)) for f in s.split() ]
2255
removed_files = property(_get_removed_files, doc="""
2256
Names of source files removed in the associated revision.
2258
:type: iterable of `FileName`
2262
public('LogMessage')
2264
class LogMessage(object):
2266
"""Log message for use with commit, import or tag operations.
2268
This is the write-enabled counterpart of Patchlog. When creating a new
2269
revision with import, commit or tag, a log message file can be used to
2270
specify a long description and custom headers.
2272
Commit and import can use the default log file of the source tree, with a
2273
special name. You can create the LogMessage object associated to the
2274
default log file with the WorkingTree.log_message method.
2276
For integration with external tools, it is useful to be able to parse an
2277
existing log file and write the parsed object back idempotently. We are
2278
lucky since this idempotence is provided by the standard email.Parser and
2282
def __init__(self, name):
2285
self.__dirty = False
2287
def get_name(self): return self.__name
2288
name = property(get_name)
2291
"""Read the log message from disk."""
2292
self.__email = email.Parser.Parser().parse(file(self.__name))
2293
self.__dirty = False
2296
"""Write the log message to disk."""
2298
f = file(self.__name, 'w')
2299
email.Generator.Generator(f).flatten(self.__email)
2300
self.__dirty = False
2303
"""Clear the in-memory log message.
2305
When creating a new log message file, this method must be used
2306
first before setting the message parts. That should help early
2307
detection of erroneous log file names.
2309
self.__email = email.Message.Message()
2312
def __getitem__(self, header):
2313
"""Text of a patchlog header by name."""
2314
if self.__email is None: self.load()
2315
return self.__email[header]
2317
def __setitem__(self, header, text):
2318
"""Set a patchlog header."""
2319
if self.__email is None: self.load()
2321
self.__email.replace_header(header, text)
2323
self.__email.add_header(header, text)
2326
def get_description(self):
2327
"""Body of the log message."""
2328
if self.__email is None: self.load()
2329
assert not self.__email.is_multipart()
2330
return self.__email.get_payload()
2332
def set_description(self, s):
2333
"""Set the body of the log message."""
2334
if self.__email is None: self.load()
2335
self.__email.set_payload(s)
2338
description = property(get_description, set_description)
2341
### Source trees ###
2349
def init_tree(directory, version=None, nested=False):
2350
"""Initialize a new project tree.
2352
:param directory: directory to initialize as a source tree.
2353
:type directory: str
2354
:param version: if given, set the the ``tree-version`` and create an empty
2356
:type version: `Version`
2357
:param nested: if true, the command will succeed even if 'directory'
2358
is already within a source tree.
2360
:return: source tree object for the given directory.
2361
:rtype: `WorkingTree`
2363
if version is not None:
2364
version = _version_param(version)
2365
_arch.init_tree(directory, version, nested)
2366
return WorkingTree(directory)
2369
def in_source_tree(directory=None):
2370
"""Is directory inside a Arch source tree?
2372
:param directory: test if that directory is in an Arch source tree.
2373
:type directory: str
2374
:return: whether this directory is inside an Arch source tree.
2377
:warning: omitting the ``directory`` argument is deprecated.
2379
if directory is None:
2381
in_source_tree, "Argument defaults to current direcotry.")
2383
return _arch.in_tree(directory)
2386
def tree_root(directory=None):
2387
"""SourceTree containing the given directory.
2389
:param directory: give the ``tree-root`` of this directory. Specify "." to
2390
get the ``tree-root`` of the current directory.
2391
:type directory: str
2392
:return: source tree containing ``directory``.
2393
:rtype: `ArchSourceTree`
2395
:warning: omitting the ``directory`` argument is deprecated.
2397
if directory is None:
2398
deprecated_usage(tree_root, "Argument defaults to current directory.")
2400
root = _arch.tree_root(directory)
2401
# XXX use SourceTree to instanciate either WorkingTree or LibraryTree
2402
return SourceTree(root)
2405
public('SourceTree',
2411
class SourceTree(DirName):
2413
"""Abstract base class for `ForeignTree` and `ArchSourceTree`."""
2415
def __new__(cls, root=None):
2416
"""Create a source tree object for the given root path.
2418
`ForeignTree` if root does not point to a Arch source tree.
2419
`LibraryTree` if root is a tree in the revision library. `WorkingTree`
2420
if root is a Arch source tree outside of the revision library.
2422
If root is omitted, use the tree-root of the current working directory.
2424
__pychecker__ = 'no-returnvalues'
2425
# checking root actually is a root done by LibraryTree and WorkingTree
2426
if cls is not SourceTree:
2427
assert root is not None
2428
return DirName.__new__(cls, root)
2431
root = _arch.tree_root(os.getcwd())
2432
root = DirName(root).realpath()
2433
if _arch.is_tree_root(root):
2434
for revlib in _arch.iter_revision_libraries():
2435
if root.startswith(revlib):
2436
return LibraryTree(root)
2437
return WorkingTree(root)
2439
assert not _arch.in_tree(root)
2440
return ForeignTree(root)
2443
class ForeignTree(SourceTree):
2444
"""Generic source tree without Arch support.
2446
Unlike Arch source trees, the root may not even exist.
2448
def __init__(self, root): pass
2451
class ArchSourceTree(SourceTree):
2453
"""Abstract base class for Arch source trees."""
2455
def get_tree_version(self):
2456
"""Default version of the tree, also called tree-version.
2458
:raise errors.TreeVersionError: no valid default version set.
2459
:raise IOError: unable to read the ++default-version file.
2461
data = _arch.tree_version(self)
2463
raise errors.TreeVersionError(self)
2465
return Version(data)
2466
except errors.NamespaceError:
2467
raise errors.TreeVersionError(self, data)
2468
tree_version = property(get_tree_version)
2470
def _get_tree_revision(self):
2471
"""Revision of the last patchlog for the tree-version.
2473
:raise errors.TreeVersionError: no valid default version set.
2474
:raise IOError: unable to read the ++default-version file.
2476
reverse_logs = self.iter_logs(reverse=True)
2477
try: last_log = reverse_logs.next()
2478
except StopIteration:
2479
raise RuntimeError('no logs for the tree-version')
2480
return last_log.revision
2481
tree_revision = property(_get_tree_revision)
2483
def check_is_tree_root(self):
2484
assert os.path.isdir(self)
2485
if not _arch.is_tree_root(self):
2486
raise Exception, "%s is not a tla project tree" % self
2488
def get_tagging_method(self):
2489
return _arch.tagging_method(self)
2490
tagging_method = property(get_tagging_method)
2492
def __inventory_filter(self, trees, directories, files, both):
2493
assert sum(map(bool, (trees, directories, files, both))) == 1
2494
if trees: return lambda(t): SourceTree(self/name_unescape(t))
2495
elif directories: return lambda(d): DirName(name_unescape(d))
2496
elif files: return lambda(f): FileName(name_unescape(f))
2499
if os.path.isdir(self/f): return DirName(name_unescape(f))
2500
else: return FileName(name_unescape(f))
2503
raise AssertionError, 'not reached'
2505
def iter_inventory(self, source=False, precious=False, backups=False,
2506
junk=False, unrecognized=False, trees=False,
2507
directories=False, files=False, both=False,
2508
names=False, limit=None):
2511
The kind of files looked for is specified by setting to
2512
``True`` exactly one of the following keyword arguments:
2514
``source``, ``precious``, ``backups``, ``junk``,
2515
``unrecognized``, ``trees``.
2517
If the ``trees`` argument is not set, whether files, directory
2518
or both should be listed is specified by setting to ``True``
2519
exactly one of the following keyword arguments:
2521
``directories``, ``files``, ``both``.
2523
:keyword source: list source files only.
2524
:keyword precious: list precious files only.
2525
:keyword backups: list backup files only.
2526
:keyword junk: list junk files only.
2527
:keyword unrecognized: list unrecognized files only.
2528
:keyword trees: list nested trees only. If this is true, the
2529
iterator wil only yield `ArchSourceTree` objects.
2531
:keyword directories: list directories only,
2532
yield only `FileName` objects.
2533
:keyword files: list files only, yield only `DirName` objects.
2534
:keyword both: list both files and directories,
2535
yield both FileName and `DirName` objects.
2537
:keyword name: do inventory as if the id-tagging-method was
2538
set to ``names``. That is useful to ignore
2539
``untagged-source`` classification.
2541
:keyword limit: restrict the inventory to this directory. Must
2542
be the name of a directory relative to the tree root.
2544
:rtype: iterator of `FileName`, `DirName`, `ArchSourceTree`
2545
according to the arguments.
2547
__pychecker__ = 'maxargs=12'
2548
if limit is not None: _check_relname_param(limit, 'limit')
2549
return itertools.imap\
2550
(self.__inventory_filter(trees, directories, files, both),
2551
_arch.iter_inventory(self, source, precious, backups,
2552
junk, unrecognized, trees,
2553
directories, files, both, names, limit))
2555
def iter_inventory_ids(self,source=False, precious=False, backups=False,
2556
junk=False, unrecognized=False, trees=False,
2557
directories=False, files=False, both=False,
2559
"""Tree inventory with file ids.
2561
:see: `ArchSourceTree.iter_inventory`
2563
:rtype: iterator of tuples ``(id, item)``, where ``item`` is
2564
`FileName`, `DirName`, `ArchSourceTree` according to the
2565
arguments and ``id`` is the associated inventory id.
2567
if limit is not None: _check_relname_param(limit, 'limit')
2568
f = self.__inventory_filter(trees, directories, files, both)
2569
def ff(t): name, id_ = t; return id_, f(name)
2570
return itertools.imap\
2571
(ff, _arch.iter_inventory_ids(self, source, precious, backups,
2572
junk, unrecognized, trees,
2573
directories, files, both, limit))
2575
def inventory(self, *args, **kw):
2576
"""Inventory of the source tree as a list. See iter_inventory.
2580
return list (self.iter_inventory(*args, **kw))
2583
return util.sorttree(self.inventory(source=True, both=True))
2585
def iter_log_versions(self, limit=None, reverse=False):
2586
"""Iterate over versions this tree has a log-version for.
2588
:param limit: only iterate log-versions in this namespace.
2589
:type limit: `Archive`, `Category`, `Branch`, `Version`
2590
:param reverse: yield log-versions in reverse order.
2594
if isinstance(limit, Archive):
2595
kwargs['archive'] = limit.name
2596
elif isinstance(limit, (Category, Branch, Version)):
2597
kwargs['archive'] = limit.archive.name
2598
if isinstance(limit, Category): kwargs['category'] = limit.nonarch
2599
elif isinstance(limit, Branch): kwargs['branch'] = limit.nonarch
2600
elif isinstance(limit, Version): kwargs['version'] = limit.nonarch
2601
elif limit is not None and not isinstance(limit, Archive):
2602
raise TypeError("Expected Archive, Category, Branch or Version"
2603
" but got: %r" % limit)
2604
for vsn in _arch.iter_log_versions(self, reverse=reverse, **kwargs):
2607
def iter_logs(self, version=None, reverse=False):
2608
"""Iterate over patchlogs present in this tree.
2610
:param version: list patchlogs from this version. Defaults to
2612
:type version: `Version`
2613
:param reverse: iterate more recent logs first.
2615
:return: patchlogs from ``version``.
2616
:rtype: iterator of `Patchlog`.
2617
:raise errors.TreeVersionError: no valid default version set.
2618
:raise IOError: unable to read the ++default-version file.
2620
if version is None: version = self.tree_version.fullname
2621
else: version = _version_param(version)
2622
for rvsn in _arch.iter_log_ls(self, version, reverse=reverse):
2623
yield Patchlog(_unsafe((rvsn,)), tree=self)
2625
def get_tag(self, file):
2626
"""File id set by an explicit id tag or tagline.
2628
:param file: name of a source file relative to the tree-root.
2630
:return: file id if the file has an explicit id or a tagline.
2633
if not os.path.exists(self/file) and not os.path.islink(self/file):
2634
raise IOError(2, 'No such file or directory', file)
2635
return _arch.get_tag(self/file)
2637
def file_find(self, name, revision):
2638
"""Find a pristine copy of a file for a given revision.
2640
Will create the requested revision in the library or in the
2641
current tree pristines if needed.
2643
:param name: name of the file.
2645
:param revision: revision to look for the file into.
2646
:type revision: `Revision`
2647
:return: absolute path name of a pristine copy of the file.
2648
:rtype: `pathname.PathName`
2649
:raise errors.MissingFileError: file is not source or is not
2650
present in the specified revision.
2652
revision = _revision_param(revision)
2653
result = _arch.file_find(self, name, revision)
2655
raise errors.MissingFileError(self, name, revision)
2656
# XXX Work around relative path when using pristines
2657
result = os.path.join(self, result)
2658
return PathName(result)
2661
class LibraryTree(ArchSourceTree):
2663
"""Read-only Arch source tree."""
2665
def __init__(self, root):
2666
"""Create a LibraryTree object with the given root path.
2668
Root must be a directory containing a Arch source tree in the
2671
ArchSourceTree.__init__(self, root)
2672
self.check_is_tree_root()
2675
class WorkingTree(ArchSourceTree):
2677
"""Working source tree, Arch source tree which can be modified."""
2679
def __init__(self, root):
2680
"""Create WorkingTree object with the given root path.
2682
Root must be a directory containing a valid Arch source tree
2683
outside of the revision library.
2685
ArchSourceTree.__init__(self, root)
2686
self.check_is_tree_root()
2688
def sync_tree(self, revision):
2689
"""Adds the patchlogs in the given revision to the current tree.
2691
Create a temporary source tree for ``revision``, then add all the
2692
patchlogs present in that tree to the current tree. No content
2693
is touched besides the patchlogs.
2695
:param revision: revision to synchronize with.
2696
:type revision: `Version`, `Revision` or str
2697
:raise errors.NamespaceError: ``revision`` is not a valid
2698
version or revision name.
2700
revision = _version_revision_param(revision)
2701
_arch.sync_tree(self, revision)
2703
def set_tree_version(self, version):
2704
version = _version_param(version)
2705
_arch.set_tree_version(self, version)
2706
tree_version = property(ArchSourceTree.get_tree_version, set_tree_version)
2708
def has_changes(self):
2709
"""Are there uncommited changes is this source tree?
2713
return _arch.has_changes(self)
2715
def changes(self, revision=None, output=None):
2716
"""Uncommited changes in this tree.
2718
:param revision: produce the changeset between this revision
2719
and the tree. If ``revision`` is ``None``, use the
2721
:type revision: `Revision`, None
2722
:param output: absolute path of the changeset to produce.
2723
:type output: string
2724
:return: changeset between ``revision`` and the tree.
2726
:raise errors.TreeVersionError: no valid default version set.
2727
:raise IOError: unable to read the ++default-version file.
2729
if revision is None: revision = self.tree_revision
2730
_check_str_param(output, 'output')
2731
_check_revision_param(revision, 'revision')
2732
return delta(revision, self, output)
2735
def star_merge(self, from_=None, reference=None, forward=False,
2737
"""Merge mutually merged branches.
2739
:param from_: branch to merge changes from, ``None`` means the
2741
:type from_: None, `Version`, `Revision`, or str
2742
:param reference: reference version for the merge, ``None``
2743
means the ``tree-version``.
2744
:type reference: None, `Version` or str
2745
:param forward: ignore already applied patch hunks.
2747
:param diff3: produce inline conflict markers instead of
2750
:raise errors.NamespaceError: ``from_`` or ``reference`` is
2751
not a valid version or revision name.
2753
list(self.iter_star_merge(from_, reference, forward, diff3))
2755
def iter_star_merge(self, from_=None, reference=None,
2756
forward=False, diff3=False):
2757
"""Merge mutually merged branches.
2759
:bug: if the merge causes a conflict, a RuntimeError is
2760
raised. You should not rely on this behaviour as it is
2761
likely to change in the future. If you want to support
2762
conflicting merges, use `iter_star_merge` instead.
2764
:param `from_`: branch to merge changes from, ``None`` means the
2766
:type `from_`: None, `Version`, `Revision`, or str
2767
:param reference: reference version for the merge, ``None``
2768
means the ``tree-version``.
2769
:type reference: None, `Version` or str
2770
:param forward: ignore already applied patch hunks.
2772
:param diff3: produce inline conflict markers instead of
2775
:raise errors.NamespaceError: ``from_`` or ``reference`` is
2776
not a valid version or revision name.
2777
:return: `MergeOutcome` iterator
2778
:rtype: `iter_changes`
2780
if from_ is not None:
2781
from_ = _version_revision_param(from_)
2782
if reference is not None:
2783
reference = _version_revision_param(reference)
2784
_arch.star_merge(self, from_, reference, forward, diff3)
2786
def iter_star_merge(self, from_=None, reference=None,
2787
forward=False, diff3=False):
2788
"""Merge mutually merged branches.
2790
:param `from_`: branch to merge changes from, ``None`` means the
2792
:type `from_`: None, `Version`, `Revision`, or str
2793
:param reference: reference version for the merge, ``None``
2794
means the ``tree-version``.
2795
:type reference: None, `Version` or str
2796
:param forward: ignore already applied patch hunks.
2798
:param diff3: produce inline conflict markers instead of
2801
:raise errors.NamespaceError: ``from_`` or ``reference`` is
2802
not a valid version or revision name.
2803
:rtype: `ChangesetApplication`
2805
# the --changes option must be supported by another method
2806
# which returns a Changeset.
2807
if from_ is not None:
2808
from_ = _version_revision_param(from_)
2809
if reference is not None:
2810
reference = _version_revision_param(reference)
2811
return ChangesetApplication(
2812
_arch.iter_star_merge(self, from_, reference, forward, diff3))
2814
def undo(self, revision=None, output=None, quiet=False, throw_away=False):
2815
"""Undo and save changes in a project tree.
2817
Remove local changes since revision and optionally save them
2820
:keyword revision: revision to revert to. Default to the last
2821
revision of the tree-version for which a patchlog is present.
2822
:type revision: `Revision`, str
2823
:keyword output: name of the output changeset directory. Must
2824
not already exist. Default to an automatic ,,undo-N name
2825
in the working tree.
2827
:keyword quiet: OBSOLETE. Incremental output is always discarded.
2829
:keyword throw_away: discard the output changeset and return
2830
``None``. Must not be used at the same time as ``output``.
2831
:type throw_away: bool
2832
:return: changeset restoring the undone changes,
2833
or None if ``throw_away``.
2834
:rtype: `Changeset`, None
2836
assert sum(map(bool, (output, throw_away))) < 2
2838
output = util.new_numbered_name(self, ',,undo-')
2839
if revision is not None:
2840
revision = _revision_param(revision)
2841
_arch.undo(self, revision, output, quiet, throw_away)
2842
if throw_away: return None
2843
return Changeset(output)
2845
def redo(self, patch=None, keep=False, quiet=False):
2846
"""Redo changes in a project tree.
2848
Apply patch to the project tree and delete patch.
2850
If patch is provided, it must be a Changeset object. Else, the highest
2851
numbered ,,undo-N directory in the project tree root is used.
2853
If keep is true, the patch directory is not deleted.
2855
_arch.redo(self, patch, keep, quiet)
2857
def set_tagging_method(self, method):
2858
_arch.set_tagging_method(self, method)
2859
tagging_method = property(ArchSourceTree.get_tagging_method,
2862
def add_tag(self, file):
2863
_arch.add(self/file)
2865
def move_tag(self, src, dest):
2866
_arch.move(self/src, self/dest)
2868
def move_file(self, src, dest):
2869
# FIXME: move explicit tag if present
2870
dest = PathName(dest)
2871
assert os.path.exists((self/dest).dirname())
2872
os.rename(self/src, self/dest)
2874
def delete(self, file):
2875
fullfile = self/file
2876
if os.path.isfile(fullfile):
2877
if _arch.has_explicit_id(fullfile):
2878
_arch.delete(fullfile)
2880
elif os.path.isdir(fullfile):
2881
shutil.rmtree(fullfile)
2883
def del_file(self, file):
2884
fullfile = self/file
2885
if os.path.isfile(fullfile):
2888
shutil.rmtree(fullfile)
2890
def del_tag(self, file):
2891
if not os.path.islink(self/file):
2892
assert os.path.exists(self/file)
2893
_arch.delete(self/file)
2895
def import_(self, log=None):
2896
"""Archive a full-source base-0 revision.
2898
If log is specified, it must be a LogMessage object or a file
2899
name as a string. If omitted, the default log message file of
2902
The --summary, --log-message and --setup options to tla are
2903
mere CLI convenience features and are not directly supported.
2905
if isinstance(log, LogMessage):
2908
assert log is None or isinstance(log, str)
2909
_arch.import_(self, log)
2911
def commit(self, log=None, strict=False, seal=False, fix=False,
2912
out_of_date_ok=False, file_list=None, version=None):
2913
"""Archive a changeset-based revision.
2915
:keyword version: version in which to commit the revision.
2916
Defaults to the `tree_version`.
2917
:type version: `Version`, str
2918
:keyword log: Log message for this revision. Defaults to the log
2919
message file of the tree-version (the file created by
2921
:type log: `LogMessage`
2922
:keyword strict: perform a strict tree-lint before commiting.
2924
:keyword seal: create a ``version-0`` revision.
2926
:keyword fix: create a ``versionfix`` revision.
2928
:keyword out_of_date_ok: commit even if the tree is out of
2930
:type out_of_date_ok: bool
2931
:keyword file_list: Only commit changes to those files,
2932
specified relative to the tree-root.
2933
:type file_list: iterable of str, with at least one item.
2935
The --summary and --log-message options to tla are mere CLI
2936
convenience features and are not directly supported.
2938
:see: `WorkingTree.iter_commit`
2940
for unused in self.iter_commit(
2941
log, strict, seal, fix, out_of_date_ok, file_list, version):
2944
def log_for_merge(self):
2945
"""Standard arch log of newly merged patches.
2949
return _arch.log_for_merge(self)
2952
def iter_commit(self, log=None, strict=False, seal=False, fix=False,
2953
out_of_date_ok=False, file_list=None, version=None,
2955
"""Archive a changeset-based revision, returning an iterator.
2958
:keyword version: version in which to commit the revision.
2959
Defaults to the `tree_version`.
2960
:type version: `Version`, str
2961
:keyword log: Log message for this revision. Defaults to the log
2962
message file of the tree-version (the file created by
2964
:type log: `LogMessage`
2965
:keyword strict: perform a strict tree-lint before commiting.
2967
:keyword seal: create a ``version-0`` revision.
2969
:keyword fix: create a ``versionfix`` revision.
2971
:keyword out_of_date_ok: commit even if the tree is out of
2973
:type out_of_date_ok: bool
2974
:keyword file_list: Only commit changes to those files,
2975
specified relative to the tree-root.
2976
:type file_list: iterable of str, with at least one item.
2977
:rtype: iterator of `TreeChange`, `Chatter` or str
2979
The --summary and --log-message options to tla are mere CLI
2980
convenience features and are not directly supported.
2982
:see: `WorkingTree.commit`
2985
version = self.tree_version.fullname
2987
version = _version_param(version)
2988
if isinstance(log, LogMessage):
2991
assert log is None or isinstance(log, str)
2993
log = os.path.abspath(log)
2994
iterator = _arch.iter_commit(
2995
self, version, log, strict, seal, fix, out_of_date_ok, file_list,
2997
return classify_changeset_creation(iterator)
2999
def log_message(self, create=True, version=None):
3000
"""Default log-message object used by import and commit.
3002
If `create` is False, and the standard log file does not already
3003
exists, return None. If `create` is True, use ``tla make-log`` if
3006
if version is not None:
3007
version = str(version)
3008
name = _arch.log_name(self, version)
3009
if not os.path.exists(name):
3010
if not create: return None
3011
_arch.make_log(self, version)
3012
return LogMessage(name)
3014
def add_log_version(self, version):
3015
"""Add a patch log version to the project tree."""
3016
version = _version_param(version)
3017
_arch.add_log_version(self, version)
3019
def remove_log_version(self, version):
3020
"""Remove a patch log version from the project tree."""
3021
version = _version_param(version)
3022
_arch.remove_log_version(self, version)
3025
"""Replay changesets into this working tree."""
3029
"""Apply delta of new revisions in the archive.
3031
Apply delta(A,B) on this working tree, where A and B are both
3032
revisions of the tree version, A is the latest whose patchlog
3033
is present in the tree and B is the latest present in the
3038
def iter_pristines(self):
3039
"""Pristines present in that source tree.
3041
:return: Revisions which have pristine trees in that source tree.
3042
:rtype: iterable of `Revision`
3044
for rvsn in _arch.iter_pristines(self):
3045
yield Revision(rvsn)
3047
def add_pristine(self, revision):
3048
"""Ensure that the project tree has a particular pristine revision.
3050
:param revision: revision to add a pristine for.
3051
:type revision: `Revision`
3053
revision = _revision_param(revision)
3054
_arch.add_pristine(self, revision)
3056
def find_pristine(self, revision):
3057
"""Path to a pristine tree for the given revision.
3059
:param revision: find a pristine for that revision.
3060
:type revision: `Revision`
3061
:rtype: `ArchSourceTree`
3062
:raise errors.NoPristineFoundError: no pristine tree was found
3063
for the given revision.
3065
if revision not in self.iter_pristines():
3066
raise errors.NoPristineFoundError(self, revision)
3067
revision = _revision_param(revision)
3068
return ArchSourceTree(_arch.find_pristine(self, revision))
3075
class Changeset(DirName):
3077
"""Arch whole-tree changeset."""
3079
def __init__(self, name):
3080
DirName.__init__(self, name)
3082
self.__index_excl = {}
3083
self.__metadata = {}
3084
self.exclude_re = re.compile('^[AE]_')
3086
def get_index(self, name, all=False):
3087
"""Load and parse an index file from the changeset.
3089
Expectable indexes are:
3090
mod-dirs mod-files orig-dirs orig-files (more?)
3093
if name in self.__index: return self.__index[key]
3095
not_exclude = lambda(id_): True
3097
not_exclude = lambda(id_): not self.exclude_re.match(id_)
3099
fullname = self/(name + '-index')
3100
if os.path.exists(fullname):
3101
index_file = open(fullname)
3103
for line in index_file:
3104
n, id_ = map(lambda(s): s.strip(), line.split())
3105
if not_exclude(id_):
3106
retval[id_] = os.path.normpath(name_unescape(n))
3109
self.__index[key] = retval
3112
def _get_does_nothing(self):
3113
"""Is the changeset a no-op?"""
3114
# TODO: checking that all indices are empty can yield false negatives
3115
# rather we should check for the effective absence of changes.
3116
for index_name in ('mod-files', 'orig-files', 'mod-dirs', 'orig-dirs'):
3117
index = self.get_index(index_name, True)
3118
if index: return False
3120
does_nothing = property(_get_does_nothing)
3122
# def get_metadata(self, name):
3123
# """Load and parse a metadata file from the changeset.
3125
# Expectable metadata tables are:
3126
# modified-only-dir original-only-dir (more?)
3128
# raise NotImplementedError, "not yet implemented"
3129
# if name in self.__metadata: return self.__metadata[name]
3131
# fullname = self/(name + '-metadata')
3132
# if os.path.exists(fullname):
3133
# for line in open(fullname):
3134
# pass # Not implemented
3135
# self.__metadata[name] = retval
3138
def __iter_mod_helper(self, what, all):
3139
__pychecker__ = 'no-abstract' # for ImmutableSet
3140
orig_index = self.get_index('orig-' + what, all)
3141
mod_index = self.get_index('mod-' + what, all)
3142
orig_ids = ImmutableSet(orig_index.iterkeys())
3143
mod_ids = ImmutableSet(mod_index.iterkeys())
3144
for id_ in orig_ids & mod_ids:
3145
yield (id_, orig_index[id_], mod_index[id_])
3147
def iter_mod_files(self, all=False):
3148
"""Iterator over (id, orig, mod) tuples for files which are are
3149
patched, renamed, or have their permissions changed."""
3150
return self.__iter_mod_helper('files', all)
3152
def iter_patched_files(self, all=False):
3153
"""Iterate over (id, orig, mod) of patched files."""
3154
patchdir = self/'patches'
3155
for f in self.iter_mod_files(all):
3156
if os.path.isfile(patchdir/f[1]+'.patch'):
3159
print "file does not exists: %s" % str(patchdir/f[1]+'.patch')
3160
#return itertools.ifilter(lambda(f): os.path.isfile(patchdir/f[2]),
3161
# self.iter_mod_files())
3163
def patch_file(self, modname):
3164
return self/'patches'/modname+'.patch'
3166
def __iter_renames_helper(self, what):
3167
for id_, orig, mod in self.__iter_mod_helper(what, all=False):
3169
yield (id_, orig, mod)
3171
def iter_renames(self):
3172
"""Iterate over (id, orig, dest) triples representing renames.
3174
id is the persistant file tag, and the key of the dictionnary.
3175
orig is the name in the original tree.
3176
dest is the name in the modified tree.
3178
from itertools import chain
3179
return chain(*map(self.__iter_renames_helper, ('files', 'dirs')))
3181
def __iter_created_helper(self, what, removed=False, all=False):
3182
__pychecker__ = 'no-abstract' # for ImmutableSet
3183
orig_index = self.get_index('orig-' + what, all)
3184
mod_index = self.get_index('mod-' + what, all)
3185
if removed: orig_index, mod_index = mod_index, orig_index
3186
orig_ids = ImmutableSet(orig_index.iterkeys())
3187
mod_ids = ImmutableSet(mod_index.iterkeys())
3188
for id_ in mod_ids - orig_ids:
3189
yield (id_, mod_index[id_])
3191
def iter_created_files(self, all=False):
3192
"""Iterator over tuples (id, dest) for created files.
3194
:param all: include Arch control files.
3197
return self.__iter_created_helper('files', all=all)
3199
def created_file(self, name):
3200
return self/'new-files-archive'/name
3202
def iter_removed_files(self, all=False):
3203
"""Iterator over tuples (id, orig) for removed files.
3205
:param all: include Arch control files.
3208
return self.__iter_created_helper('files', removed=True, all=all)
3210
def removed_file(self, name):
3211
return self/'removed-files-archive'/name
3213
def iter_created_dirs(self, all=False):
3214
"""Iterator over tuples (id, dest) for created directories.
3216
:param all: include Arch control files.
3219
return self.__iter_created_helper('dirs', all=all)
3221
def iter_removed_dirs(self, all=False):
3222
"""Iterator over tuples (id, orig) for removed directories.
3224
:param all: include Arch control files.
3227
return self.__iter_created_helper('dirs', removed=True, all=all)
3229
def iter_apply(self, tree, reverse=False):
3230
"""Apply this changeset to a tree, with incremental output.
3232
:param tree: the tree to apply changes to.
3233
:type tree: `WorkingTree`
3234
:param reverse: invert the meaning of the changeset; adds
3235
become deletes, etc.
3237
:rtype: `ChangesetApplication`
3239
_check_working_tree_param(tree, 'tree')
3240
return ChangesetApplication(
3241
_arch.iter_apply_changeset(self, tree, reverse))
3246
def changeset(orig, mod, dest):
3252
deprecated_callable(changeset, delta)
3253
return delta(ArchSourceTree(orig), ArchSourceTree(mod), dest)
3256
def _tree_or_existing_revision_param(param, name):
3257
if isinstance(param, Revision):
3258
# should check for actual existence, but that is expensive
3259
if not param.archive.is_registered():
3260
raise errors.ArchiveNotRegistered(param.archive)
3261
return param.fullname
3262
elif isinstance(param, ArchSourceTree):
3265
raise TypeError("Parameter \"%s\" must be ArchSourceTree or Revision"
3266
" but was: %r" % (name, param))
3268
def _check_str_param(param, name):
3269
"""Internal argument checking utility"""
3270
if not isinstance(param, basestring):
3271
raise TypeError("Parameter \"%s\" must be a string"
3272
" but was: %r" % (name, param))
3274
def _check_working_tree_param(param, name):
3275
"""Internal argument checking utility"""
3276
if not isinstance(param, WorkingTree):
3277
raise TypeError("Parameter \"%s\" must be a WorkingTree"
3278
" but was: %r" % (name, param))
3281
public('ChangesetApplication', 'ChangesetCreation', 'delta', 'iter_delta')
3283
class ChangesetApplication(object):
3284
"""Incremental changeset application process."""
3286
def __init__(self, proc):
3287
"""For internal use only."""
3288
self._iter_process = proc
3291
"""Return an iterator of `MergeOutcome`"""
3292
return classify_changeset_application(self._iter_process)
3294
def _get_conflicting(self):
3295
"Did conflicts occur during changeset application?"
3296
if not self.finished:
3297
raise AttributeError("Changeset application is not complete.")
3298
return self._iter_process.status == 1
3299
conflicting = property(_get_conflicting)
3301
def _get_finished(self):
3302
"Is the changeset application complete?"
3303
return self._iter_process.finished
3304
finished = property(_get_finished)
3306
class ChangesetCreation(object):
3307
"""Incremental changeset generation process."""
3309
def __init__(self, proc, dest):
3310
"""For internal use only."""
3311
self._iter_process = proc
3313
self._changeset = None
3316
"""Return an iterator of `TreeChange`"""
3317
return classify_changeset_creation(self._iter_process)
3319
def _get_changeset(self):
3320
"""Generated changeset."""
3321
if self._changeset is None:
3322
if not self.finished:
3323
raise AttributeError("Changeset generation is not complete.")
3324
self._changeset = Changeset(self._dest)
3325
return self._changeset
3326
changeset = property(_get_changeset)
3328
def _get_finished(self):
3329
"Is the changeset creation complete?"
3330
return self._iter_process.finished
3331
finished = property(_get_finished)
3334
def iter_delta(orig, mod, dest):
3335
"""Compute a whole-tree changeset with incremental output.
3337
:param orig: old revision or directory.
3338
:type orig: `Revision`, `ArchSourceTree`
3339
:param mod: new revision or directory,
3340
:type mod: `Revision`, `ArchSourceTree`
3341
:param dest: path of the changeset to create.
3343
:rtype: `ChangesetCreation`
3345
orig = _tree_or_existing_revision_param(orig, 'orig')
3346
mod = _tree_or_existing_revision_param(mod, 'mod')
3347
_check_str_param(dest, 'dest')
3348
proc = _arch.iter_delta(orig, mod, dest)
3349
return ChangesetCreation(proc, dest)
3352
def delta(orig, mod, dest):
3353
"""Compute a whole-tree changeset.
3355
Create the output directory ``dest`` (it must not already exist).
3357
Compare the source trees ``orig`` and ``mod`` (which may be source
3358
arch source tree or revisions). Create a changeset in ``dest``.
3360
:param orig: the old revision or directory.
3361
:type orig: `Revision`, `ArchSourceTree`
3362
:param mod: the new revision or directory.
3363
:type mod: `Revision`, `ArchSourceTree`
3364
:param dest: path of the changeset to create.
3366
:return: changeset from ``orig`` to ``mod``.
3369
__pychecker__ = 'unusednames=line'
3370
iter_ = iter_delta(orig, mod, dest)
3371
for line in iter_: pass
3372
return iter_.changeset
3375
### Top level archive functions ###
3377
public('my_id', 'set_my_id', 'make_archive', 'register_archive')
3380
"""The current registered user id
3382
:return: the user id, for example 'John Doe <jdoe@example.org>'.
3385
return _arch.my_id()
3388
def set_my_id(new_id):
3389
"""Set the current registered user id
3391
:param new_id: new value of the user id.
3394
return _arch.set_my_id(new_id)
3397
def make_archive(name, location, signed=False, listing=False):
3398
"""Create and register new commitable archive.
3400
:param name: archive name (e.g. "david@allouche.net--2003b").
3401
:type name: `Archive` or str
3402
:param location: URL of the archive
3404
:param signed: create GPG signatures for the archive contents.
3406
:param listing: maintains ''.listing'' files to enable HTTP access.
3409
:return: an `Archive` instance for the given name.
3412
:raise errors.NamespaceError: ``name`` is not a valid archive name.
3414
name = _archive_param(name)
3415
_arch.make_archive(name, location, signed, listing)
3416
return Archive(_unsafe((name,)))
3418
def register_archive(name, location):
3419
"""Record the location of an archive.
3421
:param name: archive name, or None to use the official name stored in the
3423
:type name: str, None
3424
:param location: URL of the archive.
3426
:return: newly registered archive.
3429
if name is not None:
3430
name = _archive_param(name)
3431
_arch.register_archive(name, location)
3432
if name is not None:
3433
return Archive(_unsafe((name,)))
3435
for n in _arch.archives():
3436
a = Archive(_unsafe((n,)))
3437
if a.location == location:
3439
raise AssertionError, 'not reached'
3442
public('iter_archives', 'archives')
3444
def iter_archives():
3445
"""Iterate over registered archives.
3447
:return: all registered archives.
3448
:rtype: iterable of `Archive`
3450
for n in _arch.archives():
3457
List of registered archives.
3459
:rtype: sequence of `Archive`
3460
:see: `iter_archives`
3462
deprecated_callable(archives, iter_archives)
3463
return list(iter_archives())
3466
public('iter_library_archives', 'library_archives')
3468
def iter_library_archives():
3469
"""Iterate over archives present in the revision library.
3471
:returns: all archives which are present in the revision library.
3472
:rtype: iterable of `Archive`
3474
for n in _arch.library_archives():
3478
def library_archives():
3481
List of archives present in the revision library.
3483
:rtype: sequence of `Archive`
3484
:see: `iter_library_archives`
3486
deprecated_callable(library_archives, iter_library_archives)
3487
return list(iter_library_archives())
3490
public('default_archive')
3492
def default_archive():
3493
"""Default Archive object or None.
3495
:return: the default archive, or None.
3496
:rtype: `Archive`, None
3498
name = _arch.default_archive()
3502
return Archive(name)
3505
public('make_continuation')
3507
def make_continuation(source_revision, tag_version):
3510
:see: `Revision.make_continuation`
3512
deprecated_callable(make_continuation, Revision.make_continuation)
3513
source_revision = _version_revision_param(source_revision)
3514
tag_version = _version_param(tag_version)
3515
_arch.archive_setup(tag_version)
3516
_arch.tag(source_revision, tag_version)
3518
public('get', 'get_patch')
3520
def get(revision, dir, link=False):
3523
Construct a project tree for a revision.
3525
:rtype: `WorkingTree`
3526
:see: `Revision.get`
3528
deprecated_callable(get, Revision.get)
3529
revision = _package_revision_param(revision)
3530
_arch.get(revision, dir, link)
3531
return WorkingTree(dir)
3534
def get_patch(revision, dir):
3538
:see: `Revision.get_patch`
3540
deprecated_callable(get_patch, Revision.get_patch)
3541
revision = _revision_param(revision)
3542
_arch.get_patch(revision, dir)
3543
return Changeset(dir)
3547
'iter_revision_libraries',
3548
'register_revision_library',
3549
'unregister_revision_library',
3552
def iter_revision_libraries():
3553
"""Iterate over registered revision library directories.
3555
:return: directory names of all registered revision libraries.
3556
:rtype: iterable of str
3558
return _arch.iter_revision_libraries()
3560
def register_revision_library(dirname):
3561
"""Register an existing revision library directory.
3563
:param dirname: absolute path name of existing user-writable directory.
3565
:todo: create_revision_library which abstracts out revlib construction.
3566
:postcondition: ``dirname`` is present in `iter_revision_libraries` output.
3568
if not os.path.isabs(dirname):
3569
raise ValueError, "not an absolute path: %r" % dirname
3570
if not os.path.isdir(dirname):
3571
raise ValueError, "directory does not exist: %r" % dirname
3572
_arch.register_revision_library(dirname)
3574
def unregister_revision_library(dirname):
3575
"""Unregister a revision library directory.
3577
:param dirname: registered revision library directory.
3579
:todo: delete_revision_library which abstracts out revlib destruction.
3580
:precondition: ``dirname`` is present in `iter_revision_libraries` output.
3581
:postcondition: ``dirname`` is not listed by `iter_revision_libraries`.
3583
if not os.path.isabs(dirname):
3584
raise ValueError, "not an absolute path: %r" % dirname
3585
_arch.unregister_revision_library(dirname)
3588
### Parsing Arch names ###
3590
public('NameParser')
3592
class NameParser(_arch.NameParser):
3594
"""Parser for names in Arch archive namespace.
3596
:see: `backends.tla.NameParser`
3600
"""Create the Category, Branch, Version or Revision object
3602
Create the namespace object corresponding to the name. This
3603
requires some guessing so, for example, nameless branches will
3606
:warning: DEPRECATED: unsafe and not really useful.
3608
a = self.get_archive()
3609
if not a: return None
3611
c = self.get_category()
3614
b = self.get_branch()
3617
v = self.get_version()
3620
r = self.get_patchlevel()
3625
### Searching in Archives ###
3628
'filter_archive_logs',
3631
'grep_summary_interactive',
3633
'revisions_merging',
3635
'revision_which_created',
3640
def filter_archive_logs(limit, pred):
3641
deprecated_callable(filter_archive_logs,
3642
because='It does not belong here.')
3643
for r in limit.iter_revisions():
3644
if pred(r.patchlog): yield r
3647
def filter_revisions(limit, pred):
3648
deprecated_callable(filter_archive_logs,
3649
because='It does not belong here.')
3650
for r in limit.iter_revisions():
3653
def grep_summary(limit, rx):
3654
deprecated_callable(grep_summary,
3655
because='It does not belong here.')
3656
crx = re.compile(rx)
3657
def pred(p): return crx.search(p['Summary'])
3658
return filter_archive_logs(limit, pred)
3661
def grep_summary_interactive(limit):
3662
deprecated_callable(grep_summary_interactive,
3663
because='It does not belong here.')
3666
rx = raw_input('search> ')
3667
except KeyboardInterrupt:
3670
for r in grep_summary(limit, rx):
3672
print 'Revision:', p['Revision']
3673
print 'Summary: ', p['Summary']
3676
def suspected_move(limit):
3677
deprecated_callable(suspected_move,
3678
because='It does not belong here.')
3679
def pred(p): return bool(p['New-files'] and p['Removed-files'])
3680
return filter_archive_logs(limit, pred)
3683
def revisions_merging(limit, rev):
3684
deprecated_callable(revisions_merging,
3685
because='It does not belong here.')
3686
def pred(p): return rev in p.merged_patches
3687
return filter_archive_logs(limit, pred)
3690
def temphack(revision):
3691
deprecated_callable(temphack,
3692
because='It does not belong here.')
3695
for ancstr in revision.iter_ancestors(metoo=True):
3696
for k in ancstr.patchlog.keys():
3701
def revision_which_created(file, revision):
3702
deprecated_callable(revision_which_created,
3703
because='It does not belong here.')
3704
for ancstr in revision.iter_ancestors(metoo=True):
3705
if file in ancstr.patchlog.new_files:
3709
def last_revision(tree):
3710
deprecated_callable(last_revision, (ArchSourceTree, 'tree_revision'))
3711
tree = SourceTree(tree)
3712
return tree.iter_logs(reverse=True).next().revision
3715
def map_name_id(tree):
3716
deprecated_callable(map_name_id,
3717
because='It does not belong here.')
3718
if not isinstance(tree, SourceTree):
3719
tree = SourceTree(tree)
3721
for id_, name in tree.iter_inventory_ids(source=True, files=True):