~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to fai/arch/_builtin.py

  • Committer: Robert Collins
  • Date: 2005-09-14 11:27:20 UTC
  • mto: (147.2.6) (364.1.3 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: robertc@robertcollins.net-20050914112720-c66a21de86eafa6e
trim fai cribbage

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# arch-tag: david@allouche.net - 2003-11-17 15:23:30 332029000
2
 
# Copyright (C) 2003 David Allouche <david@allouche.net>
3
 
#
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.
8
 
#
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.
13
 
#
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
17
 
 
18
 
"""
19
 
Internal module providing top-level arch package names
20
 
 
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``.
24
 
 
25
 
.. _arch: arch-module.html
26
 
 
27
 
This module is strictly internal and should never be used.
28
 
 
29
 
:var tla_tells_empty_meta_info: The back-end can to tell empty archive
30
 
    meta-info files.
31
 
 
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
37
 
    missing files.
38
 
 
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.
42
 
 
43
 
:type tla_tells_empty_meta_info: bool
44
 
 
45
 
:var backend: See `arch.backend`
46
 
 
47
 
:var _arch: Internal deprecated interface to the backend
48
 
"""
49
 
 
50
 
### Package docstring ###
51
 
 
52
 
_package_doc = """\
53
 
High level bindings for the Arch revision control system
54
 
 
55
 
Archive Namespace Class Hierarchy
56
 
---------------------------------
57
 
 
58
 
:group Namespace Classes: Archive, Category, Branch, Version, Revision
59
 
 
60
 
:group Abstract Namespace Classes: NamespaceObject, Setupable, Package,
61
 
    CategoryIterable, BranchIterable, VersionIterable, RevisionIterable,
62
 
    ArchiveItem, CategoryItem, BranchItem, VersionItem
63
 
 
64
 
:group Archive-Related Classes: RevisionFile, NameParser
65
 
 
66
 
The archive namespace classes form a complex hierarchy which
67
 
cannot be appropriately described in individual classes.
68
 
 
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.
72
 
 
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.
76
 
 
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.
82
 
 
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.
88
 
 
89
 
::
90
 
 
91
 
  ,--------------------------------> NamespaceObject
92
 
  |                                   / \     / \\
93
 
  |                                    |       |
94
 
  | CategoryIterable <---- Archive ----'       |
95
 
  |         |                              ArchiveItem <-----.
96
 
  |         |                                 / \            |
97
 
  |        \ /                                 |             |
98
 
  | BranchIterable <------ Category -----------+-------> Setupable
99
 
  |         |                                  |              / \\
100
 
  |         |                                  |               |
101
 
  |         |                  ,---------------+---------> Package
102
 
  |        \ /                 |               |           / \  |
103
 
  | VersionIterable <----- Branch ------> CategoryItem      |   |
104
 
  |         |                                 / \           |   |
105
 
            |                                  |            |   |
106
 
  |         |                  ,---------------+------------'   |
107
 
  |        \ /                 |               |                |
108
 
  | RevisionIterable <---- Version -----> BranchItem            |
109
 
  |     |        / \                          / \               |
110
 
  |     |         |                            |                |
111
 
  `-----'         |                            |                |
112
 
                  |       Revision ----> VersionItem            |
113
 
                  |                                             |
114
 
                  `---------------------------------------------'
115
 
 
116
 
 
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.
120
 
 
121
 
 
122
 
:group Source Tree Classes: SourceTree, ForeignTree, ArchSourceTree,
123
 
    LibraryTree, WorkingTree
124
 
 
125
 
:group Changeset and Log Classes: Changeset, Patchlog, LogMessage
126
 
 
127
 
:group Incremental Ouput: ChangesetCreation, ChangesetApplication,
128
 
    Chatter, TreeChange, FileAddition, FileDeletion, FileModification,
129
 
    FilePermissionsChange, FileRename, SymlinkModification,
130
 
    MergeOutcome, PatchConflict
131
 
 
132
 
:group Archive Functions: archives, iter_archives, make_archive,
133
 
    register_archive, get, get_patch, make_continuation
134
 
 
135
 
:group Source Tree Functions: init_tree, in_source_tree, tree_root
136
 
 
137
 
:group User Functions: default_archive, my_id, set_my_id
138
 
 
139
 
:group Changeset Generation Functions: changeset, delta, iter_delta
140
 
 
141
 
:group Pika Escaping Functions: name_escape, name_unescape
142
 
 
143
 
:group Revision Library Functions: register_revision_library,
144
 
    unregister_revision_library, iter_revision_libraries, library_archives,
145
 
    iter_library_archives
146
 
 
147
 
:group Incremental Output Functions: classify_chatter,
148
 
    classify_changeset_creation, classify_changeset_application
149
 
 
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
153
 
 
154
 
:var backend: Backend controller.
155
 
 
156
 
    This object is used to configure the backend system: name of the
157
 
    executable, process handling strategy and command-line logging.
158
 
 
159
 
:type backend: `backends.commandline.CommandLineBackend`
160
 
"""
161
 
 
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
166
 
 
167
 
import os
168
 
import shutil
169
 
import itertools
170
 
import re
171
 
from sets import ImmutableSet
172
 
import errors
173
 
import util
174
 
from pathname import PathName, FileName, DirName
175
 
from deprecation import deprecated_usage, deprecated_callable
176
 
from _escaping import *
177
 
from _output import *
178
 
 
179
 
__all__ = []
180
 
 
181
 
def public(*names):
182
 
    __all__.extend(names)
183
 
 
184
 
 
185
 
### Backend abstraction and compatibility
186
 
 
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)
193
 
 
194
 
_arch = _BackendProxy()
195
 
 
196
 
from backends import commandline
197
 
 
198
 
backend = commandline.default_backend()
199
 
 
200
 
public('backend')
201
 
 
202
 
 
203
 
### Internal helpers for namespace sanity checks
204
 
 
205
 
class _unsafe(tuple):
206
 
    """Used internally to disable namespace sanity checks"""
207
 
 
208
 
 
209
 
def _archive_param(archive):
210
 
    """Internal argument checking utility"""
211
 
    if isinstance(archive, Archive):
212
 
        return archive.name
213
 
    else:
214
 
        if not NameParser.is_archive_name(archive):
215
 
            raise errors.NamespaceError(archive, 'archive name')
216
 
        return archive
217
 
 
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)
223
 
    return archive
224
 
 
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)
231
 
        return item.nonarch
232
 
    else:
233
 
        p = NameParser(item)
234
 
        if not p.has_category():
235
 
            raise errors.NamespaceError(item, 'archive limit')
236
 
        if not p.has_archive():
237
 
            return item
238
 
        elif archive != p.get_archive():
239
 
            message = "%s is not an item of %s" % (item.fullname, archive)
240
 
            raise ValueError(message)
241
 
        else:
242
 
            return p.get_nonarch()
243
 
 
244
 
def _version_param(vsn):
245
 
    """Internal argument checking utility"""
246
 
    if isinstance(vsn, Version):
247
 
        return vsn.fullname
248
 
    else:
249
 
        p = NameParser(vsn)
250
 
        if not p.has_archive() or not p.is_version():
251
 
            raise errors.NamespaceError(vsn, 'fully-qualified version')
252
 
        return vsn
253
 
 
254
 
def _revision_param(rev):
255
 
    """Internal argument checking utility."""
256
 
    if isinstance(rev, Revision):
257
 
        return rev.fullname
258
 
    else:
259
 
        p = NameParser(rev)
260
 
        if not p.has_archive() or not p.has_patchlevel():
261
 
            raise errors.NamespaceError(rev, 'fully-qualified revision')
262
 
        return rev
263
 
 
264
 
def _version_revision_param(vsn_rev):
265
 
    """Internal argument checking utility"""
266
 
    if isinstance(vsn_rev, BranchItem):
267
 
        return vsn_rev.fullname
268
 
    else:
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')
273
 
        return vsn_rev
274
 
 
275
 
def _package_revision_param(pkg_rev):
276
 
    """Internal argument checking utility"""
277
 
    if isinstance(pkg_rev, CategoryItem):
278
 
        return pkg_rev.fullname
279
 
    else:
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')
284
 
        return pkg_rev
285
 
 
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" \
290
 
                        % (name, param))
291
 
 
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))
297
 
 
298
 
def _check_relname_param(param, name):
299
 
    """Internal argument checking utility"""
300
 
    if not isinstance(param, basestring):
301
 
        exc_type = TypeError
302
 
    elif os.path.isabs(param):
303
 
        exc_type = ValueError
304
 
    else:
305
 
        return
306
 
    raise exc_type("Parameter \"%s\" must be a relative path (string)"
307
 
                   " but was: %r" % (name, param))
308
 
 
309
 
 
310
 
### Base class for all namespace classes ###
311
 
 
312
 
public('NamespaceObject')
313
 
 
314
 
class NamespaceObject(object):
315
 
 
316
 
    """Base class for all archive objects."""
317
 
 
318
 
    def get_fullname(self):
319
 
        """Deprecated
320
 
 
321
 
        Fully qualified name of this namespace object.
322
 
 
323
 
        :rtype: str
324
 
        :see: `NamespaceObject.fullname`
325
 
        """
326
 
        raise NotImplementedError
327
 
 
328
 
    fullname = property(get_fullname, doc = """
329
 
    Fully qualfied name of this namespace object.
330
 
 
331
 
    :type: str
332
 
    """)
333
 
 
334
 
    def exists(self):
335
 
        """Does this namespace exists?
336
 
 
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`.
343
 
 
344
 
        :return: whether this namespace object exists.
345
 
        :rtype: bool
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.
349
 
        """
350
 
        raise NotImplementedError
351
 
 
352
 
    def __repr__(self):
353
 
        """Fully-qualified name in angle brackets.
354
 
 
355
 
        :rtype: str
356
 
        """
357
 
        return '<%s>' % self.fullname
358
 
 
359
 
    def __str__(self):
360
 
        """Fully-qualified name.
361
 
 
362
 
        Returns the value of the fullname attribute.
363
 
 
364
 
        :rtype: str
365
 
        """
366
 
        return self.fullname
367
 
 
368
 
    def __eq__(self, x):
369
 
        """Compare types and fully-qualified names.
370
 
 
371
 
        :return: wether objects have the same types and names.
372
 
        :rtype: bool
373
 
        """
374
 
        return type(self) is type(x) \
375
 
               and self.__class__ is x.__class__ \
376
 
               and self.fullname == x.fullname
377
 
 
378
 
    def __ne__(self, x):
379
 
        """Compare types and fully-qualified names.
380
 
 
381
 
        :return: whether objects have different types or names.
382
 
        :rtype: bool
383
 
        """
384
 
        return type(self) is not type(x) \
385
 
               or self.__class__ is not x.__class__ \
386
 
               or self.fullname != x.fullname
387
 
 
388
 
 
389
 
### Mixins for archive iteration aspect ###
390
 
 
391
 
public(
392
 
    'RevisionIterable',
393
 
    'VersionIterable',
394
 
    'BranchIterable',
395
 
    'CategoryIterable',
396
 
    )
397
 
 
398
 
class RevisionIterable(NamespaceObject):
399
 
 
400
 
    """Abstract class for namespace classes above Revision.
401
 
 
402
 
    RevisionIterable provides features which are common to all objects
403
 
    containing revisions.
404
 
    """
405
 
 
406
 
    def iter_revisions(self, reverse=False):
407
 
        """Iterate over archive revisions.
408
 
 
409
 
        :param reverse: reverse order, recent revisions first.
410
 
        :type reverse: bool
411
 
        :return: all existing revisions in this namespace.
412
 
        :rtype: iterable of `Revision`
413
 
 
414
 
        :precondition: `self.exists()` returns ``True``.
415
 
        """
416
 
        raise NotImplementedError
417
 
 
418
 
    def iter_library_revisions(self, reverse=False):
419
 
        """Iterate over library revisions.
420
 
 
421
 
        :param reverse: reverse order, recent revisions first.
422
 
        :type reverse: bool
423
 
        :return: revisions in this namespace which are present in the
424
 
            revision library.
425
 
        :rtype: iterable of `Revision`
426
 
        """
427
 
        raise NotImplementedError
428
 
 
429
 
 
430
 
class VersionIterable(RevisionIterable):
431
 
 
432
 
    """Abstract class for archive classes above Version.
433
 
 
434
 
    VersionIterable provides features which are common to all objects
435
 
    containing versions.
436
 
    """
437
 
 
438
 
    def iter_versions(self, reverse=False):
439
 
        """Iterate over archive versions.
440
 
 
441
 
        :param reverse: reverse order, higher versions first.
442
 
        :type reverse: bool
443
 
        :return: all existing versions in this namespace.
444
 
        :rtype: iterable of `Version`
445
 
 
446
 
        :precondition: `self.exists()` returns ``True``.
447
 
        """
448
 
        raise NotImplementedError
449
 
 
450
 
    def iter_library_versions(self, reverse=False):
451
 
        """Iterate over library revisions.
452
 
 
453
 
        :param reverse: reverse order, higher versions first.
454
 
        :type reverse: bool
455
 
        :return: versions in this namespace which are present in the
456
 
            revision library.
457
 
        :rtype: iterable of `Version`
458
 
        """
459
 
        raise NotImplementedError
460
 
 
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
464
 
 
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
468
 
 
469
 
 
470
 
class BranchIterable(VersionIterable):
471
 
 
472
 
    """Base class for archive classes above Branch.
473
 
 
474
 
    BranchIterable provides features which are common to all objects
475
 
    containing branches.
476
 
    """
477
 
 
478
 
    def iter_branches(self):
479
 
        """Iterate over archive branches.
480
 
 
481
 
        :return: all existing branches in this namespace.
482
 
        :rtype: iterable of `Branch`
483
 
        :precondition: `self.exists()` returns ``True``.
484
 
        """
485
 
        raise NotImplementedError
486
 
 
487
 
    def iter_library_branches(self):
488
 
        """Iterate over library branches.
489
 
 
490
 
        :return: branches in this namespace which are present in the
491
 
            revision library.
492
 
        :rtype: iterable of `Branch`
493
 
        """
494
 
        raise NotImplementedError
495
 
 
496
 
    def iter_versions(self, reverse=False):
497
 
        for b in self.iter_branches():
498
 
            for v in b.iter_versions(reverse): yield v
499
 
 
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
503
 
 
504
 
 
505
 
class CategoryIterable(BranchIterable):
506
 
 
507
 
    """Base class for Archive.
508
 
 
509
 
    CategoryIterable provides features for the aspect of Archive wich
510
 
    relates to its containing other archive objects.
511
 
    """
512
 
 
513
 
    def iter_categories(self):
514
 
        """Iterate over archive categories.
515
 
 
516
 
        :return: all existing categories in this namespace.
517
 
        :rtype: iterable of `Category`
518
 
        :precondition: `self.exists()` returns ``True``.
519
 
        """
520
 
        raise NotImplementedError
521
 
 
522
 
    def iter_library_categories(self):
523
 
        """Iterate over library categories.
524
 
 
525
 
        :return: categories in this namespace which are present in the
526
 
            revision library.
527
 
        :rtype: iterable of `Category`
528
 
        """
529
 
        raise NotImplementedError
530
 
 
531
 
    def iter_branches(self):
532
 
        for c in self.iter_categories():
533
 
            for b in c.iter_branches(): yield b
534
 
 
535
 
    def iter_library_branches(self):
536
 
        for c in self.iter_library_categories():
537
 
            for b in c.iter_library_branches(): yield b
538
 
 
539
 
 
540
 
### Base classes for archive containment aspect ###
541
 
 
542
 
public(
543
 
    'ArchiveItem',
544
 
    'CategoryItem',
545
 
    'BranchItem',
546
 
    'VersionItem',
547
 
    )
548
 
 
549
 
class ArchiveItem(NamespaceObject):
550
 
 
551
 
    """Base class for all archive components classes.
552
 
 
553
 
    ArchiveItem provides features common to all objects which are
554
 
    structural components of Archive.
555
 
    """
556
 
 
557
 
    def __init__(self, archive, nonarch):
558
 
        self._archive = archive
559
 
        self._nonarch = nonarch
560
 
 
561
 
    def get_archive(self):
562
 
        """Deprecated.
563
 
 
564
 
        Archive which contains this namespace object.
565
 
 
566
 
        :rtype: `Archive`
567
 
        :see: `ArchiveItem.archive`
568
 
        """
569
 
        deprecated_callable(self.get_archive, (type(self), 'archive'))
570
 
        return self.archive
571
 
 
572
 
    def _get_archive(self):
573
 
        return Archive(_unsafe((self._archive,)))
574
 
 
575
 
    archive = property(_get_archive, doc="""
576
 
    Archive which contains this namespace object.
577
 
 
578
 
    :type: `Archive`
579
 
    """)
580
 
 
581
 
    def get_nonarch(self):
582
 
        """Deprecated.
583
 
 
584
 
        Non-arch part of this namespace name.
585
 
 
586
 
        :rtype: str
587
 
        :see: `ArchiveItem.nonarch`
588
 
        """
589
 
        deprecated_callable(self.get_nonarch, (type(self), 'nonarch'))
590
 
        return self.nonarch
591
 
 
592
 
    def _get_nonarch(self):
593
 
        return self._nonarch
594
 
 
595
 
    nonarch = property(_get_nonarch, doc="""
596
 
    Non-arch part of this namespace name.
597
 
 
598
 
    :type: str
599
 
    """)
600
 
 
601
 
    def get_fullname(self):
602
 
        deprecated_callable(self.get_fullname, (type(self), 'fullname'))
603
 
        return self.fullname
604
 
 
605
 
    def _get_fullname(self):
606
 
        return '%s/%s' % (self._archive, self._nonarch)
607
 
 
608
 
    fullname = property(_get_fullname, doc=NamespaceObject.fullname.__doc__)
609
 
 
610
 
 
611
 
class CategoryItem(ArchiveItem):
612
 
 
613
 
    """Base class for archive classes below Category.
614
 
 
615
 
    CategoryItem provides features common to all objects which are
616
 
    structural components of Category.
617
 
    """
618
 
 
619
 
    def get_category(self):
620
 
        """Deprecated.
621
 
 
622
 
        Category which contains this namespace object.
623
 
 
624
 
        :rtype: `Category`
625
 
        :see: `CategoryItem.category`
626
 
        """
627
 
        deprecated_callable(self.get_category, (type(self), 'category'))
628
 
        return self.category
629
 
 
630
 
    def _get_category(self):
631
 
        return Category(_unsafe((self._archive, self._nonarch.split('--')[0])))
632
 
 
633
 
    category = property(_get_category, doc="""
634
 
    Category which contains this object.
635
 
 
636
 
    :type: `Category`
637
 
    """)
638
 
 
639
 
 
640
 
class BranchItem(CategoryItem):
641
 
 
642
 
    """Base class for archive classes Version and Revision.
643
 
 
644
 
    BranchItem provides features common to all objects which are
645
 
    structural components of Branch.
646
 
    """
647
 
 
648
 
    def get_branch(self):
649
 
        """Deprecated.
650
 
 
651
 
        Branch which contains this object.
652
 
 
653
 
        :rtype: `Branch`
654
 
        :see: `BranchItem.branch`
655
 
        """
656
 
        deprecated_callable(self.get_branch, (type(self), 'branch'))
657
 
        return self.branch
658
 
 
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)))
663
 
 
664
 
    branch = property(_get_branch, doc="""
665
 
    Branch which contains this namespace object.
666
 
 
667
 
    :type: `Branch`
668
 
    """)
669
 
 
670
 
 
671
 
class VersionItem(BranchItem):
672
 
 
673
 
    """Base class for Revision.
674
 
 
675
 
    VersionItem provides features for the aspect of Revision which
676
 
    relate to its containment within other archive objects.
677
 
    """
678
 
 
679
 
    def __init__(self, archive, version, patchlevel):
680
 
        BranchItem.__init__(self, archive, "%s--%s" % (version, patchlevel))
681
 
        self._version, self._patchlevel = version, patchlevel
682
 
 
683
 
    def get_branch(self):
684
 
        deprecated_callable(self.get_branch, (type(self), 'branch'))
685
 
        return self.branch
686
 
 
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)))
691
 
 
692
 
    branch = property(_get_branch, doc=BranchItem.branch.__doc__)
693
 
 
694
 
    def get_version(self):
695
 
        """Deprecated.
696
 
 
697
 
        Version which contains this revision.
698
 
 
699
 
        :rtype: `Version`
700
 
        :see: `VersionItem.version`
701
 
        """
702
 
        deprecated_callable(self.get_version, (type(self), 'version'))
703
 
        return self.version
704
 
 
705
 
    def _get_version(self):
706
 
        return Version(_unsafe((self._archive, self._version)))
707
 
 
708
 
    version = property(_get_version, doc="""
709
 
    Version which contains this revision.
710
 
 
711
 
    :type: `Version`
712
 
    """)
713
 
 
714
 
    def get_patchlevel(self):
715
 
        """Deprecated.
716
 
 
717
 
        Patch-level part of this object's name.
718
 
 
719
 
        :rtype: str
720
 
        :see: `VersionItem.patchlevel`
721
 
        """
722
 
        deprecated_callable(self.get_patchlevel, (type(self), 'patchlevel'))
723
 
        return self.patchlevel
724
 
 
725
 
    def _get_patchlevel(self):
726
 
        return self._patchlevel
727
 
 
728
 
    patchlevel = property(_get_patchlevel, doc="""
729
 
    Patch-level part of this object's name.
730
 
 
731
 
    :type: str
732
 
    """)
733
 
 
734
 
 
735
 
### Mixins for misc. features of archive objects ###
736
 
 
737
 
public('Setupable', 'Package')
738
 
 
739
 
class Setupable(ArchiveItem):
740
 
 
741
 
    """Base class for container archive objects."""
742
 
 
743
 
    def setup(self):
744
 
        """Create this namespace in the archive.
745
 
 
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
750
 
        """
751
 
        # FIXME: do not do the right thing for anonymous branches
752
 
        _arch.archive_setup(self.fullname)
753
 
 
754
 
 
755
 
class Package(Setupable, RevisionIterable):
756
 
 
757
 
    """Base class for ordered container archive objects."""
758
 
 
759
 
    def as_revision(self):
760
 
        """Deprecated.
761
 
 
762
 
        Latest revision in this package.
763
 
 
764
 
        :rtype: `Revision`
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`
769
 
        """
770
 
        deprecated_callable(self.as_revision, self.latest_revision)
771
 
        return self.iter_revisions(reverse=True).next()
772
 
 
773
 
    def latest_revision(self):
774
 
        """Latest revision in this package.
775
 
 
776
 
        :rtype: `Revision`
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.
781
 
        """
782
 
        try:
783
 
            return self.iter_revisions(reverse=True).next()
784
 
        except errors.ExecProblem:
785
 
            try:
786
 
                self_exists = self.exists()
787
 
            except errors.ArchiveNotRegistered:
788
 
                raise ValueError('Archive is not registered: %s'
789
 
                                 % self.archive)
790
 
            if not self_exists:
791
 
                raise ValueError('Package does not exist: %s' % self)
792
 
            raise
793
 
        except StopIteration:
794
 
            raise ValueError('Package contains no revision: %s' % self)
795
 
 
796
 
 
797
 
### Archive classes ###
798
 
 
799
 
public('Archive', 'Category', 'Branch', 'Version', 'Revision')
800
 
 
801
 
class Archive(CategoryIterable):
802
 
 
803
 
    """Arch archive namespace object.
804
 
 
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`.
808
 
 
809
 
    :see: `Category`, `Branch`, `Version`, `Revision`
810
 
    """
811
 
 
812
 
    def __init__(self, name):
813
 
        """Create an archive object from its registered name.
814
 
 
815
 
        :param name: archive name, like "jdoe@example.com--2003"
816
 
        :type name: str
817
 
        :raise errors.NamespaceError: invalid archive name.
818
 
        """
819
 
        if isinstance(name, Archive):
820
 
            name = name.__name
821
 
        if isinstance(name, _unsafe):
822
 
            assert len(name) == 1
823
 
            name = name[0]
824
 
        elif not NameParser.is_archive_name(name):
825
 
            raise errors.NamespaceError(name, 'archive name')
826
 
        self.__name = name
827
 
 
828
 
    def get_name(self):
829
 
        """Deprecated.
830
 
 
831
 
        Logical name of the archive.
832
 
 
833
 
        :rtype: str
834
 
        :see: `Archive.name`
835
 
        """
836
 
        deprecated_callable(Archive.get_name, (Archive, 'name'))
837
 
        return self.name
838
 
 
839
 
    def _get_name(self):
840
 
        return self.__name
841
 
 
842
 
    name = property(_get_name, doc="""
843
 
    Logical name of the archive.
844
 
 
845
 
    :type: str
846
 
    """)
847
 
 
848
 
    def get_fullname(self):
849
 
        deprecated_callable(Archive.get_fullname, (Archive, 'fullname'))
850
 
        return self.name
851
 
 
852
 
    fullname = property(_get_name, doc=NamespaceObject.fullname.__doc__)
853
 
 
854
 
    def get_location(self):
855
 
        """Deprecated.
856
 
 
857
 
        URI of the archive, specifies location and access method.
858
 
 
859
 
        :rtype: str
860
 
        :see: `Archive.location`
861
 
        """
862
 
        deprecated_callable(Archive.get_location, (Archive, 'location'))
863
 
        return self.location
864
 
 
865
 
    def _get_location(self):
866
 
        return _arch.whereis_archive(self.name)
867
 
 
868
 
    location = property(_get_location, doc="""
869
 
    URI of the archive, specifies location and access method.
870
 
 
871
 
    For example 'http://ddaa.net/arch/2004', or
872
 
    'sftp://user@sourcecontrol.net/public_html/2004'.
873
 
 
874
 
    :type: str
875
 
    """)
876
 
 
877
 
    def _meta_info(self, key):
878
 
        return _arch.archive_meta_info(self.name, key)
879
 
 
880
 
    def _has_meta_info(self, key):
881
 
        if tla_tells_empty_meta_info:
882
 
            try:
883
 
                _arch.archive_meta_info(self.name, key)
884
 
                return True
885
 
            except KeyError:
886
 
                return False
887
 
        else:
888
 
            return _arch.archive_meta_info(self.name, key) is not None
889
 
 
890
 
    def get_is_mirror(self):
891
 
        """Deprecated.
892
 
 
893
 
        Is this archive registration a mirror?
894
 
 
895
 
        :rtype: bool
896
 
        :see: Archive.is_mirror
897
 
        """
898
 
        deprecated_callable(Archive.get_is_mirror, (Archive, 'is_mirror'))
899
 
        return self.is_mirror
900
 
 
901
 
    def _get_is_mirror(self):
902
 
        return self._has_meta_info('mirror')
903
 
 
904
 
    is_mirror = property(_get_is_mirror, doc="""
905
 
    Is this archive registration a mirror?
906
 
 
907
 
    :type: bool
908
 
    """)
909
 
 
910
 
    def get_official_name(self):
911
 
        """Deprecated.
912
 
 
913
 
        Official archive name of this archive registration.
914
 
 
915
 
        :rtype: str
916
 
        :see: `Archive.official_name`
917
 
        """
918
 
        deprecated_callable(Archive.get_official_name,
919
 
                            (Archive, 'official_name'))
920
 
        return self.official_name
921
 
 
922
 
    def _get_official_name(self):
923
 
        return _arch.archive_meta_info(self.name, 'name')
924
 
 
925
 
    official_name = property(_get_official_name, doc="""
926
 
    Official archive name of this archive registration.
927
 
 
928
 
    :type: str
929
 
    """)
930
 
 
931
 
    def get_is_signed(self):
932
 
        """Deprecated.
933
 
 
934
 
        Is the archive GPG-signed?
935
 
 
936
 
        :rtype: bool
937
 
        :see: `Archive.is_signed`
938
 
        """
939
 
        deprecated_callable(Archive.get_is_signed, (Archive, 'is_signed'))
940
 
        return self.is_signed
941
 
 
942
 
    def _get_is_signed(self):
943
 
        return self._has_meta_info('signed-archive')
944
 
 
945
 
    is_signed = property(_get_is_signed, doc="""
946
 
    Is the archive GPG-signed?
947
 
 
948
 
    :type: bool
949
 
    """)
950
 
 
951
 
    def get_has_listings(self):
952
 
        """Deprecated.
953
 
 
954
 
        Does the archive provide .listing file for http access?
955
 
 
956
 
        :rtype: bool
957
 
        :see: `Archive.has_listings`
958
 
        """
959
 
        deprecated_callable(Archive.get_has_listings,
960
 
                            (Archive, 'has_listings'))
961
 
        return self.has_listings
962
 
 
963
 
    def _get_has_listings(self):
964
 
        return self._has_meta_info('http-blows')
965
 
 
966
 
    has_listings = property(_get_has_listings, doc="""
967
 
    Does the archive provide .listing file for http access?
968
 
 
969
 
    :type: bool
970
 
    """)
971
 
 
972
 
    def _get_version_string(self):
973
 
        location = '/'.join((self.location, '.archive-version'))
974
 
        import urllib
975
 
        version_file = urllib.urlopen(location)
976
 
        try:
977
 
            ret = version_file.read()
978
 
        finally:
979
 
            version_file.close()
980
 
        return ret.rstrip()
981
 
 
982
 
    version_string = property(_get_version_string, doc="""
983
 
    Version string of the archive.
984
 
 
985
 
    Contents of the ``.archive-version`` file at the root of the archive.
986
 
 
987
 
    :type: str
988
 
    """)
989
 
 
990
 
    def __getitem__(self, category):
991
 
        """Instanciate a Category belonging to this archive.
992
 
 
993
 
        :param category: unqualified category name
994
 
        :type category: str
995
 
        :rtype: `Category`
996
 
        """
997
 
        if not NameParser.is_category_name(category):
998
 
            raise errors.NamespaceError(category, 'unqualified category name')
999
 
        return Category(_unsafe((self.name, category)))
1000
 
 
1001
 
    def exists(self):
1002
 
        if _arch.archive_registered(self.name):
1003
 
            return True
1004
 
        else:
1005
 
            raise errors.ArchiveNotRegistered(self.name)
1006
 
 
1007
 
    def is_registered(self):
1008
 
        """Is this archive registered?
1009
 
 
1010
 
        :return: Whether the location associated to this registration name is
1011
 
            known.
1012
 
        :rtype: bool
1013
 
        :see: `register_archive`, `Archive.unregister`
1014
 
        """
1015
 
        return _arch.archive_registered(self.name)
1016
 
 
1017
 
    def iter_categories(self):
1018
 
        for c in _arch.categories(self.name):
1019
 
            yield Category(_unsafe((self.name, c)))
1020
 
 
1021
 
    def iter_library_categories(self):
1022
 
        for c in _arch.library_categories(self.name):
1023
 
            yield Category(_unsafe((self.name, c)))
1024
 
 
1025
 
    def get_categories(self):
1026
 
        """Deprecated.
1027
 
 
1028
 
        Categories in this archive.
1029
 
 
1030
 
        :rtype: tuple of `Category`
1031
 
        :see: `iter_categories`
1032
 
        """
1033
 
        deprecated_callable(Archive.get_categories, Archive.iter_categories)
1034
 
        return tuple(self.iter_categories())
1035
 
 
1036
 
    def _get_categories(self):
1037
 
        deprecated_callable((Archive, 'categories'), Archive.iter_categories)
1038
 
        return tuple(self.iter_categories())
1039
 
 
1040
 
    categories = property(_get_categories, doc="""
1041
 
        Deprecated.
1042
 
 
1043
 
        Categories in this archive.
1044
 
 
1045
 
        :type: tuple of `Category`
1046
 
        :see: `iter_categories`
1047
 
        """)
1048
 
 
1049
 
    def get_library_categories(self):
1050
 
        """Deprecated.
1051
 
 
1052
 
        Categories in this archive  present in the library.
1053
 
 
1054
 
        :rtype: tuple of `Category`
1055
 
        :see: `iter_library_categories`
1056
 
        """
1057
 
        deprecated_callable(Archive.get_library_categories,
1058
 
                            Archive.iter_library_categories)
1059
 
        return tuple(self.iter_library_categories())
1060
 
 
1061
 
    def _get_library_categories(self):
1062
 
        deprecated_callable((Archive, 'library_categories'),
1063
 
                            Archive.iter_library_categories)
1064
 
        return tuple(self.iter_library_categories())
1065
 
 
1066
 
    library_categories = property(_get_library_categories, doc="""
1067
 
    Deprecated.
1068
 
 
1069
 
    Categories in this archive  present in the library.
1070
 
 
1071
 
    :type; tuple of `Category`
1072
 
    :see: `iter_library_categories`
1073
 
    """)
1074
 
 
1075
 
    def unregister(self):
1076
 
        """Unregister this archive.
1077
 
 
1078
 
        :precondition: `self.is_registered()`
1079
 
        :postcondition: not `self.is_registered()`
1080
 
        :see: `register_archive`
1081
 
        """
1082
 
        return _arch.unregister_archive(self.name)
1083
 
 
1084
 
    def make_mirror(self, name, location, signed=False, listing=False):
1085
 
        """Create and register a new mirror of this archive.
1086
 
 
1087
 
        :param name: name of the new mirror (for example
1088
 
            'david@allouche.net--2003b-MIRROR').
1089
 
        :type name: str
1090
 
        :param location: writeable URI were to create the archive mirror.
1091
 
        :type location: str
1092
 
        :param signed: create GPG signatures for the mirror contents
1093
 
        :type signed: bool
1094
 
        :param listing: maintains ''.listing'' files to enable HTTP access.
1095
 
        :type listing: bool
1096
 
 
1097
 
        :return: object for the newly created archive mirror.
1098
 
        :rtype: `Archive`
1099
 
 
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()
1104
 
 
1105
 
        :raise errors.NamespaceError: ``name`` is not a valid archive name.
1106
 
        """
1107
 
        a = Archive(name) # check archive name validity first
1108
 
        _arch.make_archive_mirror(self.official_name, name, location,
1109
 
                                  signed, listing)
1110
 
        return a
1111
 
 
1112
 
    def mirror(self, limit=None, fromto=None,
1113
 
               no_cached=False, cached_tags=False):
1114
 
        """Update an archive mirror.
1115
 
 
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
1119
 
 
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.
1123
 
 
1124
 
        :precondition: If ``fromto`` is provided, both items must be registered
1125
 
            archives names whose official name is this archive.
1126
 
 
1127
 
        :param no_cached: do not copy cached revisions.
1128
 
        :type no_cached: bool
1129
 
 
1130
 
        :param cached_tags: copy only cachedrevs for tags to other archives.
1131
 
        :type cached_tags: bool
1132
 
        """
1133
 
        if limit:
1134
 
            limit = map(lambda x: _archive_limit_param(self.name, x), limit)
1135
 
        if fromto is None:
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)
1142
 
 
1143
 
 
1144
 
class Category(Setupable, BranchIterable):
1145
 
 
1146
 
    """Arch category namespace object.
1147
 
 
1148
 
    :see: `Archive`, `Branch`, `Version`, `Revision`
1149
 
    """
1150
 
 
1151
 
    def __init__(self, name):
1152
 
        """Create a category object from its name.
1153
 
 
1154
 
        :param name: fully-qualified category name, like
1155
 
          "jdoe@example.com/frob"
1156
 
        :type name: str
1157
 
        :raise errors.NamespaceError: ``name`` is not a valid category name.
1158
 
        """
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)
1167
 
 
1168
 
    def __getitem__(self, b):
1169
 
        """Instanciate a branch belonging to this category.
1170
 
 
1171
 
        For example ``Category('jdoe@example.com/frob')['devel']`` is
1172
 
        equivalent to ``Branch('jdoe@example.com/frob--devel')``.
1173
 
 
1174
 
        :param b: branch id.
1175
 
        :type b: str
1176
 
        :rtype: `Category`
1177
 
 
1178
 
        :raise NamespaceError: argument is not a valid branch id.
1179
 
        """
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))))
1187
 
 
1188
 
    def exists(self):
1189
 
        return _arch.category_exists(self._archive, self._nonarch)
1190
 
 
1191
 
    def iter_branches(self):
1192
 
        for b in _arch.branches(self.fullname):
1193
 
            yield Branch(_unsafe((self._archive, b)))
1194
 
 
1195
 
    def iter_library_branches(self):
1196
 
        for b in _arch.library_branches(self.fullname):
1197
 
            yield Branch(_unsafe((self._archive, b)))
1198
 
 
1199
 
    def get_branches(self):
1200
 
        """Deprecated.
1201
 
 
1202
 
        Branches in this category.
1203
 
 
1204
 
        :rtype: tuple of `Branch`
1205
 
        :see: `iter_branches`
1206
 
        """
1207
 
        deprecated_callable(self.get_branches, self.iter_branches)
1208
 
        return tuple(self.iter_branches())
1209
 
 
1210
 
    def _get_branches(self):
1211
 
        deprecated_callable((type(self), 'branches'), self.iter_branches)
1212
 
        return tuple(self.iter_branches())
1213
 
 
1214
 
    branches = property(_get_branches, doc="""
1215
 
    Deprecated.
1216
 
 
1217
 
    Branches in this category.
1218
 
 
1219
 
    :type: tuple of `Branch`
1220
 
    :see: `Category.iter_branches`
1221
 
    """)
1222
 
 
1223
 
    def get_library_branches(self):
1224
 
        """Deprecated.
1225
 
 
1226
 
        Branches in this category present in the library.
1227
 
 
1228
 
        :rtype: tuple of `Branch`
1229
 
        :see: `iter_library_branches`
1230
 
        """
1231
 
        deprecated_callable(self.get_library_branches,
1232
 
                            self.iter_library_branches)
1233
 
        return tuple(self.iter_library_branches())
1234
 
 
1235
 
    def _get_library_branches(self):
1236
 
        deprecated_callable((Category, 'library_branches'),
1237
 
                            self.iter_library_branches)
1238
 
        return tuple(self.iter_library_branches())
1239
 
 
1240
 
    library_branches = property(_get_library_branches, doc="""
1241
 
    Deprecated.
1242
 
 
1243
 
    Branches in this category present in the library.
1244
 
 
1245
 
    :type: tuple of `Branch`
1246
 
    :see: `Category.iter_branches`
1247
 
    """)
1248
 
 
1249
 
 
1250
 
class Branch(CategoryItem, Package, VersionIterable):
1251
 
 
1252
 
    """Arch branch namespace object.
1253
 
 
1254
 
    :see: `Archive`, `Category`, `Version`, `Revision`
1255
 
    """
1256
 
 
1257
 
    def __init__(self, name):
1258
 
        """Create a Branch object from its name.
1259
 
 
1260
 
        :param name: fully-qualified branch name, like
1261
 
            "jdoe@example.com--2004/frob--devo" or
1262
 
            "jdoe@example.com--2004/frob".
1263
 
        :type name: str
1264
 
        :raise errors.NamespaceError: ``name`` is not a valid branch name.
1265
 
        """
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)
1275
 
 
1276
 
    def __getitem__(self, v):
1277
 
        """Instanciate a version belonging to this branch.
1278
 
 
1279
 
        For example ``Branch('jdoe@example.com/frob--devel')['0']`` is
1280
 
        equivalent to ``Branch('jdoe@example.com/frob--devel--0')``.
1281
 
 
1282
 
        :param v: branch id.
1283
 
        :type v: str
1284
 
        :rtype: `Version`
1285
 
 
1286
 
        :raise NamespaceError: argument is not a valid version id.
1287
 
        """
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))))
1291
 
 
1292
 
    def exists(self):
1293
 
        return _arch.branch_exists(self._archive, self._nonarch)
1294
 
 
1295
 
    def iter_versions(self, reverse=False):
1296
 
        for v in _arch.versions(self.fullname, reverse):
1297
 
            yield Version(_unsafe((self._archive, v)))
1298
 
 
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)))
1302
 
 
1303
 
    def get_versions(self, reverse=False):
1304
 
        """Deprecated.
1305
 
 
1306
 
        Versions in this branch.
1307
 
 
1308
 
        :rtype: tuple of `Version`
1309
 
        :see: `iter_versions`
1310
 
        """
1311
 
        deprecated_callable(self.get_versions, self.iter_versions)
1312
 
        return tuple(self.iter_versions(reverse))
1313
 
 
1314
 
    def _get_versions(self):
1315
 
        deprecated_callable((Branch, 'versions'), self.iter_versions)
1316
 
        return tuple(self.iter_versions(reverse=False))
1317
 
 
1318
 
    versions = property(_get_versions, doc="""
1319
 
    Deprecated.
1320
 
 
1321
 
    Versions in this branch.
1322
 
 
1323
 
    :type: tuple of `Version`
1324
 
    :see: `iter_versions`
1325
 
    """)
1326
 
 
1327
 
    def get_library_versions(self, reverse=False):
1328
 
        """Deprecated.
1329
 
 
1330
 
        Versions in this branch present in the library.
1331
 
 
1332
 
        :rtype: tuple of `Version`
1333
 
        :see: `iter_library_versions`
1334
 
        """
1335
 
        deprecated_callable(self.get_library_versions,
1336
 
                            self.iter_library_versions)
1337
 
        return tuple(self.iter_library_versions(reverse))
1338
 
 
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))
1343
 
 
1344
 
    library_versions = property(_get_library_versions, doc="""
1345
 
    Deprecated.
1346
 
 
1347
 
    Versions in this branch present in the library.
1348
 
 
1349
 
    :type: tuple of `Version`
1350
 
    :see: `iter_library_versions`
1351
 
    """)
1352
 
 
1353
 
    def as_version(self):
1354
 
        """Deprecated.
1355
 
 
1356
 
        Latest version in this branch.
1357
 
 
1358
 
        :rtype: `Version`
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`
1363
 
        """
1364
 
        deprecated_callable(self.as_version, self.latest_version)
1365
 
        return list(self.iter_versions(reverse=True))[0]
1366
 
 
1367
 
    def latest_version(self):
1368
 
        """Latest version in this branch.
1369
 
 
1370
 
        :rtype: `Version`
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.
1375
 
        """
1376
 
        try:
1377
 
            return self.iter_versions(reverse=True).next()
1378
 
        except errors.ExecProblem:
1379
 
            try:
1380
 
                self_exists = self.exists()
1381
 
            except errors.ArchiveNotRegistered:
1382
 
                raise ValueError('Archive is not registered: %s'
1383
 
                                 % self.archive)
1384
 
            if not self_exists:
1385
 
                raise ValueError('Branch does not exist: %s' % self)
1386
 
            raise
1387
 
        except StopIteration:
1388
 
            raise ValueError('Branch contains no version: %s' % self)
1389
 
 
1390
 
 
1391
 
 
1392
 
class Version(BranchItem, Package, RevisionIterable):
1393
 
 
1394
 
    """Arch version namespace object.
1395
 
 
1396
 
    :see: `Archive`, `Category`, `Branch`, `Revision`
1397
 
    """
1398
 
 
1399
 
    def __init__(self, name):
1400
 
        """Create a Version object from its name.
1401
 
 
1402
 
        :param name: fully-qualified version name, like
1403
 
           "jdoe@example.com--2004/frob--devo--1.2".
1404
 
        :type name: str
1405
 
 
1406
 
        :note: Nameless branches have no "branch" part in their name.
1407
 
        """
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)
1416
 
 
1417
 
    def __getitem__(self, lvl):
1418
 
        """Instanciate a revision belonging to this version.
1419
 
 
1420
 
        For example ``Version('jdoe@example.com/frob--devel--0')['patch-1']``
1421
 
        is equivalent to
1422
 
        ``Revision('jdoe@example.com/frob--devel--0--patch1')``.
1423
 
 
1424
 
        :param lvl: patch level.
1425
 
        :type lvl: str
1426
 
        :rtype: `Revision`
1427
 
 
1428
 
        :raise NamespaceError: argument is not a valid version patchlevel.
1429
 
        """
1430
 
        if not NameParser.is_patchlevel(lvl):
1431
 
            raise errors.NamespaceError(lvl, 'unqualified patchlevel')
1432
 
        return Revision(_unsafe((self._archive, self._nonarch, lvl)))
1433
 
 
1434
 
    def as_version(self):
1435
 
        """Deprecated.
1436
 
 
1437
 
        This version.
1438
 
 
1439
 
        :rtype: `Version`
1440
 
        """
1441
 
        deprecated_callable(self.as_version, because='Foolish consistency.')
1442
 
        return self
1443
 
 
1444
 
    def exists(self):
1445
 
        return _arch.version_exists(self._archive, self._nonarch)
1446
 
 
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)))
1450
 
 
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)))
1454
 
 
1455
 
    def get_revisions(self, reverse=False):
1456
 
        """Deprecated.
1457
 
 
1458
 
        Revisions in this version.
1459
 
 
1460
 
        :rtype: tuple of `Revision`
1461
 
        :see: `iter_revisions`
1462
 
        """
1463
 
        deprecated_callable(self.get_revisions, self.iter_revisions)
1464
 
        return tuple(self.iter_revisions(reverse))
1465
 
 
1466
 
    def _get_revisions(self):
1467
 
        deprecated_callable((Version, 'revisions'), self.iter_revisions)
1468
 
        return tuple(self.iter_revisions(reverse=False))
1469
 
 
1470
 
    revisions = property(_get_revisions, doc="""
1471
 
    Deprecated.
1472
 
 
1473
 
    Revisions in this version.
1474
 
 
1475
 
    :type: tuple of `Revision`
1476
 
    :see: `iter_revisions`
1477
 
    """)
1478
 
 
1479
 
    def get_library_revisions(self, reverse=False):
1480
 
        """Deprecated.
1481
 
 
1482
 
        Revisions in this version present in the library.
1483
 
 
1484
 
        :rtype: tuple of `Revision`
1485
 
        :see: `iter_library_revisions`
1486
 
        """
1487
 
        deprecated_callable(self.get_library_revisions,
1488
 
                            self.iter_library_revisions)
1489
 
        return tuple(self.iter_library_revisions(reverse))
1490
 
 
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))
1495
 
 
1496
 
    library_revisions = property(_get_library_revisions, doc="""
1497
 
    Deprecated.
1498
 
 
1499
 
    Revisions in this version present in the library.
1500
 
 
1501
 
    :type: tuple of `Revision`
1502
 
    :see: `iter_library_revisions`
1503
 
    """)
1504
 
 
1505
 
    def iter_merges(self, other=None, reverse=False, metoo=True):
1506
 
        """Iterate over merge points in this version.
1507
 
 
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
1513
 
        objects.
1514
 
 
1515
 
        :param other: list merges with that version.
1516
 
        :type other: `Version`
1517
 
        :param reverse: reverse order, recent revisions first.
1518
 
        :type reverse: bool
1519
 
        :param metoo: do not report the presence of a patch within itself
1520
 
        :type metoo: bool
1521
 
 
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`
1525
 
        """
1526
 
        if other is None: othername = None
1527
 
        else: othername = other.fullname
1528
 
        flow = _arch.iter_merges(self.fullname, othername, reverse, metoo)
1529
 
        last_target = None
1530
 
        sources = []
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)
1536
 
            else:
1537
 
                yield self[last_target], itertools.imap(Revision, sources)
1538
 
                last_target = target
1539
 
                sources = [source]
1540
 
        yield self[last_target], itertools.imap(Revision, sources)
1541
 
 
1542
 
    def iter_cachedrevs(self):
1543
 
        """Iterate over the cached revisions in this version.
1544
 
 
1545
 
        :rtype: iterator of `Revision`
1546
 
        """
1547
 
        for rev in _arch.cachedrevs(self.fullname):
1548
 
            lvl = rev.split('--')[-1]
1549
 
            yield self[lvl]
1550
 
 
1551
 
 
1552
 
class Revision(VersionItem):
1553
 
 
1554
 
    """Arch revision namespace object.
1555
 
 
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
1559
 
    """
1560
 
 
1561
 
    def __init__(self, name):
1562
 
        """Create a Revision object from its name.
1563
 
 
1564
 
        :param name: fully-qualified revision, like
1565
 
            "jdoe@example.com--2004/frob--devo--1.2--patch-2".
1566
 
        :type name: str
1567
 
 
1568
 
        :note: Nameless branches have no "branch" part in their name.
1569
 
        """
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(),
1577
 
                    p.get_patchlevel())
1578
 
        VersionItem.__init__(self, *name)
1579
 
        self.__patchlog = Patchlog(self)
1580
 
 
1581
 
    def as_revision(self):
1582
 
        """Deprecated
1583
 
 
1584
 
        Returns this revision. For consistency with `Package.as_revision()`.
1585
 
 
1586
 
        :rtype: `Revision`
1587
 
        """
1588
 
        deprecated_callable(self.as_revision, because='Foolish consistency.')
1589
 
        return self
1590
 
 
1591
 
    def exists(self):
1592
 
        return _arch.revision_exists \
1593
 
               (self._archive, self._version, self._patchlevel)
1594
 
 
1595
 
    def get(self, dir, link=False):
1596
 
        """Construct a project tree for this revision.
1597
 
 
1598
 
        Extract this revision from the archive.
1599
 
 
1600
 
        :param dir: path of the project tree to create. Must not
1601
 
            already exist.
1602
 
        :type dir: str
1603
 
        :param link: hardlink files to revision library instead of copying
1604
 
        :type link: bool
1605
 
        :return: newly created project tree.
1606
 
        :rtype: `WorkingTree`
1607
 
        """
1608
 
        _arch.get(self.fullname, dir, link)
1609
 
        return WorkingTree(dir)
1610
 
 
1611
 
    def get_patch(self, dir):
1612
 
        """Fetch the changeset associated to this revision.
1613
 
 
1614
 
        :param dir: name of the changeset directory to create. Must
1615
 
            not already exist.
1616
 
        :type dir: str
1617
 
        :return: changeset associated to this revision.
1618
 
        :rtype: `Changeset`
1619
 
        """
1620
 
        _arch.get_patch(self.fullname, dir)
1621
 
        return Changeset(dir)
1622
 
 
1623
 
    def get_patchlog(self):
1624
 
        """Deprecated.
1625
 
 
1626
 
        Patchlog associated to this revision.
1627
 
 
1628
 
        :rtype: `Patchlog`
1629
 
        :see: `Revision.patchlog`
1630
 
        """
1631
 
        deprecated_callable(self.get_patchlog, (Revision, 'patchlog'))
1632
 
        return self.patchlog
1633
 
 
1634
 
    def _get_patchlog(self):
1635
 
        return self.__patchlog
1636
 
 
1637
 
    patchlog = property(_get_patchlog, doc="""
1638
 
    Patchlog associated to this revision.
1639
 
 
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.
1643
 
 
1644
 
    :type: `Patchlog`
1645
 
    :see: `ArchSourceTree.iter_logs`
1646
 
    """)
1647
 
 
1648
 
    def make_continuation(self, target):
1649
 
        """Create a continuation of this revision in the target version.
1650
 
 
1651
 
        :param target: version to create a continuation into. If it does not
1652
 
            exist yet, it is created.
1653
 
        :type target: Version
1654
 
        """
1655
 
        _check_version_param(target, 'target')
1656
 
        target_name = target.fullname
1657
 
        _arch.archive_setup(target_name)
1658
 
        _arch.tag(self.fullname, target_name)
1659
 
 
1660
 
    def library_add(self, library=None):
1661
 
        """Add this revision to the library.
1662
 
 
1663
 
        :postcondition: self in self.version.iter_library_revisions()
1664
 
        """
1665
 
        _arch.library_add(self.fullname, library)
1666
 
 
1667
 
    def library_remove(self):
1668
 
        """Remove this revision from the library.
1669
 
 
1670
 
        :precondition: self in self.version.iter_library_revisions()
1671
 
        :postcondition: self not in self.version.iter_library_revisions()
1672
 
        """
1673
 
        _arch.library_remove(self.fullname)
1674
 
 
1675
 
    def library_find(self):
1676
 
        """The copy of this revision in the library.
1677
 
 
1678
 
        :rtype: `LibraryTree`
1679
 
        :precondition: self in self.version.iter_library_revisions()
1680
 
        """
1681
 
        return LibraryTree(_arch.library_find(self.fullname))
1682
 
 
1683
 
    #def library_file(self, file):
1684
 
    #    raise NotImplementedError, "library_file is not yet implemented"
1685
 
 
1686
 
    def get_ancestor(self):
1687
 
        """Deprecated.
1688
 
 
1689
 
        Parent revision.
1690
 
 
1691
 
        :return:
1692
 
            - The previous namespace revision, if this revision is regular
1693
 
              commit.
1694
 
            - The tag origin, if this revision is a continuation
1695
 
            - ``None`` if this revision is an import.
1696
 
 
1697
 
        :rtype: `Revision` or None
1698
 
        :see: `Revision.ancestor`
1699
 
        """
1700
 
        deprecated_callable(self.get_ancestor, (Revision, 'ancestor'))
1701
 
        return self.ancestor
1702
 
 
1703
 
    def _get_ancestor(self):
1704
 
        rvsn = _arch.ancestor(self.fullname)
1705
 
        if rvsn is None:
1706
 
            return None
1707
 
        else:
1708
 
            return Revision(rvsn)
1709
 
 
1710
 
    ancestor = property(_get_ancestor, doc="""
1711
 
    Parent revision.
1712
 
 
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.
1716
 
 
1717
 
    :type: `Revision` or None
1718
 
    """)
1719
 
 
1720
 
    def get_previous(self):
1721
 
        """Deprecated.
1722
 
 
1723
 
        Previous namespace revision.
1724
 
 
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`
1729
 
        """
1730
 
        deprecated_callable(self.get_previous, (Revision, 'previous'))
1731
 
        return self.previous
1732
 
 
1733
 
    def _get_previous(self):
1734
 
        rvsn = _arch.previous(self.fullname)
1735
 
        if rvsn is None: return None
1736
 
        else: return Revision(rvsn)
1737
 
 
1738
 
    previous = property(_get_previous, doc="""
1739
 
    Previous namespace revision.
1740
 
 
1741
 
    The previous revision in the same version, or None if this revision is a
1742
 
    ``base-0``.
1743
 
 
1744
 
    :type: `Revision` or None
1745
 
    """)
1746
 
 
1747
 
    def iter_ancestors(self, metoo=False):
1748
 
        """Ancestor revisions.
1749
 
 
1750
 
        :param metoo: yield ``self`` as the first revision.
1751
 
        :type metoo: bool
1752
 
        :return: all the revisions in that line of development.
1753
 
        :rtype: iterator of `Revision`
1754
 
        """
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)
1759
 
 
1760
 
    def cache(self, cache=None):
1761
 
        """Cache a full source tree for this revision in its archive.
1762
 
 
1763
 
        :param cache: cache root for trees with pristines.
1764
 
        :type cache: bool
1765
 
        """
1766
 
        _arch.cacherev(self.fullname, cache)
1767
 
 
1768
 
    def uncache(self):
1769
 
        """Remove the cached tree of this revision from its archive."""
1770
 
        _arch.uncacherev(self.fullname)
1771
 
 
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])
1776
 
 
1777
 
    _checksum_regex = re.compile(
1778
 
        "^Signature-for: (.*)/(.*)\n"
1779
 
        "([a-zA-Z0-9]+ [^/\\s]+ [a-fA-F0-9]+\n)*",
1780
 
        re.MULTILINE)
1781
 
 
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]
1789
 
        checksums = {}
1790
 
        for algo, name, value in checksum_words:
1791
 
            name_sums = checksums.setdefault(name, dict())
1792
 
            name_sums[algo] = value
1793
 
        return checksums
1794
 
 
1795
 
    def iter_files(self):
1796
 
        """Files stored in the archive for that revision.
1797
 
 
1798
 
        :rtype: iterable of `RevisionFile`
1799
 
        """
1800
 
        import urllib
1801
 
        for checksum_name, can_fail in \
1802
 
                (('checksum', False), ('checksum.cacherev', True)):
1803
 
            try:
1804
 
                checksum_file = urllib.urlopen(self._file_url(checksum_name))
1805
 
            except IOError:
1806
 
                if can_fail: continue
1807
 
                else: raise
1808
 
            try:
1809
 
                checksum_data = checksum_file.read()
1810
 
            finally:
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)
1816
 
 
1817
 
 
1818
 
public('RevisionFile')
1819
 
 
1820
 
class RevisionFile(object):
1821
 
 
1822
 
    """File component of an archived revision.
1823
 
 
1824
 
    :ivar revision: revision this file belongs to.
1825
 
    :type revision: `Revision`
1826
 
    :ivar name: name of that file.
1827
 
    :type name: str
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
1832
 
    """
1833
 
 
1834
 
    def __init__(self, revision, name, checksums):
1835
 
        self.revision = revision
1836
 
        self.name = name
1837
 
        self.checksums = checksums
1838
 
 
1839
 
    def _get_data(self):
1840
 
        import urllib
1841
 
        my_file = urllib.urlopen(self.revision._file_url(self.name))
1842
 
        try:
1843
 
            ret = my_file.read()
1844
 
        finally:
1845
 
            my_file.close()
1846
 
        return ret
1847
 
 
1848
 
    data = property(_get_data,
1849
 
                    """Content of of that file.
1850
 
 
1851
 
                    :type: str
1852
 
                    """)
1853
 
 
1854
 
 
1855
 
### Patch logs ###
1856
 
 
1857
 
import email
1858
 
import email.Parser
1859
 
import email.Generator
1860
 
 
1861
 
public('Patchlog')
1862
 
 
1863
 
class Patchlog(object):
1864
 
 
1865
 
    """Log entry associated to a revision.
1866
 
 
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
1870
 
    extension headers.
1871
 
 
1872
 
    Patchlogs are formatted according to RFC-822, and are parsed using the
1873
 
    standard email-handling facilities.
1874
 
 
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.
1877
 
 
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.
1881
 
 
1882
 
    Additional properties provide appropriate standard conversion of the
1883
 
    standard headers.
1884
 
    """
1885
 
 
1886
 
    def __init__(self, revision, tree=None, fromlib=False):
1887
 
        """Patchlog associated to the given revision.
1888
 
 
1889
 
        The patchlog may be retrieved from the provided ``tree``, from the
1890
 
        revision library if ``fromlib`` is set, or from the archive.
1891
 
 
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
1896
 
        """
1897
 
        if isinstance(revision, Revision):
1898
 
            self.__revision = revision.fullname
1899
 
        elif isinstance(revision, _unsafe):
1900
 
            self.__revision, = revision
1901
 
        else:
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
1907
 
        self.tree = None
1908
 
        if tree is not None: self.tree = str(tree)
1909
 
        self.fromlib = bool(fromlib)
1910
 
        self.__email = None
1911
 
 
1912
 
    def __repr__(self):
1913
 
        if self.tree is not None:
1914
 
            return 'Patchlog(%s)' % repr(self.__revision)
1915
 
        else:
1916
 
            return 'Patchlog(%s, tree=%s)' % (repr(self.__revision),
1917
 
                                              repr(self.tree))
1918
 
 
1919
 
    def _parse(self):
1920
 
        """Deferred parsing of the log text."""
1921
 
        if self.__email is not None:
1922
 
            return self.__email
1923
 
        if self.tree is not None:
1924
 
            s = _arch.cat_log(self.tree, self.__revision)
1925
 
        elif self.fromlib:
1926
 
            s = _arch.library_log(self.__revision)
1927
 
        else:
1928
 
            s = _arch.cat_archive_log(self.__revision)
1929
 
        self.__email = email.Parser.Parser().parsestr(s)
1930
 
        return self.__email
1931
 
 
1932
 
    def __getitem__(self, header):
1933
 
        """Text of a patchlog header by name.
1934
 
 
1935
 
        :param header: name of a patchlog header.
1936
 
        :type header: str
1937
 
        :return: text of the header, or None if the header is not present.
1938
 
        :rtype: str, None
1939
 
        """
1940
 
        return self._parse()[header]
1941
 
 
1942
 
    def items(self):
1943
 
        """List of 2-tuples containing all headers and values.
1944
 
 
1945
 
        :rtype: list of 2-tuple of str
1946
 
        """
1947
 
        return self._parse().items()
1948
 
 
1949
 
    def get_revision(self):
1950
 
        """Deprecated.
1951
 
 
1952
 
        Revision associated to this patchlog.
1953
 
 
1954
 
        :rtype: `Revision`
1955
 
        :see: `Patchlog.revision`
1956
 
        """
1957
 
        deprecated_callable(self.get_revision, (Patchlog, 'revision'))
1958
 
        return self.revision
1959
 
 
1960
 
    def _get_revision(self):
1961
 
        assert self.__revision == self['Archive']+'/'+self['Revision']
1962
 
        return Revision(self.__revision)
1963
 
 
1964
 
    revision = property(_get_revision, doc="""
1965
 
    Revision associated to this patchlog.
1966
 
 
1967
 
    :type: `Revision`
1968
 
    """)
1969
 
 
1970
 
    def get_summary(self):
1971
 
        """Deprecated.
1972
 
 
1973
 
        Patchlog summary, a one-line natural language description.
1974
 
 
1975
 
        :rtype: str
1976
 
        :see: `Patchlog.summary`
1977
 
        """
1978
 
        deprecated_callable(self.get_summary, (Patchlog, 'summary'))
1979
 
        return self.summary
1980
 
 
1981
 
    def _get_summary(self):
1982
 
        return self['Summary']
1983
 
 
1984
 
    summary = property(_get_summary, doc="""
1985
 
    Patchlog summary, a one-line natural language description.
1986
 
 
1987
 
    :type: str
1988
 
    """)
1989
 
 
1990
 
    def get_description(self):
1991
 
        """Deprecated.
1992
 
 
1993
 
        Patchlog body, a long natural language description.
1994
 
 
1995
 
        :rtype: str
1996
 
        :see: `Patchlog.description`
1997
 
        """
1998
 
        deprecated_callable(self.get_description, (Patchlog, 'description'))
1999
 
        return self.description
2000
 
 
2001
 
    def _get_description(self):
2002
 
        m = self._parse()
2003
 
        assert not m.is_multipart()
2004
 
        return m.get_payload()
2005
 
 
2006
 
    description = property(_get_description, doc="""
2007
 
    Patchlog body, a long natural language description.
2008
 
 
2009
 
    :type: str
2010
 
    """)
2011
 
 
2012
 
    def get_date(self):
2013
 
        """Deprecated.
2014
 
 
2015
 
        For the description of the local time tuple, see the
2016
 
        documentation of the `time` module.
2017
 
 
2018
 
        :rtype: local time tuple
2019
 
        :see: `Patchlog.date`
2020
 
        """
2021
 
        deprecated_callable(self.get_date, (Patchlog, 'date'))
2022
 
        return self.date
2023
 
 
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')
2028
 
 
2029
 
    date = property(_get_date, doc="""
2030
 
    Time of the associated revision.
2031
 
 
2032
 
    For the description of the local time tuple, see the documentation
2033
 
    of the `time` module.
2034
 
 
2035
 
    :type: local time tuple
2036
 
    """)
2037
 
 
2038
 
    def get_creator(self):
2039
 
        """Deprecated.
2040
 
 
2041
 
        User id of the the creator of the associated revision.
2042
 
 
2043
 
        :rtype: str
2044
 
        :see: `Patchlog.creator`
2045
 
        """
2046
 
        deprecated_callable(self.get_creator, (Patchlog, 'creator'))
2047
 
        return self.creator
2048
 
 
2049
 
    def _get_creator(self):
2050
 
        return self['Creator']
2051
 
 
2052
 
    creator = property(_get_creator, doc="""
2053
 
    User id of the the creator of the associated revision.
2054
 
 
2055
 
    :type: str
2056
 
    """)
2057
 
 
2058
 
    def get_continuation(self):
2059
 
        """Deprecated.
2060
 
 
2061
 
        Ancestor of tag revisions.
2062
 
        None for commit and import revisions.
2063
 
 
2064
 
        :rtype: `Revision`, None.
2065
 
        :see: `Patchlog.continuation_of`
2066
 
        """
2067
 
        deprecated_callable(
2068
 
            self.get_continuation, (Patchlog, 'continuation_of'))
2069
 
        return self.continuation_of
2070
 
 
2071
 
    def _get_continuation(self):
2072
 
        deprecated_callable(
2073
 
            (Patchlog, 'continuation'), (Patchlog, 'continuation_of'))
2074
 
        return self.continuation_of
2075
 
 
2076
 
    continuation = property(_get_continuation, doc="""
2077
 
    Deprecated.
2078
 
 
2079
 
    Ancestor of tag revisions.
2080
 
    None for commit and import revisions.
2081
 
 
2082
 
    :type: `Revision`, None.
2083
 
    :see: `Patchlog.continuation_of`
2084
 
    """)
2085
 
 
2086
 
    def _get_continuation_of(self):
2087
 
        c = self['Continuation-of']
2088
 
        if c is None:
2089
 
            return None
2090
 
        return Revision(c)
2091
 
 
2092
 
    continuation_of = property(_get_continuation_of, doc="""
2093
 
    Ancestor of tag revisions.
2094
 
    None for commit and import revisions.
2095
 
 
2096
 
    :type: `Revision`, None.
2097
 
    """)
2098
 
 
2099
 
    def new_merge_revision(self, revision_name):
2100
 
        """Helper to produce a correct output even on broken baz logs
2101
 
 
2102
 
        :param revision_name: The name of a revision to produce
2103
 
        :type revision_name: str
2104
 
        """
2105
 
        if revision_name == "!!!!!nothing-should-depend-on-this":
2106
 
            return self.revision
2107
 
        else:
2108
 
            return Revision(revision_name)
2109
 
 
2110
 
    def get_new_patches(self):
2111
 
        """Deprecated.
2112
 
 
2113
 
        New-patches header as an iterable of Revision.
2114
 
 
2115
 
        :rtype: iterable of `Revision`
2116
 
        :see: `Patchlog.new_patches`
2117
 
        """
2118
 
        deprecated_callable(
2119
 
            Patchlog.get_new_patches, (Patchlog, 'new_patches'))
2120
 
        return self.new_patches
2121
 
 
2122
 
    def _get_new_patches(self):
2123
 
        return map(self.new_merge_revision, self['New-patches'].split())
2124
 
 
2125
 
    new_patches = property(_get_new_patches, doc="""
2126
 
    New-patches header as an iterable of Revision.
2127
 
 
2128
 
    Patchlogs added in this revision.
2129
 
 
2130
 
    :type: iterable of `Revision`
2131
 
    """)
2132
 
 
2133
 
    def get_merged_patches(self):
2134
 
        """Deprecated.
2135
 
 
2136
 
        Revisions merged in this revision. That is the revisions
2137
 
        listed in the New-patches header except the revision of the
2138
 
        patchlog.
2139
 
 
2140
 
        :rtype: iterable of `Revision`
2141
 
        :see: `Patchlog.merged_patches`
2142
 
        """
2143
 
        deprecated_callable(
2144
 
            Patchlog.get_merged_patches, (Patchlog, 'merged_patches'))
2145
 
        return self.merged_patches
2146
 
 
2147
 
    def _get_merged_patches(self):
2148
 
        rvsn = self.revision.fullname
2149
 
        return filter(lambda(r): r.fullname != rvsn, self.new_patches)
2150
 
 
2151
 
    merged_patches = property(_get_merged_patches, doc="""
2152
 
    Revisions merged in this revision.
2153
 
 
2154
 
    That is the revisions listed in the New-patches header except the
2155
 
    revision of the patchlog.
2156
 
 
2157
 
    :type: iterable of `Revision`
2158
 
    """)
2159
 
 
2160
 
    def get_new_files(self):
2161
 
        """Deprecated.
2162
 
 
2163
 
        Source files added in the associated revision.
2164
 
 
2165
 
        :rtype: iterable of `FileName`
2166
 
        :see: `Patchlog.new_files`
2167
 
        """
2168
 
        deprecated_callable(self.get_new_files, (Patchlog, 'new_files'))
2169
 
        return self.new_files
2170
 
 
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() ]
2175
 
 
2176
 
    new_files = property(_get_new_files, doc="""
2177
 
    Source files added in the associated revision.
2178
 
 
2179
 
    :type: iterable of `FileName`
2180
 
    """)
2181
 
 
2182
 
    def get_modified_files(self):
2183
 
        """Deprecated.
2184
 
 
2185
 
        Names of source files modified in the associated revision.
2186
 
 
2187
 
        :rtype: iterable of `FileName`
2188
 
        """
2189
 
        deprecated_callable(
2190
 
            self.get_modified_files, (Patchlog, 'modified_files'))
2191
 
        return self.modified_files
2192
 
 
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() ]
2197
 
 
2198
 
    modified_files = property(_get_modified_files, doc="""
2199
 
    Names of source files modified in the associated revision.
2200
 
 
2201
 
    :type: iterable of `FileName`
2202
 
    """)
2203
 
 
2204
 
    def get_renamed_files(self):
2205
 
        """Deprecated.
2206
 
 
2207
 
        Source files renames in the associated revision.
2208
 
 
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.
2212
 
 
2213
 
        :rtype: dict
2214
 
        """
2215
 
        deprecated_callable(
2216
 
            self.get_renamed_files, (Patchlog, 'renamed_files'))
2217
 
        return self.renamed_files
2218
 
 
2219
 
    def _get_renamed_files(self):
2220
 
        s = self['Renamed-files']
2221
 
        renames = {}
2222
 
        if s is None:
2223
 
            return renames
2224
 
        s = s.split()
2225
 
        for i in range(len(s)/2):
2226
 
            renames[s[i*2]] = s[i*2+1]
2227
 
        return renames
2228
 
 
2229
 
    renamed_files = property(_get_renamed_files, doc="""
2230
 
    Source files renames in the associated revision.
2231
 
 
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.
2235
 
 
2236
 
    :type: dict
2237
 
    """)
2238
 
 
2239
 
    def get_removed_files(self):
2240
 
        """Deprecated.
2241
 
 
2242
 
        Names of source files removed in the associated revision.
2243
 
 
2244
 
        :rtype: iterable of `FileName`
2245
 
        """
2246
 
        deprecated_callable(
2247
 
            self.get_removed_files, (Patchlog, 'removed_files'))
2248
 
        return self.removed_files
2249
 
 
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() ]
2254
 
 
2255
 
    removed_files = property(_get_removed_files, doc="""
2256
 
    Names of source files removed in the associated revision.
2257
 
 
2258
 
    :type: iterable of `FileName`
2259
 
    """)
2260
 
 
2261
 
 
2262
 
public('LogMessage')
2263
 
 
2264
 
class LogMessage(object):
2265
 
 
2266
 
    """Log message for use with commit, import or tag operations.
2267
 
 
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.
2271
 
 
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.
2275
 
 
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
2279
 
    email.Generator.
2280
 
    """
2281
 
 
2282
 
    def __init__(self, name):
2283
 
        self.__name = name
2284
 
        self.__email = None
2285
 
        self.__dirty = False
2286
 
 
2287
 
    def get_name(self): return self.__name
2288
 
    name = property(get_name)
2289
 
 
2290
 
    def load(self):
2291
 
        """Read the log message from disk."""
2292
 
        self.__email = email.Parser.Parser().parse(file(self.__name))
2293
 
        self.__dirty = False
2294
 
 
2295
 
    def save(self):
2296
 
        """Write the log message to disk."""
2297
 
        if self.__dirty:
2298
 
            f = file(self.__name, 'w')
2299
 
            email.Generator.Generator(f).flatten(self.__email)
2300
 
            self.__dirty = False
2301
 
 
2302
 
    def clear(self):
2303
 
        """Clear the in-memory log message.
2304
 
 
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.
2308
 
        """
2309
 
        self.__email = email.Message.Message()
2310
 
        self.__dirty = True
2311
 
 
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]
2316
 
 
2317
 
    def __setitem__(self, header, text):
2318
 
        """Set a patchlog header."""
2319
 
        if self.__email is None: self.load()
2320
 
        try:
2321
 
            self.__email.replace_header(header, text)
2322
 
        except KeyError:
2323
 
            self.__email.add_header(header, text)
2324
 
        self.__dirty = True
2325
 
 
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()
2331
 
 
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)
2336
 
        self.__dirty = True
2337
 
 
2338
 
    description = property(get_description, set_description)
2339
 
 
2340
 
 
2341
 
### Source trees ###
2342
 
 
2343
 
public(
2344
 
    'init_tree',
2345
 
    'in_source_tree',
2346
 
    'tree_root',
2347
 
    )
2348
 
 
2349
 
def init_tree(directory, version=None, nested=False):
2350
 
    """Initialize a new project tree.
2351
 
 
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
2355
 
       log version.
2356
 
    :type version: `Version`
2357
 
    :param nested: if true, the command will succeed even if 'directory'
2358
 
        is already within a source tree.
2359
 
    :type nested: bool
2360
 
    :return: source tree object for the given directory.
2361
 
    :rtype: `WorkingTree`
2362
 
    """
2363
 
    if version is not None:
2364
 
        version = _version_param(version)
2365
 
    _arch.init_tree(directory, version, nested)
2366
 
    return WorkingTree(directory)
2367
 
 
2368
 
 
2369
 
def in_source_tree(directory=None):
2370
 
    """Is directory inside a Arch source tree?
2371
 
 
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.
2375
 
    :rtype: bool
2376
 
 
2377
 
    :warning: omitting the ``directory`` argument is deprecated.
2378
 
    """
2379
 
    if directory is None:
2380
 
        deprecated_usage(
2381
 
            in_source_tree, "Argument defaults to current direcotry.")
2382
 
        directory = '.'
2383
 
    return _arch.in_tree(directory)
2384
 
 
2385
 
 
2386
 
def tree_root(directory=None):
2387
 
    """SourceTree containing the given directory.
2388
 
 
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`
2394
 
 
2395
 
    :warning: omitting the ``directory`` argument is deprecated.
2396
 
    """
2397
 
    if directory is None:
2398
 
        deprecated_usage(tree_root, "Argument defaults to current directory.")
2399
 
        directory = '.'
2400
 
    root = _arch.tree_root(directory)
2401
 
    # XXX use SourceTree to instanciate either WorkingTree or LibraryTree
2402
 
    return SourceTree(root)
2403
 
 
2404
 
 
2405
 
public('SourceTree',
2406
 
       'ForeignTree',
2407
 
       'ArchSourceTree',
2408
 
       'LibraryTree',
2409
 
       'WorkingTree')
2410
 
 
2411
 
class SourceTree(DirName):
2412
 
 
2413
 
    """Abstract base class for `ForeignTree` and `ArchSourceTree`."""
2414
 
 
2415
 
    def __new__(cls, root=None):
2416
 
        """Create a source tree object for the given root path.
2417
 
 
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.
2421
 
 
2422
 
        If root is omitted, use the tree-root of the current working directory.
2423
 
        """
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)
2429
 
        else:
2430
 
            if not 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)
2438
 
            else:
2439
 
                assert not _arch.in_tree(root)
2440
 
                return ForeignTree(root)
2441
 
 
2442
 
 
2443
 
class ForeignTree(SourceTree):
2444
 
    """Generic source tree without Arch support.
2445
 
 
2446
 
    Unlike Arch source trees, the root may not even exist.
2447
 
    """
2448
 
    def __init__(self, root): pass
2449
 
 
2450
 
 
2451
 
class ArchSourceTree(SourceTree):
2452
 
 
2453
 
    """Abstract base class for Arch source trees."""
2454
 
 
2455
 
    def get_tree_version(self):
2456
 
        """Default version of the tree, also called tree-version.
2457
 
 
2458
 
        :raise errors.TreeVersionError: no valid default version set.
2459
 
        :raise IOError: unable to read the ++default-version file.
2460
 
        """
2461
 
        data = _arch.tree_version(self)
2462
 
        if data is None:
2463
 
            raise errors.TreeVersionError(self)
2464
 
        try:
2465
 
            return Version(data)
2466
 
        except errors.NamespaceError:
2467
 
            raise errors.TreeVersionError(self, data)
2468
 
    tree_version = property(get_tree_version)
2469
 
 
2470
 
    def _get_tree_revision(self):
2471
 
        """Revision of the last patchlog for the tree-version.
2472
 
 
2473
 
        :raise errors.TreeVersionError: no valid default version set.
2474
 
        :raise IOError: unable to read the ++default-version file.
2475
 
        """
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)
2482
 
 
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
2487
 
 
2488
 
    def get_tagging_method(self):
2489
 
        return _arch.tagging_method(self)
2490
 
    tagging_method = property(get_tagging_method)
2491
 
 
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))
2497
 
        elif both:
2498
 
            def ctor(f):
2499
 
                if os.path.isdir(self/f): return DirName(name_unescape(f))
2500
 
                else: return FileName(name_unescape(f))
2501
 
            return ctor
2502
 
        else:
2503
 
            raise AssertionError, 'not reached'
2504
 
 
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):
2509
 
        """Tree inventory.
2510
 
 
2511
 
        The kind of files looked for is specified by setting to
2512
 
        ``True`` exactly one of the following keyword arguments:
2513
 
 
2514
 
            ``source``, ``precious``, ``backups``, ``junk``,
2515
 
            ``unrecognized``, ``trees``.
2516
 
 
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:
2520
 
 
2521
 
            ``directories``, ``files``, ``both``.
2522
 
 
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.
2530
 
 
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.
2536
 
 
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.
2540
 
 
2541
 
        :keyword limit: restrict the inventory to this directory. Must
2542
 
            be the name of a directory relative to the tree root.
2543
 
 
2544
 
        :rtype: iterator of `FileName`, `DirName`, `ArchSourceTree`
2545
 
            according to the arguments.
2546
 
        """
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))
2554
 
 
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,
2558
 
                           limit=None):
2559
 
        """Tree inventory with file ids.
2560
 
 
2561
 
        :see: `ArchSourceTree.iter_inventory`
2562
 
 
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.
2566
 
        """
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))
2574
 
 
2575
 
    def inventory(self, *args, **kw):
2576
 
        """Inventory of the source tree as a list. See iter_inventory.
2577
 
 
2578
 
        DEPRECATED
2579
 
        """
2580
 
        return list (self.iter_inventory(*args, **kw))
2581
 
 
2582
 
    def get_tree(self):
2583
 
        return util.sorttree(self.inventory(source=True, both=True))
2584
 
 
2585
 
    def iter_log_versions(self, limit=None, reverse=False):
2586
 
        """Iterate over versions this tree has a log-version for.
2587
 
 
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.
2591
 
        :type reverse: bool
2592
 
        """
2593
 
        kwargs = {}
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):
2605
 
            yield Version(vsn)
2606
 
 
2607
 
    def iter_logs(self, version=None, reverse=False):
2608
 
        """Iterate over patchlogs present in this tree.
2609
 
 
2610
 
        :param version: list patchlogs from this version. Defaults to
2611
 
            the tree-version.
2612
 
        :type version: `Version`
2613
 
        :param reverse: iterate more recent logs first.
2614
 
        :type reverse: bool
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.
2619
 
        """
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)
2624
 
 
2625
 
    def get_tag(self, file):
2626
 
        """File id set by an explicit id tag or tagline.
2627
 
 
2628
 
        :param file: name of a source file relative to the tree-root.
2629
 
        :type file: str
2630
 
        :return: file id if the file has an explicit id or a tagline.
2631
 
        :rtype: str
2632
 
        """
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)
2636
 
 
2637
 
    def file_find(self, name, revision):
2638
 
         """Find a pristine copy of a file for a given revision.
2639
 
 
2640
 
         Will create the requested revision in the library or in the
2641
 
         current tree pristines if needed.
2642
 
 
2643
 
         :param name: name of the file.
2644
 
         :type name: str
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.
2651
 
         """
2652
 
         revision = _revision_param(revision)
2653
 
         result = _arch.file_find(self, name, revision)
2654
 
         if result is None:
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)
2659
 
 
2660
 
 
2661
 
class LibraryTree(ArchSourceTree):
2662
 
 
2663
 
    """Read-only Arch source tree."""
2664
 
 
2665
 
    def __init__(self, root):
2666
 
        """Create a LibraryTree object with the given root path.
2667
 
 
2668
 
        Root must be a directory containing a Arch source tree in the
2669
 
        revision library.
2670
 
        """
2671
 
        ArchSourceTree.__init__(self, root)
2672
 
        self.check_is_tree_root()
2673
 
 
2674
 
 
2675
 
class WorkingTree(ArchSourceTree):
2676
 
 
2677
 
    """Working source tree, Arch source tree which can be modified."""
2678
 
 
2679
 
    def __init__(self, root):
2680
 
        """Create WorkingTree object with the given root path.
2681
 
 
2682
 
        Root must be a directory containing a valid Arch source tree
2683
 
        outside of the revision library.
2684
 
        """
2685
 
        ArchSourceTree.__init__(self, root)
2686
 
        self.check_is_tree_root()
2687
 
 
2688
 
    def sync_tree(self, revision):
2689
 
        """Adds the patchlogs in the given revision to the current tree.
2690
 
 
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.
2694
 
 
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.
2699
 
        """
2700
 
        revision = _version_revision_param(revision)
2701
 
        _arch.sync_tree(self, revision)
2702
 
 
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)
2707
 
 
2708
 
    def has_changes(self):
2709
 
        """Are there uncommited changes is this source tree?
2710
 
 
2711
 
        :rtype: bool
2712
 
        """
2713
 
        return _arch.has_changes(self)
2714
 
 
2715
 
    def changes(self, revision=None, output=None):
2716
 
        """Uncommited changes in this tree.
2717
 
 
2718
 
        :param revision: produce the changeset between this revision
2719
 
            and the tree. If ``revision`` is ``None``, use the
2720
 
            tree-revision.
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.
2725
 
        :rtype: Changeset
2726
 
        :raise errors.TreeVersionError: no valid default version set.
2727
 
        :raise IOError: unable to read the ++default-version file.
2728
 
        """
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)
2733
 
 
2734
 
 
2735
 
    def star_merge(self, from_=None, reference=None, forward=False,
2736
 
                   diff3=False):
2737
 
        """Merge mutually merged branches.
2738
 
 
2739
 
        :param from_: branch to merge changes from, ``None`` means the
2740
 
            ``tree-version``.
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.
2746
 
        :type forward: bool
2747
 
        :param diff3: produce inline conflict markers instead of
2748
 
            ``.rej`` files.
2749
 
        :type diff3: bool
2750
 
        :raise errors.NamespaceError: ``from_`` or ``reference`` is
2751
 
            not a valid version or revision name.
2752
 
        """
2753
 
        list(self.iter_star_merge(from_, reference, forward, diff3))
2754
 
 
2755
 
    def iter_star_merge(self, from_=None, reference=None,
2756
 
                   forward=False, diff3=False):
2757
 
        """Merge mutually merged branches.
2758
 
 
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.
2763
 
 
2764
 
        :param `from_`: branch to merge changes from, ``None`` means the
2765
 
            ``tree-version``.
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.
2771
 
        :type forward: bool
2772
 
        :param diff3: produce inline conflict markers instead of
2773
 
            ``.rej`` files.
2774
 
        :type diff3: bool
2775
 
        :raise errors.NamespaceError: ``from_`` or ``reference`` is
2776
 
            not a valid version or revision name.
2777
 
        :return: `MergeOutcome` iterator
2778
 
        :rtype: `iter_changes`
2779
 
        """
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)
2785
 
 
2786
 
    def iter_star_merge(self, from_=None, reference=None,
2787
 
                        forward=False, diff3=False):
2788
 
        """Merge mutually merged branches.
2789
 
 
2790
 
        :param `from_`: branch to merge changes from, ``None`` means the
2791
 
            ``tree-version``.
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.
2797
 
        :type forward: bool
2798
 
        :param diff3: produce inline conflict markers instead of
2799
 
            ``.rej`` files.
2800
 
        :type diff3: bool
2801
 
        :raise errors.NamespaceError: ``from_`` or ``reference`` is
2802
 
            not a valid version or revision name.
2803
 
        :rtype: `ChangesetApplication`
2804
 
        """
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))
2813
 
 
2814
 
    def undo(self, revision=None, output=None, quiet=False, throw_away=False):
2815
 
        """Undo and save changes in a project tree.
2816
 
 
2817
 
        Remove local changes since revision and optionally save them
2818
 
        as a changeset.
2819
 
 
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.
2826
 
        :type output: str
2827
 
        :keyword quiet: OBSOLETE. Incremental output is always discarded.
2828
 
        :type quiet: bool
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
2835
 
        """
2836
 
        assert sum(map(bool, (output, throw_away))) < 2
2837
 
        if output is None:
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)
2844
 
 
2845
 
    def redo(self, patch=None, keep=False, quiet=False):
2846
 
        """Redo changes in a project tree.
2847
 
 
2848
 
        Apply patch to the project tree and delete patch.
2849
 
 
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.
2852
 
 
2853
 
        If keep is true, the patch directory is not deleted.
2854
 
        """
2855
 
        _arch.redo(self, patch, keep, quiet)
2856
 
 
2857
 
    def set_tagging_method(self, method):
2858
 
        _arch.set_tagging_method(self, method)
2859
 
    tagging_method = property(ArchSourceTree.get_tagging_method,
2860
 
                              set_tagging_method)
2861
 
 
2862
 
    def add_tag(self, file):
2863
 
        _arch.add(self/file)
2864
 
 
2865
 
    def move_tag(self, src, dest):
2866
 
        _arch.move(self/src, self/dest)
2867
 
 
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)
2873
 
 
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)
2879
 
            os.unlink(fullfile)
2880
 
        elif os.path.isdir(fullfile):
2881
 
            shutil.rmtree(fullfile)
2882
 
 
2883
 
    def del_file(self, file):
2884
 
        fullfile = self/file
2885
 
        if os.path.isfile(fullfile):
2886
 
            os.unlink(fullfile)
2887
 
        else:
2888
 
            shutil.rmtree(fullfile)
2889
 
 
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)
2894
 
 
2895
 
    def import_(self, log=None):
2896
 
        """Archive a full-source base-0 revision.
2897
 
 
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
2900
 
        the tree is used.
2901
 
 
2902
 
        The --summary, --log-message and --setup options to tla are
2903
 
        mere CLI convenience features and are not directly supported.
2904
 
        """
2905
 
        if isinstance(log, LogMessage):
2906
 
            log.save()
2907
 
            log = log.name
2908
 
        assert log is None or isinstance(log, str)
2909
 
        _arch.import_(self, log)
2910
 
 
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.
2914
 
 
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
2920
 
            ``tla make-log``.
2921
 
        :type log: `LogMessage`
2922
 
        :keyword strict: perform a strict tree-lint before commiting.
2923
 
        :type strict: bool
2924
 
        :keyword seal: create a ``version-0`` revision.
2925
 
        :type seal: bool
2926
 
        :keyword fix: create a ``versionfix`` revision.
2927
 
        :type fix: bool
2928
 
        :keyword out_of_date_ok: commit even if the tree is out of
2929
 
            date.
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.
2934
 
 
2935
 
        The --summary and --log-message options to tla are mere CLI
2936
 
        convenience features and are not directly supported.
2937
 
 
2938
 
        :see: `WorkingTree.iter_commit`
2939
 
        """
2940
 
        for unused in self.iter_commit(
2941
 
            log, strict, seal, fix, out_of_date_ok, file_list, version):
2942
 
            pass
2943
 
 
2944
 
    def log_for_merge(self):
2945
 
        """Standard arch log of newly merged patches.
2946
 
 
2947
 
        :rtype: str
2948
 
        """
2949
 
        return _arch.log_for_merge(self)
2950
 
 
2951
 
 
2952
 
    def iter_commit(self, log=None, strict=False, seal=False, fix=False,
2953
 
                    out_of_date_ok=False, file_list=None, version=None,
2954
 
                    base=None):
2955
 
        """Archive a changeset-based revision, returning an iterator.
2956
 
 
2957
 
 
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
2963
 
            ``tla make-log``.
2964
 
        :type log: `LogMessage`
2965
 
        :keyword strict: perform a strict tree-lint before commiting.
2966
 
        :type strict: bool
2967
 
        :keyword seal: create a ``version-0`` revision.
2968
 
        :type seal: bool
2969
 
        :keyword fix: create a ``versionfix`` revision.
2970
 
        :type fix: bool
2971
 
        :keyword out_of_date_ok: commit even if the tree is out of
2972
 
            date.
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
2978
 
 
2979
 
        The --summary and --log-message options to tla are mere CLI
2980
 
        convenience features and are not directly supported.
2981
 
 
2982
 
        :see: `WorkingTree.commit`
2983
 
        """
2984
 
        if version is None:
2985
 
            version = self.tree_version.fullname
2986
 
        else:
2987
 
            version = _version_param(version)
2988
 
        if isinstance(log, LogMessage):
2989
 
            log.save()
2990
 
            log = log.name
2991
 
        assert log is None or isinstance(log, str)
2992
 
        if log is not None:
2993
 
            log = os.path.abspath(log)
2994
 
        iterator = _arch.iter_commit(
2995
 
            self, version, log, strict, seal, fix, out_of_date_ok, file_list,
2996
 
            base)
2997
 
        return classify_changeset_creation(iterator)
2998
 
 
2999
 
    def log_message(self, create=True, version=None):
3000
 
        """Default log-message object used by import and commit.
3001
 
 
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
3004
 
        needed.
3005
 
        """
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)
3013
 
 
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)
3018
 
 
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)
3023
 
 
3024
 
    def replay(self):
3025
 
        """Replay changesets into this working tree."""
3026
 
        _arch.replay(self)
3027
 
 
3028
 
    def update(self):
3029
 
        """Apply delta of new revisions in the archive.
3030
 
 
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
3034
 
        archive.
3035
 
        """
3036
 
        _arch.update(self)
3037
 
 
3038
 
    def iter_pristines(self):
3039
 
        """Pristines present in that source tree.
3040
 
 
3041
 
        :return: Revisions which have pristine trees in that source tree.
3042
 
        :rtype: iterable of `Revision`
3043
 
        """
3044
 
        for rvsn in _arch.iter_pristines(self):
3045
 
            yield Revision(rvsn)
3046
 
 
3047
 
    def add_pristine(self, revision):
3048
 
        """Ensure that the project tree has a particular pristine revision.
3049
 
 
3050
 
        :param revision: revision to add a pristine for.
3051
 
        :type revision: `Revision`
3052
 
        """
3053
 
        revision = _revision_param(revision)
3054
 
        _arch.add_pristine(self, revision)
3055
 
 
3056
 
    def find_pristine(self, revision):
3057
 
        """Path to a pristine tree for the given revision.
3058
 
 
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.
3064
 
        """
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))
3069
 
 
3070
 
 
3071
 
### Changesets ###
3072
 
 
3073
 
public('Changeset')
3074
 
 
3075
 
class Changeset(DirName):
3076
 
 
3077
 
    """Arch whole-tree changeset."""
3078
 
 
3079
 
    def __init__(self, name):
3080
 
        DirName.__init__(self, name)
3081
 
        self.__index = {}
3082
 
        self.__index_excl = {}
3083
 
        self.__metadata = {}
3084
 
        self.exclude_re = re.compile('^[AE]_')
3085
 
 
3086
 
    def get_index(self, name, all=False):
3087
 
        """Load and parse an index file from the changeset.
3088
 
 
3089
 
        Expectable indexes are:
3090
 
        mod-dirs mod-files orig-dirs orig-files (more?)
3091
 
        """
3092
 
        key = (name, all)
3093
 
        if name in self.__index: return self.__index[key]
3094
 
        if all:
3095
 
            not_exclude = lambda(id_): True
3096
 
        else:
3097
 
            not_exclude = lambda(id_): not self.exclude_re.match(id_)
3098
 
        retval = {}
3099
 
        fullname = self/(name + '-index')
3100
 
        if os.path.exists(fullname):
3101
 
            index_file = open(fullname)
3102
 
            try:
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))
3107
 
            finally:
3108
 
                index_file.close()
3109
 
        self.__index[key] = retval
3110
 
        return retval
3111
 
 
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
3119
 
        return True
3120
 
    does_nothing = property(_get_does_nothing)
3121
 
 
3122
 
#     def get_metadata(self, name):
3123
 
#         """Load and parse a metadata file from the changeset.
3124
 
#
3125
 
#         Expectable metadata tables are:
3126
 
#         modified-only-dir original-only-dir (more?)
3127
 
#         """
3128
 
#         raise NotImplementedError, "not yet implemented"
3129
 
#         if name in self.__metadata: return self.__metadata[name]
3130
 
#         retval = []
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
3136
 
#         return retval
3137
 
 
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_])
3146
 
 
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)
3151
 
 
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'):
3157
 
                yield f
3158
 
            else:
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())
3162
 
 
3163
 
    def patch_file(self, modname):
3164
 
        return self/'patches'/modname+'.patch'
3165
 
 
3166
 
    def __iter_renames_helper(self, what):
3167
 
        for id_, orig, mod in self.__iter_mod_helper(what, all=False):
3168
 
            if orig != mod:
3169
 
                yield (id_, orig, mod)
3170
 
 
3171
 
    def iter_renames(self):
3172
 
        """Iterate over (id, orig, dest) triples representing renames.
3173
 
 
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.
3177
 
        """
3178
 
        from itertools import chain
3179
 
        return chain(*map(self.__iter_renames_helper, ('files', 'dirs')))
3180
 
 
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_])
3190
 
 
3191
 
    def iter_created_files(self, all=False):
3192
 
        """Iterator over tuples (id, dest) for created files.
3193
 
 
3194
 
        :param all: include Arch control files.
3195
 
        :type all: bool
3196
 
        """
3197
 
        return self.__iter_created_helper('files', all=all)
3198
 
 
3199
 
    def created_file(self, name):
3200
 
        return self/'new-files-archive'/name
3201
 
 
3202
 
    def iter_removed_files(self, all=False):
3203
 
        """Iterator over tuples (id, orig) for removed files.
3204
 
 
3205
 
        :param all: include Arch control files.
3206
 
        :type all: bool
3207
 
        """
3208
 
        return self.__iter_created_helper('files', removed=True, all=all)
3209
 
 
3210
 
    def removed_file(self, name):
3211
 
        return self/'removed-files-archive'/name
3212
 
 
3213
 
    def iter_created_dirs(self, all=False):
3214
 
        """Iterator over tuples (id, dest) for created directories.
3215
 
 
3216
 
        :param all: include Arch control files.
3217
 
        :type all: bool
3218
 
        """
3219
 
        return self.__iter_created_helper('dirs', all=all)
3220
 
 
3221
 
    def iter_removed_dirs(self, all=False):
3222
 
        """Iterator over tuples (id, orig) for removed directories.
3223
 
 
3224
 
        :param all: include Arch control files.
3225
 
        :type all: bool
3226
 
        """
3227
 
        return self.__iter_created_helper('dirs', removed=True, all=all)
3228
 
 
3229
 
    def iter_apply(self, tree, reverse=False):
3230
 
        """Apply this changeset to a tree, with incremental output.
3231
 
 
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.
3236
 
        :type reverse: bool
3237
 
        :rtype: `ChangesetApplication`
3238
 
        """
3239
 
        _check_working_tree_param(tree, 'tree')
3240
 
        return ChangesetApplication(
3241
 
            _arch.iter_apply_changeset(self, tree, reverse))
3242
 
 
3243
 
 
3244
 
public('changeset')
3245
 
 
3246
 
def changeset(orig, mod, dest):
3247
 
    """Deprecated.
3248
 
 
3249
 
    :see: `delta`
3250
 
    :rtype: `Changeset`
3251
 
    """
3252
 
    deprecated_callable(changeset, delta)
3253
 
    return delta(ArchSourceTree(orig), ArchSourceTree(mod), dest)
3254
 
 
3255
 
 
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):
3263
 
        return param
3264
 
    else:
3265
 
        raise TypeError("Parameter \"%s\" must be ArchSourceTree or Revision"
3266
 
                        " but was: %r" % (name, param))
3267
 
 
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))
3273
 
 
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))
3279
 
 
3280
 
 
3281
 
public('ChangesetApplication', 'ChangesetCreation', 'delta', 'iter_delta')
3282
 
 
3283
 
class ChangesetApplication(object):
3284
 
    """Incremental changeset application process."""
3285
 
 
3286
 
    def __init__(self, proc):
3287
 
        """For internal use only."""
3288
 
        self._iter_process = proc
3289
 
 
3290
 
    def __iter__(self):
3291
 
        """Return an iterator of `MergeOutcome`"""
3292
 
        return classify_changeset_application(self._iter_process)
3293
 
 
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)
3300
 
 
3301
 
    def _get_finished(self):
3302
 
        "Is the changeset application complete?"
3303
 
        return self._iter_process.finished
3304
 
    finished = property(_get_finished)
3305
 
 
3306
 
class ChangesetCreation(object):
3307
 
    """Incremental changeset generation process."""
3308
 
 
3309
 
    def __init__(self, proc, dest):
3310
 
        """For internal use only."""
3311
 
        self._iter_process = proc
3312
 
        self._dest = dest
3313
 
        self._changeset = None
3314
 
 
3315
 
    def __iter__(self):
3316
 
        """Return an iterator of `TreeChange`"""
3317
 
        return classify_changeset_creation(self._iter_process)
3318
 
 
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)
3327
 
 
3328
 
    def _get_finished(self):
3329
 
        "Is the changeset creation complete?"
3330
 
        return self._iter_process.finished
3331
 
    finished = property(_get_finished)
3332
 
 
3333
 
 
3334
 
def iter_delta(orig, mod, dest):
3335
 
    """Compute a whole-tree changeset with incremental output.
3336
 
 
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.
3342
 
    :type dest: str
3343
 
    :rtype: `ChangesetCreation`
3344
 
    """
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)
3350
 
 
3351
 
 
3352
 
def delta(orig, mod, dest):
3353
 
    """Compute a whole-tree changeset.
3354
 
 
3355
 
    Create the output directory ``dest`` (it must not already exist).
3356
 
 
3357
 
    Compare the source trees ``orig`` and ``mod`` (which may be source
3358
 
    arch source tree or revisions). Create a changeset in ``dest``.
3359
 
 
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.
3365
 
    :type dest: str
3366
 
    :return: changeset from ``orig`` to ``mod``.
3367
 
    :rtype: `Changeset`
3368
 
    """
3369
 
    __pychecker__ = 'unusednames=line'
3370
 
    iter_ = iter_delta(orig, mod, dest)
3371
 
    for line in iter_: pass
3372
 
    return iter_.changeset
3373
 
 
3374
 
 
3375
 
### Top level archive functions ###
3376
 
 
3377
 
public('my_id', 'set_my_id', 'make_archive', 'register_archive')
3378
 
 
3379
 
def my_id():
3380
 
    """The current registered user id
3381
 
 
3382
 
    :return: the user id, for example 'John Doe <jdoe@example.org>'.
3383
 
    :rtype: str
3384
 
    """
3385
 
    return _arch.my_id()
3386
 
 
3387
 
 
3388
 
def set_my_id(new_id):
3389
 
    """Set the current registered user id
3390
 
 
3391
 
    :param new_id: new value of the user id.
3392
 
    :type new_id: str
3393
 
    """
3394
 
    return _arch.set_my_id(new_id)
3395
 
 
3396
 
 
3397
 
def make_archive(name, location, signed=False, listing=False):
3398
 
    """Create and register new commitable archive.
3399
 
 
3400
 
    :param name: archive name (e.g. "david@allouche.net--2003b").
3401
 
    :type name: `Archive` or str
3402
 
    :param location: URL of the archive
3403
 
    :type location: str
3404
 
    :param signed: create GPG signatures for the archive contents.
3405
 
    :type signed: bool
3406
 
    :param listing: maintains ''.listing'' files to enable HTTP access.
3407
 
    :type listing: bool
3408
 
 
3409
 
    :return: an `Archive` instance for the given name.
3410
 
    :rtype: `Archive`
3411
 
 
3412
 
    :raise errors.NamespaceError: ``name`` is not a valid archive name.
3413
 
    """
3414
 
    name = _archive_param(name)
3415
 
    _arch.make_archive(name, location, signed, listing)
3416
 
    return Archive(_unsafe((name,)))
3417
 
 
3418
 
def register_archive(name, location):
3419
 
    """Record the location of an archive.
3420
 
 
3421
 
    :param name: archive name, or None to use the official name stored in the
3422
 
        archive.
3423
 
    :type name: str, None
3424
 
    :param location: URL of the archive.
3425
 
    :type location: str
3426
 
    :return: newly registered archive.
3427
 
    :rtype: `Archive`.
3428
 
    """
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,)))
3434
 
    else:
3435
 
        for n in _arch.archives():
3436
 
            a = Archive(_unsafe((n,)))
3437
 
            if a.location == location:
3438
 
                return a
3439
 
        raise AssertionError, 'not reached'
3440
 
 
3441
 
 
3442
 
public('iter_archives', 'archives')
3443
 
 
3444
 
def iter_archives():
3445
 
    """Iterate over registered archives.
3446
 
 
3447
 
    :return: all registered archives.
3448
 
    :rtype: iterable of `Archive`
3449
 
    """
3450
 
    for n in _arch.archives():
3451
 
        yield Archive(n)
3452
 
 
3453
 
 
3454
 
def archives():
3455
 
    """Deprecated.
3456
 
 
3457
 
    List of registered archives.
3458
 
 
3459
 
    :rtype: sequence of `Archive`
3460
 
    :see: `iter_archives`
3461
 
    """
3462
 
    deprecated_callable(archives, iter_archives)
3463
 
    return list(iter_archives())
3464
 
 
3465
 
 
3466
 
public('iter_library_archives', 'library_archives')
3467
 
 
3468
 
def iter_library_archives():
3469
 
    """Iterate over archives present in the revision library.
3470
 
 
3471
 
    :returns: all archives which are present in the revision library.
3472
 
    :rtype: iterable of `Archive`
3473
 
    """
3474
 
    for n in _arch.library_archives():
3475
 
        yield Archive(n)
3476
 
 
3477
 
 
3478
 
def library_archives():
3479
 
    """Deprecated.
3480
 
 
3481
 
    List of archives present in the revision library.
3482
 
 
3483
 
    :rtype: sequence of `Archive`
3484
 
    :see: `iter_library_archives`
3485
 
    """
3486
 
    deprecated_callable(library_archives, iter_library_archives)
3487
 
    return list(iter_library_archives())
3488
 
 
3489
 
 
3490
 
public('default_archive')
3491
 
 
3492
 
def default_archive():
3493
 
    """Default Archive object or None.
3494
 
 
3495
 
    :return: the default archive, or None.
3496
 
    :rtype: `Archive`, None
3497
 
    """
3498
 
    name = _arch.default_archive()
3499
 
    if name is None:
3500
 
        return None
3501
 
    else:
3502
 
        return Archive(name)
3503
 
 
3504
 
 
3505
 
public('make_continuation')
3506
 
 
3507
 
def make_continuation(source_revision, tag_version):
3508
 
    """Deprecated.
3509
 
 
3510
 
    :see: `Revision.make_continuation`
3511
 
    """
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)
3517
 
 
3518
 
public('get', 'get_patch')
3519
 
 
3520
 
def get(revision, dir, link=False):
3521
 
    """Deprecated.
3522
 
 
3523
 
    Construct a project tree for a revision.
3524
 
 
3525
 
    :rtype: `WorkingTree`
3526
 
    :see: `Revision.get`
3527
 
    """
3528
 
    deprecated_callable(get, Revision.get)
3529
 
    revision = _package_revision_param(revision)
3530
 
    _arch.get(revision, dir, link)
3531
 
    return WorkingTree(dir)
3532
 
 
3533
 
 
3534
 
def get_patch(revision, dir):
3535
 
    """Deprecated.
3536
 
 
3537
 
    :rtype: `Changeset`
3538
 
    :see: `Revision.get_patch`
3539
 
    """
3540
 
    deprecated_callable(get_patch, Revision.get_patch)
3541
 
    revision = _revision_param(revision)
3542
 
    _arch.get_patch(revision, dir)
3543
 
    return Changeset(dir)
3544
 
 
3545
 
 
3546
 
public(
3547
 
    'iter_revision_libraries',
3548
 
    'register_revision_library',
3549
 
    'unregister_revision_library',
3550
 
    )
3551
 
 
3552
 
def iter_revision_libraries():
3553
 
    """Iterate over registered revision library directories.
3554
 
 
3555
 
    :return: directory names of all registered revision libraries.
3556
 
    :rtype: iterable of str
3557
 
    """
3558
 
    return _arch.iter_revision_libraries()
3559
 
 
3560
 
def register_revision_library(dirname):
3561
 
    """Register an existing revision library directory.
3562
 
 
3563
 
    :param dirname: absolute path name of existing user-writable directory.
3564
 
    :type dirname: str
3565
 
    :todo: create_revision_library which abstracts out revlib construction.
3566
 
    :postcondition: ``dirname`` is present in `iter_revision_libraries` output.
3567
 
    """
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)
3573
 
 
3574
 
def unregister_revision_library(dirname):
3575
 
    """Unregister a revision library directory.
3576
 
 
3577
 
    :param dirname: registered revision library directory.
3578
 
    :type dirname: str
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`.
3582
 
    """
3583
 
    if not os.path.isabs(dirname):
3584
 
        raise ValueError, "not an absolute path: %r" % dirname
3585
 
    _arch.unregister_revision_library(dirname)
3586
 
 
3587
 
 
3588
 
### Parsing Arch names ###
3589
 
 
3590
 
public('NameParser')
3591
 
 
3592
 
class NameParser(_arch.NameParser):
3593
 
 
3594
 
    """Parser for names in Arch archive namespace.
3595
 
 
3596
 
    :see: `backends.tla.NameParser`
3597
 
    """
3598
 
 
3599
 
    def object(self):
3600
 
        """Create the Category, Branch, Version or Revision object
3601
 
 
3602
 
        Create the namespace object corresponding to the name. This
3603
 
        requires some guessing so, for example, nameless branches will
3604
 
        not be recognized.
3605
 
 
3606
 
        :warning: DEPRECATED: unsafe and not really useful.
3607
 
        """
3608
 
        a = self.get_archive()
3609
 
        if not a: return None
3610
 
        a = Archive(a)
3611
 
        c = self.get_category()
3612
 
        if not c: return a
3613
 
        c = a[c]
3614
 
        b = self.get_branch()
3615
 
        if not b: return c
3616
 
        b = c[b]
3617
 
        v = self.get_version()
3618
 
        if not v: return b
3619
 
        v = b[v]
3620
 
        r = self.get_patchlevel()
3621
 
        if not r: return v
3622
 
        return v[r]
3623
 
 
3624
 
 
3625
 
### Searching in Archives ###
3626
 
 
3627
 
public(
3628
 
    'filter_archive_logs',
3629
 
    'filter_revisions',
3630
 
    'grep_summary',
3631
 
    'grep_summary_interactive',
3632
 
    'suspected_move',
3633
 
    'revisions_merging',
3634
 
    'temphack',
3635
 
    'revision_which_created',
3636
 
    'last_revision',
3637
 
    'map_name_id',
3638
 
    )
3639
 
 
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
3645
 
 
3646
 
 
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():
3651
 
        if pred(r): yield r
3652
 
 
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)
3659
 
 
3660
 
 
3661
 
def grep_summary_interactive(limit):
3662
 
    deprecated_callable(grep_summary_interactive,
3663
 
                        because='It does not belong here.')
3664
 
    while True:
3665
 
        try:
3666
 
            rx = raw_input('search> ')
3667
 
        except KeyboardInterrupt:
3668
 
            break
3669
 
        if not rx: break
3670
 
        for r in grep_summary(limit, rx):
3671
 
            p = r.patchlog
3672
 
            print 'Revision:', p['Revision']
3673
 
            print 'Summary: ', p['Summary']
3674
 
 
3675
 
 
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)
3681
 
 
3682
 
 
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)
3688
 
 
3689
 
 
3690
 
def temphack(revision):
3691
 
    deprecated_callable(temphack,
3692
 
                        because='It does not belong here.')
3693
 
    import sets
3694
 
    retval = sets.Set()
3695
 
    for ancstr in revision.iter_ancestors(metoo=True):
3696
 
        for k in ancstr.patchlog.keys():
3697
 
            retval.add(k)
3698
 
    return retval
3699
 
 
3700
 
 
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:
3706
 
            return ancstr
3707
 
    return None
3708
 
 
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
3713
 
 
3714
 
 
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)
3720
 
    retval = {}
3721
 
    for id_, name in tree.iter_inventory_ids(source=True, files=True):
3722
 
        retval[name] = id_
3723
 
    return retval
3724