~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Martin Pool
  • Date: 2010-02-25 06:17:27 UTC
  • mfrom: (5055 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5057.
  • Revision ID: mbp@sourcefrog.net-20100225061727-4sd9lt0qmdc6087t
merge news

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
31
31
lazy_import(globals(), """
32
32
import collections
33
33
import copy
 
34
import os
34
35
import re
35
36
import tarfile
36
37
 
 
38
import bzrlib
37
39
from bzrlib import (
38
40
    chk_map,
39
41
    errors,
40
42
    generate_ids,
41
43
    osutils,
 
44
    symbol_versioning,
42
45
    )
43
46
""")
44
47
 
45
 
from bzrlib import (
46
 
    lazy_regex,
47
 
    trace,
 
48
from bzrlib.errors import (
 
49
    BzrCheckError,
 
50
    BzrError,
48
51
    )
49
 
 
 
52
from bzrlib.symbol_versioning import deprecated_in, deprecated_method
 
53
from bzrlib.trace import mutter
50
54
from bzrlib.static_tuple import StaticTuple
51
 
from bzrlib.symbol_versioning import (
52
 
    deprecated_in,
53
 
    deprecated_method,
54
 
    )
55
55
 
56
56
 
57
57
class InventoryEntry(object):
104
104
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
105
105
    >>> i.path2id('src/wibble')
106
106
    '2325'
 
107
    >>> '2325' in i
 
108
    True
107
109
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
108
110
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
109
111
    >>> i['2326']
129
131
    RENAMED = 'renamed'
130
132
    MODIFIED_AND_RENAMED = 'modified and renamed'
131
133
 
132
 
    __slots__ = ['file_id', 'revision', 'parent_id', 'name']
133
 
 
134
 
    # Attributes that all InventoryEntry instances are expected to have, but
135
 
    # that don't vary for all kinds of entry.  (e.g. symlink_target is only
136
 
    # relevant to InventoryLink, so there's no reason to make every
137
 
    # InventoryFile instance allocate space to hold a value for it.)
138
 
    # Attributes that only vary for files: executable, text_sha1, text_size,
139
 
    # text_id
140
 
    executable = False
141
 
    text_sha1 = None
142
 
    text_size = None
143
 
    text_id = None
144
 
    # Attributes that only vary for symlinks: symlink_target
145
 
    symlink_target = None
146
 
    # Attributes that only vary for tree-references: reference_revision
147
 
    reference_revision = None
148
 
 
 
134
    __slots__ = []
149
135
 
150
136
    def detect_changes(self, old_entry):
151
137
        """Return a (text_modified, meta_modified) from this to old_entry.
172
158
        candidates = {}
173
159
        # identify candidate head revision ids.
174
160
        for inv in previous_inventories:
175
 
            if inv.has_id(self.file_id):
 
161
            if self.file_id in inv:
176
162
                ie = inv[self.file_id]
177
163
                if ie.revision in candidates:
178
164
                    # same revision value in two different inventories:
190
176
                    candidates[ie.revision] = ie
191
177
        return candidates
192
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
 
193
189
    def has_text(self):
194
190
        """Return true if the object this entry represents has textual data.
195
191
 
201
197
        """
202
198
        return False
203
199
 
204
 
    def __init__(self, file_id, name, parent_id):
 
200
    def __init__(self, file_id, name, parent_id, text_id=None):
205
201
        """Create an InventoryEntry
206
202
 
207
203
        The filename must be a single component, relative to the
218
214
        """
219
215
        if '/' in name or '\\' in name:
220
216
            raise errors.InvalidEntryName(name=name)
 
217
        self.executable = False
 
218
        self.revision = None
 
219
        self.text_sha1 = None
 
220
        self.text_size = None
221
221
        self.file_id = file_id
222
 
        self.revision = None
223
222
        self.name = name
 
223
        self.text_id = text_id
224
224
        self.parent_id = parent_id
 
225
        self.symlink_target = None
 
226
        self.reference_revision = None
225
227
 
226
228
    def kind_character(self):
227
229
        """Return a short kind indicator useful for appending to names."""
228
 
        raise errors.BzrError('unknown kind %r' % self.kind)
 
230
        raise BzrError('unknown kind %r' % self.kind)
229
231
 
230
232
    known_kinds = ('file', 'directory', 'symlink')
231
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
 
232
257
    def sorted_children(self):
233
258
        return sorted(self.children.items())
234
259
 
251
276
        """
252
277
        if self.parent_id is not None:
253
278
            if not inv.has_id(self.parent_id):
254
 
                raise errors.BzrCheckError(
255
 
                    'missing parent {%s} in inventory for revision {%s}' % (
256
 
                        self.parent_id, rev_id))
 
279
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
 
280
                        % (self.parent_id, rev_id))
257
281
        checker._add_entry_to_text_key_references(inv, self)
258
282
        self._check(checker, rev_id)
259
283
 
373
397
        pass
374
398
 
375
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
 
376
428
class InventoryDirectory(InventoryEntry):
377
429
    """A directory in an inventory."""
378
430
 
379
 
    __slots__ = ['children']
380
 
 
381
 
    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']
382
434
 
383
435
    def _check(self, checker, rev_id):
384
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))
385
441
        # In non rich root repositories we do not expect a file graph for the
386
442
        # root.
387
443
        if self.name == '' and not checker.rich_roots:
403
459
    def __init__(self, file_id, name, parent_id):
404
460
        super(InventoryDirectory, self).__init__(file_id, name, parent_id)
405
461
        self.children = {}
 
462
        self.kind = 'directory'
406
463
 
407
464
    def kind_character(self):
408
465
        """See InventoryEntry.kind_character."""
409
466
        return '/'
410
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
 
411
481
 
412
482
class InventoryFile(InventoryEntry):
413
483
    """A file in an inventory."""
414
484
 
415
 
    __slots__ = ['text_sha1', 'text_size', 'text_id', 'executable']
416
 
 
417
 
    kind = 'file'
418
 
 
419
 
    def __init__(self, file_id, name, parent_id):
420
 
        super(InventoryFile, self).__init__(file_id, name, parent_id)
421
 
        self.text_sha1 = None
422
 
        self.text_size = None
423
 
        self.text_id = None
424
 
        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']
425
488
 
426
489
    def _check(self, checker, tree_revision_id):
427
490
        """See InventoryEntry._check"""
470
533
        """See InventoryEntry.has_text."""
471
534
        return True
472
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
 
473
540
    def kind_character(self):
474
541
        """See InventoryEntry.kind_character."""
475
542
        return ''
476
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
 
477
561
    def _read_tree_state(self, path, work_tree):
478
562
        """See InventoryEntry._read_tree_state."""
479
563
        self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
511
595
class InventoryLink(InventoryEntry):
512
596
    """A file in an inventory."""
513
597
 
514
 
    __slots__ = ['symlink_target']
515
 
 
516
 
    kind = 'symlink'
517
 
 
518
 
    def __init__(self, file_id, name, parent_id):
519
 
        super(InventoryLink, self).__init__(file_id, name, parent_id)
520
 
        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']
521
601
 
522
602
    def _check(self, checker, tree_revision_id):
523
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))
524
608
        if self.symlink_target is None:
525
609
            checker._report_items.append(
526
610
                'symlink {%s} has no target in revision {%s}'
541
625
        # FIXME: which _modified field should we use ? RBC 20051003
542
626
        text_modified = (self.symlink_target != old_entry.symlink_target)
543
627
        if text_modified:
544
 
            trace.mutter("    symlink target changed")
 
628
            mutter("    symlink target changed")
545
629
        meta_modified = False
546
630
        return text_modified, meta_modified
547
631
 
564
648
        differ = DiffSymlink(old_tree, new_tree, output_to)
565
649
        return differ.diff_symlink(old_target, new_target)
566
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
 
567
655
    def kind_character(self):
568
656
        """See InventoryEntry.kind_character."""
569
657
        return ''
570
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
 
571
675
    def _read_tree_state(self, path, work_tree):
572
676
        """See InventoryEntry._read_tree_state."""
573
677
        self.symlink_target = work_tree.get_symlink_target(self.file_id)
585
689
 
586
690
class TreeReference(InventoryEntry):
587
691
 
588
 
    __slots__ = ['reference_revision']
589
 
 
590
692
    kind = 'tree-reference'
591
693
 
592
694
    def __init__(self, file_id, name, parent_id, revision=None,
631
733
    inserted, other than through the Inventory API.
632
734
    """
633
735
 
634
 
    @deprecated_method(deprecated_in((2, 4, 0)))
635
736
    def __contains__(self, file_id):
636
737
        """True if this entry contains a file with given id.
637
738
 
638
739
        >>> inv = Inventory()
639
740
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
640
741
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
641
 
        >>> inv.has_id('123')
 
742
        >>> '123' in inv
642
743
        True
643
 
        >>> inv.has_id('456')
 
744
        >>> '456' in inv
644
745
        False
645
746
 
646
747
        Note that this method along with __iter__ are not encouraged for use as
721
822
                # if we finished all children, pop it off the stack
722
823
                stack.pop()
723
824
 
724
 
    def _preload_cache(self):
725
 
        """Populate any caches, we are about to access all items.
726
 
        
727
 
        The default implementation does nothing, because CommonInventory doesn't
728
 
        have a cache.
729
 
        """
730
 
        pass
731
 
    
732
825
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
733
826
        yield_parents=False):
734
827
        """Iterate over the entries in a directory first order.
747
840
            specific_file_ids = set(specific_file_ids)
748
841
        # TODO? Perhaps this should return the from_dir so that the root is
749
842
        # yielded? or maybe an option?
750
 
        if from_dir is None and specific_file_ids is None:
751
 
            # They are iterating from the root, and have not specified any
752
 
            # specific entries to look at. All current callers fully consume the
753
 
            # iterator, so we can safely assume we are accessing all entries
754
 
            self._preload_cache()
755
843
        if from_dir is None:
756
844
            if self.root is None:
757
845
                return
759
847
            if (not yield_parents and specific_file_ids is not None and
760
848
                len(specific_file_ids) == 1):
761
849
                file_id = list(specific_file_ids)[0]
762
 
                if self.has_id(file_id):
 
850
                if file_id in self:
763
851
                    yield self.id2path(file_id), self[file_id]
764
852
                return
765
853
            from_dir = self.root
775
863
            parents = set()
776
864
            byid = self
777
865
            def add_ancestors(file_id):
778
 
                if not byid.has_id(file_id):
 
866
                if file_id not in byid:
779
867
                    return
780
868
                parent_id = byid[file_id].parent_id
781
869
                if parent_id is None:
825
913
                    file_id, self[file_id]))
826
914
        return delta
827
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
 
828
924
    def make_entry(self, kind, name, parent_id, file_id=None):
829
925
        """Simple thunk to bzrlib.inventory.make_entry."""
830
926
        return make_entry(kind, name, parent_id, file_id)
844
940
                if ie.kind == 'directory':
845
941
                    descend(ie, child_path)
846
942
 
847
 
        if self.root is not None:
848
 
            descend(self.root, u'')
 
943
        descend(self.root, u'')
849
944
        return accum
850
945
 
851
946
    def directories(self):
965
1060
 
966
1061
    >>> inv.path2id('hello.c')
967
1062
    '123-123'
968
 
    >>> inv.has_id('123-123')
 
1063
    >>> '123-123' in inv
969
1064
    True
970
1065
 
971
1066
    There are iterators over the contents:
1128
1223
            other.add(entry.copy())
1129
1224
        return other
1130
1225
 
 
1226
    def _get_mutable_inventory(self):
 
1227
        """See CommonInventory._get_mutable_inventory."""
 
1228
        return copy.deepcopy(self)
 
1229
 
1131
1230
    def __iter__(self):
1132
1231
        """Iterate over all file-ids."""
1133
1232
        return iter(self._byid)
1173
1272
    def _add_child(self, entry):
1174
1273
        """Add an entry to the inventory, without adding it to its parent"""
1175
1274
        if entry.file_id in self._byid:
1176
 
            raise errors.BzrError(
1177
 
                "inventory already contains entry with id {%s}" %
1178
 
                entry.file_id)
 
1275
            raise BzrError("inventory already contains entry with id {%s}" %
 
1276
                           entry.file_id)
1179
1277
        self._byid[entry.file_id] = entry
1180
1278
        for child in getattr(entry, 'children', {}).itervalues():
1181
1279
            self._add_child(child)
1184
1282
    def add(self, entry):
1185
1283
        """Add entry to inventory.
1186
1284
 
 
1285
        To add  a file to a branch ready to be committed, use Branch.add,
 
1286
        which calls this.
 
1287
 
1187
1288
        :return: entry
1188
1289
        """
1189
1290
        if entry.file_id in self._byid:
1234
1335
        >>> inv = Inventory()
1235
1336
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1236
1337
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1237
 
        >>> inv.has_id('123')
 
1338
        >>> '123' in inv
1238
1339
        True
1239
1340
        >>> del inv['123']
1240
 
        >>> inv.has_id('123')
 
1341
        >>> '123' in inv
1241
1342
        False
1242
1343
        """
1243
1344
        ie = self[file_id]
1345
1446
        """
1346
1447
        new_name = ensure_normalized_name(new_name)
1347
1448
        if not is_valid_name(new_name):
1348
 
            raise errors.BzrError("not an acceptable filename: %r" % new_name)
 
1449
            raise BzrError("not an acceptable filename: %r" % new_name)
1349
1450
 
1350
1451
        new_parent = self._byid[new_parent_id]
1351
1452
        if new_name in new_parent.children:
1352
 
            raise errors.BzrError("%r already exists in %r" %
1353
 
                (new_name, self.id2path(new_parent_id)))
 
1453
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
1354
1454
 
1355
1455
        new_parent_idpath = self.get_idpath(new_parent_id)
1356
1456
        if file_id in new_parent_idpath:
1357
 
            raise errors.BzrError(
1358
 
                "cannot move directory %r into a subdirectory of itself, %r"
 
1457
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
1359
1458
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1360
1459
 
1361
1460
        file_ie = self._byid[file_id]
1397
1496
    def __init__(self, search_key_name):
1398
1497
        CommonInventory.__init__(self)
1399
1498
        self._fileid_to_entry_cache = {}
1400
 
        self._fully_cached = False
1401
1499
        self._path_to_fileid_cache = {}
1402
1500
        self._search_key_name = search_key_name
1403
1501
        self.root_id = None
1490
1588
            if entry.kind == 'directory':
1491
1589
                directories_to_expand.add(entry.file_id)
1492
1590
            interesting.add(entry.parent_id)
1493
 
            children_of_parent_id.setdefault(entry.parent_id, set()
1494
 
                                             ).add(entry.file_id)
 
1591
            children_of_parent_id.setdefault(entry.parent_id, []
 
1592
                                             ).append(entry.file_id)
1495
1593
 
1496
1594
        # Now, interesting has all of the direct parents, but not the
1497
1595
        # parents of those parents. It also may have some duplicates with
1505
1603
            next_parents = set()
1506
1604
            for entry in self._getitems(remaining_parents):
1507
1605
                next_parents.add(entry.parent_id)
1508
 
                children_of_parent_id.setdefault(entry.parent_id, set()
1509
 
                                                 ).add(entry.file_id)
 
1606
                children_of_parent_id.setdefault(entry.parent_id, []
 
1607
                                                 ).append(entry.file_id)
1510
1608
            # Remove any search tips we've already processed
1511
1609
            remaining_parents = next_parents.difference(interesting)
1512
1610
            interesting.update(remaining_parents)
1525
1623
            for entry in self._getitems(next_file_ids):
1526
1624
                if entry.kind == 'directory':
1527
1625
                    directories_to_expand.add(entry.file_id)
1528
 
                children_of_parent_id.setdefault(entry.parent_id, set()
1529
 
                                                 ).add(entry.file_id)
 
1626
                children_of_parent_id.setdefault(entry.parent_id, []
 
1627
                                                 ).append(entry.file_id)
1530
1628
        return interesting, children_of_parent_id
1531
1629
 
1532
1630
    def filter(self, specific_fileids):
1554
1652
            # parent_to_children with at least the tree root.)
1555
1653
            return other
1556
1654
        cache = self._fileid_to_entry_cache
1557
 
        remaining_children = collections.deque(parent_to_children[self.root_id])
 
1655
        try:
 
1656
            remaining_children = collections.deque(parent_to_children[self.root_id])
 
1657
        except:
 
1658
            import pdb; pdb.set_trace()
 
1659
            raise
1558
1660
        while remaining_children:
1559
1661
            file_id = remaining_children.popleft()
1560
1662
            ie = cache[file_id]
1610
1712
        self._fileid_to_entry_cache[result.file_id] = result
1611
1713
        return result
1612
1714
 
 
1715
    def _get_mutable_inventory(self):
 
1716
        """See CommonInventory._get_mutable_inventory."""
 
1717
        entries = self.iter_entries()
 
1718
        inv = Inventory(None, self.revision_id)
 
1719
        for path, inv_entry in entries:
 
1720
            inv.add(inv_entry.copy())
 
1721
        return inv
 
1722
 
1613
1723
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1614
1724
        propagate_caches=False):
1615
1725
        """Create a new CHKInventory by applying inventory_delta to this one.
1956
2066
 
1957
2067
    def iter_just_entries(self):
1958
2068
        """Iterate over all entries.
1959
 
 
 
2069
        
1960
2070
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1961
2071
        and the order of entries is undefined.
1962
2072
 
1970
2080
                self._fileid_to_entry_cache[file_id] = ie
1971
2081
            yield ie
1972
2082
 
1973
 
    def _preload_cache(self):
1974
 
        """Make sure all file-ids are in _fileid_to_entry_cache"""
1975
 
        if self._fully_cached:
1976
 
            return # No need to do it again
1977
 
        # The optimal sort order is to use iteritems() directly
1978
 
        cache = self._fileid_to_entry_cache
1979
 
        for key, entry in self.id_to_entry.iteritems():
1980
 
            file_id = key[0]
1981
 
            if file_id not in cache:
1982
 
                ie = self._bytes_to_entry(entry)
1983
 
                cache[file_id] = ie
1984
 
            else:
1985
 
                ie = cache[file_id]
1986
 
        last_parent_id = last_parent_ie = None
1987
 
        pid_items = self.parent_id_basename_to_file_id.iteritems()
1988
 
        for key, child_file_id in pid_items:
1989
 
            if key == ('', ''): # This is the root
1990
 
                if child_file_id != self.root_id:
1991
 
                    raise ValueError('Data inconsistency detected.'
1992
 
                        ' We expected data with key ("","") to match'
1993
 
                        ' the root id, but %s != %s'
1994
 
                        % (child_file_id, self.root_id))
1995
 
                continue
1996
 
            parent_id, basename = key
1997
 
            ie = cache[child_file_id]
1998
 
            if parent_id == last_parent_id:
1999
 
                parent_ie = last_parent_ie
2000
 
            else:
2001
 
                parent_ie = cache[parent_id]
2002
 
            if parent_ie.kind != 'directory':
2003
 
                raise ValueError('Data inconsistency detected.'
2004
 
                    ' An entry in the parent_id_basename_to_file_id map'
2005
 
                    ' has parent_id {%s} but the kind of that object'
2006
 
                    ' is %r not "directory"' % (parent_id, parent_ie.kind))
2007
 
            if parent_ie._children is None:
2008
 
                parent_ie._children = {}
2009
 
            basename = basename.decode('utf-8')
2010
 
            if basename in parent_ie._children:
2011
 
                existing_ie = parent_ie._children[basename]
2012
 
                if existing_ie != ie:
2013
 
                    raise ValueError('Data inconsistency detected.'
2014
 
                        ' Two entries with basename %r were found'
2015
 
                        ' in the parent entry {%s}'
2016
 
                        % (basename, parent_id))
2017
 
            if basename != ie.name:
2018
 
                raise ValueError('Data inconsistency detected.'
2019
 
                    ' In the parent_id_basename_to_file_id map, file_id'
2020
 
                    ' {%s} is listed as having basename %r, but in the'
2021
 
                    ' id_to_entry map it is %r'
2022
 
                    % (child_file_id, basename, ie.name))
2023
 
            parent_ie._children[basename] = ie
2024
 
        self._fully_cached = True
2025
 
 
2026
2083
    def iter_changes(self, basis):
2027
2084
        """Generate a Tree.iter_changes change list between this and basis.
2028
2085
 
2188
2245
class CHKInventoryDirectory(InventoryDirectory):
2189
2246
    """A directory in an inventory."""
2190
2247
 
2191
 
    __slots__ = ['_children', '_chk_inventory']
 
2248
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
2249
                 'text_id', 'parent_id', '_children', 'executable',
 
2250
                 'revision', 'symlink_target', 'reference_revision',
 
2251
                 '_chk_inventory']
2192
2252
 
2193
2253
    def __init__(self, file_id, name, parent_id, chk_inventory):
2194
2254
        # Don't call InventoryDirectory.__init__ - it isn't right for this
2195
2255
        # class.
2196
2256
        InventoryEntry.__init__(self, file_id, name, parent_id)
2197
2257
        self._children = None
 
2258
        self.kind = 'directory'
2198
2259
        self._chk_inventory = chk_inventory
2199
2260
 
2200
2261
    @property
2285
2346
    return name
2286
2347
 
2287
2348
 
2288
 
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
 
2349
_NAME_RE = None
2289
2350
 
2290
2351
def is_valid_name(name):
 
2352
    global _NAME_RE
 
2353
    if _NAME_RE is None:
 
2354
        _NAME_RE = re.compile(r'^[^/\\]+$')
 
2355
 
2291
2356
    return bool(_NAME_RE.match(name))
2292
2357
 
2293
2358
 
2383
2448
            raise errors.InconsistentDelta(new_path, item[1],
2384
2449
                "new_path with no entry")
2385
2450
        yield item
2386
 
 
2387
 
 
2388
 
def mutable_inventory_from_tree(tree):
2389
 
    """Create a new inventory that has the same contents as a specified tree.
2390
 
 
2391
 
    :param tree: Revision tree to create inventory from
2392
 
    """
2393
 
    entries = tree.iter_entries_by_dir()
2394
 
    inv = Inventory(None, tree.get_revision_id())
2395
 
    for path, inv_entry in entries:
2396
 
        inv.add(inv_entry.copy())
2397
 
    return inv