~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Martin Pool
  • Date: 2010-06-02 05:03:31 UTC
  • mto: This revision was merged to the branch mainline in revision 5279.
  • Revision ID: mbp@canonical.com-20100602050331-n2p1qt8hfsahspnv
Correct more sloppy use of the term 'Linux'

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
23
23
# But those depend on its position within a particular inventory, and
24
24
# it would be nice not to need to hold the backpointer here.
25
25
 
26
 
from __future__ import absolute_import
27
 
 
28
26
# This should really be an id randomly assigned when the tree is
29
27
# created, but it's not for now.
30
28
ROOT_ID = "TREE_ROOT"
33
31
lazy_import(globals(), """
34
32
import collections
35
33
import copy
 
34
import os
36
35
import re
37
36
import tarfile
38
37
 
 
38
import bzrlib
39
39
from bzrlib import (
40
40
    chk_map,
41
41
    errors,
42
42
    generate_ids,
43
43
    osutils,
 
44
    symbol_versioning,
44
45
    )
45
46
""")
46
47
 
47
 
from bzrlib import (
48
 
    lazy_regex,
49
 
    trace,
 
48
from bzrlib.errors import (
 
49
    BzrCheckError,
 
50
    BzrError,
50
51
    )
51
 
 
 
52
from bzrlib.symbol_versioning import deprecated_in, deprecated_method
 
53
from bzrlib.trace import mutter
52
54
from bzrlib.static_tuple import StaticTuple
53
 
from bzrlib.symbol_versioning import (
54
 
    deprecated_in,
55
 
    deprecated_method,
56
 
    )
57
55
 
58
56
 
59
57
class InventoryEntry(object):
106
104
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
107
105
    >>> i.path2id('src/wibble')
108
106
    '2325'
 
107
    >>> '2325' in i
 
108
    True
109
109
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
110
110
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
111
111
    >>> i['2326']
131
131
    RENAMED = 'renamed'
132
132
    MODIFIED_AND_RENAMED = 'modified and renamed'
133
133
 
134
 
    __slots__ = ['file_id', 'revision', 'parent_id', 'name']
135
 
 
136
 
    # Attributes that all InventoryEntry instances are expected to have, but
137
 
    # that don't vary for all kinds of entry.  (e.g. symlink_target is only
138
 
    # relevant to InventoryLink, so there's no reason to make every
139
 
    # InventoryFile instance allocate space to hold a value for it.)
140
 
    # Attributes that only vary for files: executable, text_sha1, text_size,
141
 
    # text_id
142
 
    executable = False
143
 
    text_sha1 = None
144
 
    text_size = None
145
 
    text_id = None
146
 
    # Attributes that only vary for symlinks: symlink_target
147
 
    symlink_target = None
148
 
    # Attributes that only vary for tree-references: reference_revision
149
 
    reference_revision = None
150
 
 
 
134
    __slots__ = []
151
135
 
152
136
    def detect_changes(self, old_entry):
153
137
        """Return a (text_modified, meta_modified) from this to old_entry.
174
158
        candidates = {}
175
159
        # identify candidate head revision ids.
176
160
        for inv in previous_inventories:
177
 
            if inv.has_id(self.file_id):
 
161
            if self.file_id in inv:
178
162
                ie = inv[self.file_id]
179
163
                if ie.revision in candidates:
180
164
                    # same revision value in two different inventories:
192
176
                    candidates[ie.revision] = ie
193
177
        return candidates
194
178
 
 
179
    @deprecated_method(deprecated_in((1, 6, 0)))
 
180
    def get_tar_item(self, root, dp, now, tree):
 
181
        """Get a tarfile item and a file stream for its content."""
 
182
        item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
 
183
        # TODO: would be cool to actually set it to the timestamp of the
 
184
        # revision it was last changed
 
185
        item.mtime = now
 
186
        fileobj = self._put_in_tar(item, tree)
 
187
        return item, fileobj
 
188
 
195
189
    def has_text(self):
196
190
        """Return true if the object this entry represents has textual data.
197
191
 
203
197
        """
204
198
        return False
205
199
 
206
 
    def __init__(self, file_id, name, parent_id):
 
200
    def __init__(self, file_id, name, parent_id, text_id=None):
207
201
        """Create an InventoryEntry
208
202
 
209
203
        The filename must be a single component, relative to the
220
214
        """
221
215
        if '/' in name or '\\' in name:
222
216
            raise errors.InvalidEntryName(name=name)
 
217
        self.executable = False
 
218
        self.revision = None
 
219
        self.text_sha1 = None
 
220
        self.text_size = None
223
221
        self.file_id = file_id
224
 
        self.revision = None
225
222
        self.name = name
 
223
        self.text_id = text_id
226
224
        self.parent_id = parent_id
 
225
        self.symlink_target = None
 
226
        self.reference_revision = None
227
227
 
228
228
    def kind_character(self):
229
229
        """Return a short kind indicator useful for appending to names."""
230
 
        raise errors.BzrError('unknown kind %r' % self.kind)
 
230
        raise BzrError('unknown kind %r' % self.kind)
231
231
 
232
232
    known_kinds = ('file', 'directory', 'symlink')
233
233
 
 
234
    def _put_in_tar(self, item, tree):
 
235
        """populate item for stashing in a tar, and return the content stream.
 
236
 
 
237
        If no content is available, return None.
 
238
        """
 
239
        raise BzrError("don't know how to export {%s} of kind %r" %
 
240
                       (self.file_id, self.kind))
 
241
 
 
242
    @deprecated_method(deprecated_in((1, 6, 0)))
 
243
    def put_on_disk(self, dest, dp, tree):
 
244
        """Create a representation of self on disk in the prefix dest.
 
245
 
 
246
        This is a template method - implement _put_on_disk in subclasses.
 
247
        """
 
248
        fullpath = osutils.pathjoin(dest, dp)
 
249
        self._put_on_disk(fullpath, tree)
 
250
        # mutter("  export {%s} kind %s to %s", self.file_id,
 
251
        #         self.kind, fullpath)
 
252
 
 
253
    def _put_on_disk(self, fullpath, tree):
 
254
        """Put this entry onto disk at fullpath, from tree tree."""
 
255
        raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
 
256
 
234
257
    def sorted_children(self):
235
258
        return sorted(self.children.items())
236
259
 
253
276
        """
254
277
        if self.parent_id is not None:
255
278
            if not inv.has_id(self.parent_id):
256
 
                raise errors.BzrCheckError(
257
 
                    'missing parent {%s} in inventory for revision {%s}' % (
258
 
                        self.parent_id, rev_id))
 
279
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
 
280
                        % (self.parent_id, rev_id))
259
281
        checker._add_entry_to_text_key_references(inv, self)
260
282
        self._check(checker, rev_id)
261
283
 
375
397
        pass
376
398
 
377
399
 
 
400
class RootEntry(InventoryEntry):
 
401
 
 
402
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
403
                 'text_id', 'parent_id', 'children', 'executable',
 
404
                 'revision', 'symlink_target', 'reference_revision']
 
405
 
 
406
    def _check(self, checker, rev_id):
 
407
        """See InventoryEntry._check"""
 
408
 
 
409
    def __init__(self, file_id):
 
410
        self.file_id = file_id
 
411
        self.children = {}
 
412
        self.kind = 'directory'
 
413
        self.parent_id = None
 
414
        self.name = u''
 
415
        self.revision = None
 
416
        symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
 
417
                               '  Please use InventoryDirectory instead.',
 
418
                               DeprecationWarning, stacklevel=2)
 
419
 
 
420
    def __eq__(self, other):
 
421
        if not isinstance(other, RootEntry):
 
422
            return NotImplemented
 
423
 
 
424
        return (self.file_id == other.file_id) \
 
425
               and (self.children == other.children)
 
426
 
 
427
 
378
428
class InventoryDirectory(InventoryEntry):
379
429
    """A directory in an inventory."""
380
430
 
381
 
    __slots__ = ['children']
382
 
 
383
 
    kind = 'directory'
 
431
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
432
                 'text_id', 'parent_id', 'children', 'executable',
 
433
                 'revision', 'symlink_target', 'reference_revision']
384
434
 
385
435
    def _check(self, checker, rev_id):
386
436
        """See InventoryEntry._check"""
 
437
        if (self.text_sha1 is not None or self.text_size is not None or
 
438
            self.text_id is not None):
 
439
            checker._report_items.append('directory {%s} has text in revision {%s}'
 
440
                                % (self.file_id, rev_id))
387
441
        # In non rich root repositories we do not expect a file graph for the
388
442
        # root.
389
443
        if self.name == '' and not checker.rich_roots:
405
459
    def __init__(self, file_id, name, parent_id):
406
460
        super(InventoryDirectory, self).__init__(file_id, name, parent_id)
407
461
        self.children = {}
 
462
        self.kind = 'directory'
408
463
 
409
464
    def kind_character(self):
410
465
        """See InventoryEntry.kind_character."""
411
466
        return '/'
412
467
 
 
468
    def _put_in_tar(self, item, tree):
 
469
        """See InventoryEntry._put_in_tar."""
 
470
        item.type = tarfile.DIRTYPE
 
471
        fileobj = None
 
472
        item.name += '/'
 
473
        item.size = 0
 
474
        item.mode = 0755
 
475
        return fileobj
 
476
 
 
477
    def _put_on_disk(self, fullpath, tree):
 
478
        """See InventoryEntry._put_on_disk."""
 
479
        os.mkdir(fullpath)
 
480
 
413
481
 
414
482
class InventoryFile(InventoryEntry):
415
483
    """A file in an inventory."""
416
484
 
417
 
    __slots__ = ['text_sha1', 'text_size', 'text_id', 'executable']
418
 
 
419
 
    kind = 'file'
420
 
 
421
 
    def __init__(self, file_id, name, parent_id):
422
 
        super(InventoryFile, self).__init__(file_id, name, parent_id)
423
 
        self.text_sha1 = None
424
 
        self.text_size = None
425
 
        self.text_id = None
426
 
        self.executable = False
 
485
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
486
                 'text_id', 'parent_id', 'children', 'executable',
 
487
                 'revision', 'symlink_target', 'reference_revision']
427
488
 
428
489
    def _check(self, checker, tree_revision_id):
429
490
        """See InventoryEntry._check"""
472
533
        """See InventoryEntry.has_text."""
473
534
        return True
474
535
 
 
536
    def __init__(self, file_id, name, parent_id):
 
537
        super(InventoryFile, self).__init__(file_id, name, parent_id)
 
538
        self.kind = 'file'
 
539
 
475
540
    def kind_character(self):
476
541
        """See InventoryEntry.kind_character."""
477
542
        return ''
478
543
 
 
544
    def _put_in_tar(self, item, tree):
 
545
        """See InventoryEntry._put_in_tar."""
 
546
        item.type = tarfile.REGTYPE
 
547
        fileobj = tree.get_file(self.file_id)
 
548
        item.size = self.text_size
 
549
        if tree.is_executable(self.file_id):
 
550
            item.mode = 0755
 
551
        else:
 
552
            item.mode = 0644
 
553
        return fileobj
 
554
 
 
555
    def _put_on_disk(self, fullpath, tree):
 
556
        """See InventoryEntry._put_on_disk."""
 
557
        osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
 
558
        if tree.is_executable(self.file_id):
 
559
            os.chmod(fullpath, 0755)
 
560
 
479
561
    def _read_tree_state(self, path, work_tree):
480
562
        """See InventoryEntry._read_tree_state."""
481
563
        self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
513
595
class InventoryLink(InventoryEntry):
514
596
    """A file in an inventory."""
515
597
 
516
 
    __slots__ = ['symlink_target']
517
 
 
518
 
    kind = 'symlink'
519
 
 
520
 
    def __init__(self, file_id, name, parent_id):
521
 
        super(InventoryLink, self).__init__(file_id, name, parent_id)
522
 
        self.symlink_target = None
 
598
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
599
                 'text_id', 'parent_id', 'children', 'executable',
 
600
                 'revision', 'symlink_target', 'reference_revision']
523
601
 
524
602
    def _check(self, checker, tree_revision_id):
525
603
        """See InventoryEntry._check"""
 
604
        if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
 
605
            checker._report_items.append(
 
606
               'symlink {%s} has text in revision {%s}'
 
607
                    % (self.file_id, tree_revision_id))
526
608
        if self.symlink_target is None:
527
609
            checker._report_items.append(
528
610
                'symlink {%s} has no target in revision {%s}'
543
625
        # FIXME: which _modified field should we use ? RBC 20051003
544
626
        text_modified = (self.symlink_target != old_entry.symlink_target)
545
627
        if text_modified:
546
 
            trace.mutter("    symlink target changed")
 
628
            mutter("    symlink target changed")
547
629
        meta_modified = False
548
630
        return text_modified, meta_modified
549
631
 
566
648
        differ = DiffSymlink(old_tree, new_tree, output_to)
567
649
        return differ.diff_symlink(old_target, new_target)
568
650
 
 
651
    def __init__(self, file_id, name, parent_id):
 
652
        super(InventoryLink, self).__init__(file_id, name, parent_id)
 
653
        self.kind = 'symlink'
 
654
 
569
655
    def kind_character(self):
570
656
        """See InventoryEntry.kind_character."""
571
657
        return ''
572
658
 
 
659
    def _put_in_tar(self, item, tree):
 
660
        """See InventoryEntry._put_in_tar."""
 
661
        item.type = tarfile.SYMTYPE
 
662
        fileobj = None
 
663
        item.size = 0
 
664
        item.mode = 0755
 
665
        item.linkname = self.symlink_target
 
666
        return fileobj
 
667
 
 
668
    def _put_on_disk(self, fullpath, tree):
 
669
        """See InventoryEntry._put_on_disk."""
 
670
        try:
 
671
            os.symlink(self.symlink_target, fullpath)
 
672
        except OSError,e:
 
673
            raise BzrError("Failed to create symlink %r -> %r, error: %s" % (fullpath, self.symlink_target, e))
 
674
 
573
675
    def _read_tree_state(self, path, work_tree):
574
676
        """See InventoryEntry._read_tree_state."""
575
677
        self.symlink_target = work_tree.get_symlink_target(self.file_id)
587
689
 
588
690
class TreeReference(InventoryEntry):
589
691
 
590
 
    __slots__ = ['reference_revision']
591
 
 
592
692
    kind = 'tree-reference'
593
693
 
594
694
    def __init__(self, file_id, name, parent_id, revision=None,
633
733
    inserted, other than through the Inventory API.
634
734
    """
635
735
 
636
 
    @deprecated_method(deprecated_in((2, 4, 0)))
637
736
    def __contains__(self, file_id):
638
737
        """True if this entry contains a file with given id.
639
738
 
640
739
        >>> inv = Inventory()
641
740
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
642
741
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
643
 
        >>> inv.has_id('123')
 
742
        >>> '123' in inv
644
743
        True
645
 
        >>> inv.has_id('456')
 
744
        >>> '456' in inv
646
745
        False
647
746
 
648
747
        Note that this method along with __iter__ are not encouraged for use as
723
822
                # if we finished all children, pop it off the stack
724
823
                stack.pop()
725
824
 
726
 
    def _preload_cache(self):
727
 
        """Populate any caches, we are about to access all items.
728
 
        
729
 
        The default implementation does nothing, because CommonInventory doesn't
730
 
        have a cache.
731
 
        """
732
 
        pass
733
 
    
734
825
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
735
826
        yield_parents=False):
736
827
        """Iterate over the entries in a directory first order.
749
840
            specific_file_ids = set(specific_file_ids)
750
841
        # TODO? Perhaps this should return the from_dir so that the root is
751
842
        # yielded? or maybe an option?
752
 
        if from_dir is None and specific_file_ids is None:
753
 
            # They are iterating from the root, and have not specified any
754
 
            # specific entries to look at. All current callers fully consume the
755
 
            # iterator, so we can safely assume we are accessing all entries
756
 
            self._preload_cache()
757
843
        if from_dir is None:
758
844
            if self.root is None:
759
845
                return
761
847
            if (not yield_parents and specific_file_ids is not None and
762
848
                len(specific_file_ids) == 1):
763
849
                file_id = list(specific_file_ids)[0]
764
 
                if self.has_id(file_id):
 
850
                if file_id in self:
765
851
                    yield self.id2path(file_id), self[file_id]
766
852
                return
767
853
            from_dir = self.root
777
863
            parents = set()
778
864
            byid = self
779
865
            def add_ancestors(file_id):
780
 
                if not byid.has_id(file_id):
 
866
                if file_id not in byid:
781
867
                    return
782
868
                parent_id = byid[file_id].parent_id
783
869
                if parent_id is None:
827
913
                    file_id, self[file_id]))
828
914
        return delta
829
915
 
 
916
    def _get_mutable_inventory(self):
 
917
        """Returns a mutable copy of the object.
 
918
 
 
919
        Some inventories are immutable, yet working trees, for example, needs
 
920
        to mutate exisiting inventories instead of creating a new one.
 
921
        """
 
922
        raise NotImplementedError(self._get_mutable_inventory)
 
923
 
830
924
    def make_entry(self, kind, name, parent_id, file_id=None):
831
925
        """Simple thunk to bzrlib.inventory.make_entry."""
832
926
        return make_entry(kind, name, parent_id, file_id)
846
940
                if ie.kind == 'directory':
847
941
                    descend(ie, child_path)
848
942
 
849
 
        if self.root is not None:
850
 
            descend(self.root, u'')
 
943
        descend(self.root, u'')
851
944
        return accum
852
945
 
853
946
    def directories(self):
967
1060
 
968
1061
    >>> inv.path2id('hello.c')
969
1062
    '123-123'
970
 
    >>> inv.has_id('123-123')
 
1063
    >>> '123-123' in inv
971
1064
    True
972
1065
 
973
1066
    There are iterators over the contents:
1130
1223
            other.add(entry.copy())
1131
1224
        return other
1132
1225
 
 
1226
    def _get_mutable_inventory(self):
 
1227
        """See CommonInventory._get_mutable_inventory."""
 
1228
        return copy.deepcopy(self)
 
1229
 
1133
1230
    def __iter__(self):
1134
1231
        """Iterate over all file-ids."""
1135
1232
        return iter(self._byid)
1175
1272
    def _add_child(self, entry):
1176
1273
        """Add an entry to the inventory, without adding it to its parent"""
1177
1274
        if entry.file_id in self._byid:
1178
 
            raise errors.BzrError(
1179
 
                "inventory already contains entry with id {%s}" %
1180
 
                entry.file_id)
 
1275
            raise BzrError("inventory already contains entry with id {%s}" %
 
1276
                           entry.file_id)
1181
1277
        self._byid[entry.file_id] = entry
1182
1278
        for child in getattr(entry, 'children', {}).itervalues():
1183
1279
            self._add_child(child)
1186
1282
    def add(self, entry):
1187
1283
        """Add entry to inventory.
1188
1284
 
 
1285
        To add  a file to a branch ready to be committed, use Branch.add,
 
1286
        which calls this.
 
1287
 
1189
1288
        :return: entry
1190
1289
        """
1191
1290
        if entry.file_id in self._byid:
1236
1335
        >>> inv = Inventory()
1237
1336
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1238
1337
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1239
 
        >>> inv.has_id('123')
 
1338
        >>> '123' in inv
1240
1339
        True
1241
1340
        >>> del inv['123']
1242
 
        >>> inv.has_id('123')
 
1341
        >>> '123' in inv
1243
1342
        False
1244
1343
        """
1245
1344
        ie = self[file_id]
1347
1446
        """
1348
1447
        new_name = ensure_normalized_name(new_name)
1349
1448
        if not is_valid_name(new_name):
1350
 
            raise errors.BzrError("not an acceptable filename: %r" % new_name)
 
1449
            raise BzrError("not an acceptable filename: %r" % new_name)
1351
1450
 
1352
1451
        new_parent = self._byid[new_parent_id]
1353
1452
        if new_name in new_parent.children:
1354
 
            raise errors.BzrError("%r already exists in %r" %
1355
 
                (new_name, self.id2path(new_parent_id)))
 
1453
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
1356
1454
 
1357
1455
        new_parent_idpath = self.get_idpath(new_parent_id)
1358
1456
        if file_id in new_parent_idpath:
1359
 
            raise errors.BzrError(
1360
 
                "cannot move directory %r into a subdirectory of itself, %r"
 
1457
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
1361
1458
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1362
1459
 
1363
1460
        file_ie = self._byid[file_id]
1399
1496
    def __init__(self, search_key_name):
1400
1497
        CommonInventory.__init__(self)
1401
1498
        self._fileid_to_entry_cache = {}
1402
 
        self._fully_cached = False
1403
1499
        self._path_to_fileid_cache = {}
1404
1500
        self._search_key_name = search_key_name
1405
1501
        self.root_id = None
1492
1588
            if entry.kind == 'directory':
1493
1589
                directories_to_expand.add(entry.file_id)
1494
1590
            interesting.add(entry.parent_id)
1495
 
            children_of_parent_id.setdefault(entry.parent_id, set()
1496
 
                                             ).add(entry.file_id)
 
1591
            children_of_parent_id.setdefault(entry.parent_id, []
 
1592
                                             ).append(entry.file_id)
1497
1593
 
1498
1594
        # Now, interesting has all of the direct parents, but not the
1499
1595
        # parents of those parents. It also may have some duplicates with
1507
1603
            next_parents = set()
1508
1604
            for entry in self._getitems(remaining_parents):
1509
1605
                next_parents.add(entry.parent_id)
1510
 
                children_of_parent_id.setdefault(entry.parent_id, set()
1511
 
                                                 ).add(entry.file_id)
 
1606
                children_of_parent_id.setdefault(entry.parent_id, []
 
1607
                                                 ).append(entry.file_id)
1512
1608
            # Remove any search tips we've already processed
1513
1609
            remaining_parents = next_parents.difference(interesting)
1514
1610
            interesting.update(remaining_parents)
1527
1623
            for entry in self._getitems(next_file_ids):
1528
1624
                if entry.kind == 'directory':
1529
1625
                    directories_to_expand.add(entry.file_id)
1530
 
                children_of_parent_id.setdefault(entry.parent_id, set()
1531
 
                                                 ).add(entry.file_id)
 
1626
                children_of_parent_id.setdefault(entry.parent_id, []
 
1627
                                                 ).append(entry.file_id)
1532
1628
        return interesting, children_of_parent_id
1533
1629
 
1534
1630
    def filter(self, specific_fileids):
1612
1708
        self._fileid_to_entry_cache[result.file_id] = result
1613
1709
        return result
1614
1710
 
 
1711
    def _get_mutable_inventory(self):
 
1712
        """See CommonInventory._get_mutable_inventory."""
 
1713
        entries = self.iter_entries()
 
1714
        inv = Inventory(None, self.revision_id)
 
1715
        for path, inv_entry in entries:
 
1716
            inv.add(inv_entry.copy())
 
1717
        return inv
 
1718
 
1615
1719
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1616
1720
        propagate_caches=False):
1617
1721
        """Create a new CHKInventory by applying inventory_delta to this one.
1958
2062
 
1959
2063
    def iter_just_entries(self):
1960
2064
        """Iterate over all entries.
1961
 
 
 
2065
        
1962
2066
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1963
2067
        and the order of entries is undefined.
1964
2068
 
1972
2076
                self._fileid_to_entry_cache[file_id] = ie
1973
2077
            yield ie
1974
2078
 
1975
 
    def _preload_cache(self):
1976
 
        """Make sure all file-ids are in _fileid_to_entry_cache"""
1977
 
        if self._fully_cached:
1978
 
            return # No need to do it again
1979
 
        # The optimal sort order is to use iteritems() directly
1980
 
        cache = self._fileid_to_entry_cache
1981
 
        for key, entry in self.id_to_entry.iteritems():
1982
 
            file_id = key[0]
1983
 
            if file_id not in cache:
1984
 
                ie = self._bytes_to_entry(entry)
1985
 
                cache[file_id] = ie
1986
 
            else:
1987
 
                ie = cache[file_id]
1988
 
        last_parent_id = last_parent_ie = None
1989
 
        pid_items = self.parent_id_basename_to_file_id.iteritems()
1990
 
        for key, child_file_id in pid_items:
1991
 
            if key == ('', ''): # This is the root
1992
 
                if child_file_id != self.root_id:
1993
 
                    raise ValueError('Data inconsistency detected.'
1994
 
                        ' We expected data with key ("","") to match'
1995
 
                        ' the root id, but %s != %s'
1996
 
                        % (child_file_id, self.root_id))
1997
 
                continue
1998
 
            parent_id, basename = key
1999
 
            ie = cache[child_file_id]
2000
 
            if parent_id == last_parent_id:
2001
 
                parent_ie = last_parent_ie
2002
 
            else:
2003
 
                parent_ie = cache[parent_id]
2004
 
            if parent_ie.kind != 'directory':
2005
 
                raise ValueError('Data inconsistency detected.'
2006
 
                    ' An entry in the parent_id_basename_to_file_id map'
2007
 
                    ' has parent_id {%s} but the kind of that object'
2008
 
                    ' is %r not "directory"' % (parent_id, parent_ie.kind))
2009
 
            if parent_ie._children is None:
2010
 
                parent_ie._children = {}
2011
 
            basename = basename.decode('utf-8')
2012
 
            if basename in parent_ie._children:
2013
 
                existing_ie = parent_ie._children[basename]
2014
 
                if existing_ie != ie:
2015
 
                    raise ValueError('Data inconsistency detected.'
2016
 
                        ' Two entries with basename %r were found'
2017
 
                        ' in the parent entry {%s}'
2018
 
                        % (basename, parent_id))
2019
 
            if basename != ie.name:
2020
 
                raise ValueError('Data inconsistency detected.'
2021
 
                    ' In the parent_id_basename_to_file_id map, file_id'
2022
 
                    ' {%s} is listed as having basename %r, but in the'
2023
 
                    ' id_to_entry map it is %r'
2024
 
                    % (child_file_id, basename, ie.name))
2025
 
            parent_ie._children[basename] = ie
2026
 
        self._fully_cached = True
2027
 
 
2028
2079
    def iter_changes(self, basis):
2029
2080
        """Generate a Tree.iter_changes change list between this and basis.
2030
2081
 
2190
2241
class CHKInventoryDirectory(InventoryDirectory):
2191
2242
    """A directory in an inventory."""
2192
2243
 
2193
 
    __slots__ = ['_children', '_chk_inventory']
 
2244
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
2245
                 'text_id', 'parent_id', '_children', 'executable',
 
2246
                 'revision', 'symlink_target', 'reference_revision',
 
2247
                 '_chk_inventory']
2194
2248
 
2195
2249
    def __init__(self, file_id, name, parent_id, chk_inventory):
2196
2250
        # Don't call InventoryDirectory.__init__ - it isn't right for this
2197
2251
        # class.
2198
2252
        InventoryEntry.__init__(self, file_id, name, parent_id)
2199
2253
        self._children = None
 
2254
        self.kind = 'directory'
2200
2255
        self._chk_inventory = chk_inventory
2201
2256
 
2202
2257
    @property
2287
2342
    return name
2288
2343
 
2289
2344
 
2290
 
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
 
2345
_NAME_RE = None
2291
2346
 
2292
2347
def is_valid_name(name):
 
2348
    global _NAME_RE
 
2349
    if _NAME_RE is None:
 
2350
        _NAME_RE = re.compile(r'^[^/\\]+$')
 
2351
 
2293
2352
    return bool(_NAME_RE.match(name))
2294
2353
 
2295
2354
 
2385
2444
            raise errors.InconsistentDelta(new_path, item[1],
2386
2445
                "new_path with no entry")
2387
2446
        yield item
2388
 
 
2389
 
 
2390
 
def mutable_inventory_from_tree(tree):
2391
 
    """Create a new inventory that has the same contents as a specified tree.
2392
 
 
2393
 
    :param tree: Revision tree to create inventory from
2394
 
    """
2395
 
    entries = tree.iter_entries_by_dir()
2396
 
    inv = Inventory(None, tree.get_revision_id())
2397
 
    for path, inv_entry in entries:
2398
 
        inv.add(inv_entry.copy())
2399
 
    return inv