~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Jelmer Vernooij
  • Date: 2011-10-14 12:49:33 UTC
  • mto: This revision was merged to the branch mainline in revision 6216.
  • Revision ID: jelmer@samba.org-20111014124933-732wmdc39ebzga0b
Fix last locking issues.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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
35
34
import re
36
35
import tarfile
37
36
 
38
 
import bzrlib
39
37
from bzrlib import (
40
38
    chk_map,
41
39
    errors,
42
40
    generate_ids,
43
41
    osutils,
44
 
    symbol_versioning,
45
42
    )
46
43
""")
47
44
 
48
 
from bzrlib.errors import (
49
 
    BzrCheckError,
50
 
    BzrError,
 
45
from bzrlib import (
 
46
    lazy_regex,
 
47
    trace,
51
48
    )
52
 
from bzrlib.symbol_versioning import deprecated_in, deprecated_method
53
 
from bzrlib.trace import mutter
 
49
 
54
50
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
109
107
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
110
108
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
111
109
    >>> i['2326']
131
129
    RENAMED = 'renamed'
132
130
    MODIFIED_AND_RENAMED = 'modified and renamed'
133
131
 
134
 
    __slots__ = []
 
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
 
135
149
 
136
150
    def detect_changes(self, old_entry):
137
151
        """Return a (text_modified, meta_modified) from this to old_entry.
158
172
        candidates = {}
159
173
        # identify candidate head revision ids.
160
174
        for inv in previous_inventories:
161
 
            if self.file_id in inv:
 
175
            if inv.has_id(self.file_id):
162
176
                ie = inv[self.file_id]
163
177
                if ie.revision in candidates:
164
178
                    # same revision value in two different inventories:
176
190
                    candidates[ie.revision] = ie
177
191
        return candidates
178
192
 
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
 
 
189
193
    def has_text(self):
190
194
        """Return true if the object this entry represents has textual data.
191
195
 
197
201
        """
198
202
        return False
199
203
 
200
 
    def __init__(self, file_id, name, parent_id, text_id=None):
 
204
    def __init__(self, file_id, name, parent_id):
201
205
        """Create an InventoryEntry
202
206
 
203
207
        The filename must be a single component, relative to the
214
218
        """
215
219
        if '/' in name or '\\' in name:
216
220
            raise errors.InvalidEntryName(name=name)
217
 
        self.executable = False
 
221
        self.file_id = file_id
218
222
        self.revision = None
219
 
        self.text_sha1 = None
220
 
        self.text_size = None
221
 
        self.file_id = file_id
222
223
        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
227
225
 
228
226
    def kind_character(self):
229
227
        """Return a short kind indicator useful for appending to names."""
230
 
        raise BzrError('unknown kind %r' % self.kind)
 
228
        raise errors.BzrError('unknown kind %r' % self.kind)
231
229
 
232
230
    known_kinds = ('file', 'directory', 'symlink')
233
231
 
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
 
 
257
232
    def sorted_children(self):
258
233
        return sorted(self.children.items())
259
234
 
276
251
        """
277
252
        if self.parent_id is not None:
278
253
            if not inv.has_id(self.parent_id):
279
 
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
280
 
                        % (self.parent_id, rev_id))
 
254
                raise errors.BzrCheckError(
 
255
                    'missing parent {%s} in inventory for revision {%s}' % (
 
256
                        self.parent_id, rev_id))
281
257
        checker._add_entry_to_text_key_references(inv, self)
282
258
        self._check(checker, rev_id)
283
259
 
397
373
        pass
398
374
 
399
375
 
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
 
 
428
376
class InventoryDirectory(InventoryEntry):
429
377
    """A directory in an inventory."""
430
378
 
431
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
432
 
                 'text_id', 'parent_id', 'children', 'executable',
433
 
                 'revision', 'symlink_target', 'reference_revision']
 
379
    __slots__ = ['children']
 
380
 
 
381
    kind = 'directory'
434
382
 
435
383
    def _check(self, checker, rev_id):
436
384
        """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))
441
385
        # In non rich root repositories we do not expect a file graph for the
442
386
        # root.
443
387
        if self.name == '' and not checker.rich_roots:
459
403
    def __init__(self, file_id, name, parent_id):
460
404
        super(InventoryDirectory, self).__init__(file_id, name, parent_id)
461
405
        self.children = {}
462
 
        self.kind = 'directory'
463
406
 
464
407
    def kind_character(self):
465
408
        """See InventoryEntry.kind_character."""
466
409
        return '/'
467
410
 
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
 
 
481
411
 
482
412
class InventoryFile(InventoryEntry):
483
413
    """A file in an inventory."""
484
414
 
485
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
486
 
                 'text_id', 'parent_id', 'children', 'executable',
487
 
                 'revision', 'symlink_target', 'reference_revision']
 
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
488
425
 
489
426
    def _check(self, checker, tree_revision_id):
490
427
        """See InventoryEntry._check"""
533
470
        """See InventoryEntry.has_text."""
534
471
        return True
535
472
 
536
 
    def __init__(self, file_id, name, parent_id):
537
 
        super(InventoryFile, self).__init__(file_id, name, parent_id)
538
 
        self.kind = 'file'
539
 
 
540
473
    def kind_character(self):
541
474
        """See InventoryEntry.kind_character."""
542
475
        return ''
543
476
 
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
 
 
561
477
    def _read_tree_state(self, path, work_tree):
562
478
        """See InventoryEntry._read_tree_state."""
563
479
        self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
595
511
class InventoryLink(InventoryEntry):
596
512
    """A file in an inventory."""
597
513
 
598
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
599
 
                 'text_id', 'parent_id', 'children', 'executable',
600
 
                 'revision', 'symlink_target', 'reference_revision']
 
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
601
521
 
602
522
    def _check(self, checker, tree_revision_id):
603
523
        """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))
608
524
        if self.symlink_target is None:
609
525
            checker._report_items.append(
610
526
                'symlink {%s} has no target in revision {%s}'
625
541
        # FIXME: which _modified field should we use ? RBC 20051003
626
542
        text_modified = (self.symlink_target != old_entry.symlink_target)
627
543
        if text_modified:
628
 
            mutter("    symlink target changed")
 
544
            trace.mutter("    symlink target changed")
629
545
        meta_modified = False
630
546
        return text_modified, meta_modified
631
547
 
648
564
        differ = DiffSymlink(old_tree, new_tree, output_to)
649
565
        return differ.diff_symlink(old_target, new_target)
650
566
 
651
 
    def __init__(self, file_id, name, parent_id):
652
 
        super(InventoryLink, self).__init__(file_id, name, parent_id)
653
 
        self.kind = 'symlink'
654
 
 
655
567
    def kind_character(self):
656
568
        """See InventoryEntry.kind_character."""
657
569
        return ''
658
570
 
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
 
 
675
571
    def _read_tree_state(self, path, work_tree):
676
572
        """See InventoryEntry._read_tree_state."""
677
573
        self.symlink_target = work_tree.get_symlink_target(self.file_id)
689
585
 
690
586
class TreeReference(InventoryEntry):
691
587
 
 
588
    __slots__ = ['reference_revision']
 
589
 
692
590
    kind = 'tree-reference'
693
591
 
694
592
    def __init__(self, file_id, name, parent_id, revision=None,
733
631
    inserted, other than through the Inventory API.
734
632
    """
735
633
 
 
634
    @deprecated_method(deprecated_in((2, 4, 0)))
736
635
    def __contains__(self, file_id):
737
636
        """True if this entry contains a file with given id.
738
637
 
739
638
        >>> inv = Inventory()
740
639
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
741
640
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
742
 
        >>> '123' in inv
 
641
        >>> inv.has_id('123')
743
642
        True
744
 
        >>> '456' in inv
 
643
        >>> inv.has_id('456')
745
644
        False
746
645
 
747
646
        Note that this method along with __iter__ are not encouraged for use as
822
721
                # if we finished all children, pop it off the stack
823
722
                stack.pop()
824
723
 
 
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
    
825
732
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
826
733
        yield_parents=False):
827
734
        """Iterate over the entries in a directory first order.
840
747
            specific_file_ids = set(specific_file_ids)
841
748
        # TODO? Perhaps this should return the from_dir so that the root is
842
749
        # 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()
843
755
        if from_dir is None:
844
756
            if self.root is None:
845
757
                return
847
759
            if (not yield_parents and specific_file_ids is not None and
848
760
                len(specific_file_ids) == 1):
849
761
                file_id = list(specific_file_ids)[0]
850
 
                if file_id in self:
 
762
                if self.has_id(file_id):
851
763
                    yield self.id2path(file_id), self[file_id]
852
764
                return
853
765
            from_dir = self.root
863
775
            parents = set()
864
776
            byid = self
865
777
            def add_ancestors(file_id):
866
 
                if file_id not in byid:
 
778
                if not byid.has_id(file_id):
867
779
                    return
868
780
                parent_id = byid[file_id].parent_id
869
781
                if parent_id is None:
913
825
                    file_id, self[file_id]))
914
826
        return delta
915
827
 
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
 
 
924
828
    def make_entry(self, kind, name, parent_id, file_id=None):
925
829
        """Simple thunk to bzrlib.inventory.make_entry."""
926
830
        return make_entry(kind, name, parent_id, file_id)
940
844
                if ie.kind == 'directory':
941
845
                    descend(ie, child_path)
942
846
 
943
 
        descend(self.root, u'')
 
847
        if self.root is not None:
 
848
            descend(self.root, u'')
944
849
        return accum
945
850
 
946
851
    def directories(self):
1060
965
 
1061
966
    >>> inv.path2id('hello.c')
1062
967
    '123-123'
1063
 
    >>> '123-123' in inv
 
968
    >>> inv.has_id('123-123')
1064
969
    True
1065
970
 
1066
971
    There are iterators over the contents:
1223
1128
            other.add(entry.copy())
1224
1129
        return other
1225
1130
 
1226
 
    def _get_mutable_inventory(self):
1227
 
        """See CommonInventory._get_mutable_inventory."""
1228
 
        return copy.deepcopy(self)
1229
 
 
1230
1131
    def __iter__(self):
1231
1132
        """Iterate over all file-ids."""
1232
1133
        return iter(self._byid)
1272
1173
    def _add_child(self, entry):
1273
1174
        """Add an entry to the inventory, without adding it to its parent"""
1274
1175
        if entry.file_id in self._byid:
1275
 
            raise BzrError("inventory already contains entry with id {%s}" %
1276
 
                           entry.file_id)
 
1176
            raise errors.BzrError(
 
1177
                "inventory already contains entry with id {%s}" %
 
1178
                entry.file_id)
1277
1179
        self._byid[entry.file_id] = entry
1278
1180
        for child in getattr(entry, 'children', {}).itervalues():
1279
1181
            self._add_child(child)
1282
1184
    def add(self, entry):
1283
1185
        """Add entry to inventory.
1284
1186
 
1285
 
        To add  a file to a branch ready to be committed, use Branch.add,
1286
 
        which calls this.
1287
 
 
1288
1187
        :return: entry
1289
1188
        """
1290
1189
        if entry.file_id in self._byid:
1335
1234
        >>> inv = Inventory()
1336
1235
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1337
1236
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1338
 
        >>> '123' in inv
 
1237
        >>> inv.has_id('123')
1339
1238
        True
1340
1239
        >>> del inv['123']
1341
 
        >>> '123' in inv
 
1240
        >>> inv.has_id('123')
1342
1241
        False
1343
1242
        """
1344
1243
        ie = self[file_id]
1446
1345
        """
1447
1346
        new_name = ensure_normalized_name(new_name)
1448
1347
        if not is_valid_name(new_name):
1449
 
            raise BzrError("not an acceptable filename: %r" % new_name)
 
1348
            raise errors.BzrError("not an acceptable filename: %r" % new_name)
1450
1349
 
1451
1350
        new_parent = self._byid[new_parent_id]
1452
1351
        if new_name in new_parent.children:
1453
 
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
1352
            raise errors.BzrError("%r already exists in %r" %
 
1353
                (new_name, self.id2path(new_parent_id)))
1454
1354
 
1455
1355
        new_parent_idpath = self.get_idpath(new_parent_id)
1456
1356
        if file_id in new_parent_idpath:
1457
 
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
 
1357
            raise errors.BzrError(
 
1358
                "cannot move directory %r into a subdirectory of itself, %r"
1458
1359
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1459
1360
 
1460
1361
        file_ie = self._byid[file_id]
1496
1397
    def __init__(self, search_key_name):
1497
1398
        CommonInventory.__init__(self)
1498
1399
        self._fileid_to_entry_cache = {}
 
1400
        self._fully_cached = False
1499
1401
        self._path_to_fileid_cache = {}
1500
1402
        self._search_key_name = search_key_name
1501
1403
        self.root_id = None
1588
1490
            if entry.kind == 'directory':
1589
1491
                directories_to_expand.add(entry.file_id)
1590
1492
            interesting.add(entry.parent_id)
1591
 
            children_of_parent_id.setdefault(entry.parent_id, []
1592
 
                                             ).append(entry.file_id)
 
1493
            children_of_parent_id.setdefault(entry.parent_id, set()
 
1494
                                             ).add(entry.file_id)
1593
1495
 
1594
1496
        # Now, interesting has all of the direct parents, but not the
1595
1497
        # parents of those parents. It also may have some duplicates with
1603
1505
            next_parents = set()
1604
1506
            for entry in self._getitems(remaining_parents):
1605
1507
                next_parents.add(entry.parent_id)
1606
 
                children_of_parent_id.setdefault(entry.parent_id, []
1607
 
                                                 ).append(entry.file_id)
 
1508
                children_of_parent_id.setdefault(entry.parent_id, set()
 
1509
                                                 ).add(entry.file_id)
1608
1510
            # Remove any search tips we've already processed
1609
1511
            remaining_parents = next_parents.difference(interesting)
1610
1512
            interesting.update(remaining_parents)
1623
1525
            for entry in self._getitems(next_file_ids):
1624
1526
                if entry.kind == 'directory':
1625
1527
                    directories_to_expand.add(entry.file_id)
1626
 
                children_of_parent_id.setdefault(entry.parent_id, []
1627
 
                                                 ).append(entry.file_id)
 
1528
                children_of_parent_id.setdefault(entry.parent_id, set()
 
1529
                                                 ).add(entry.file_id)
1628
1530
        return interesting, children_of_parent_id
1629
1531
 
1630
1532
    def filter(self, specific_fileids):
1652
1554
            # parent_to_children with at least the tree root.)
1653
1555
            return other
1654
1556
        cache = self._fileid_to_entry_cache
1655
 
        try:
1656
 
            remaining_children = collections.deque(parent_to_children[self.root_id])
1657
 
        except:
1658
 
            import pdb; pdb.set_trace()
1659
 
            raise
 
1557
        remaining_children = collections.deque(parent_to_children[self.root_id])
1660
1558
        while remaining_children:
1661
1559
            file_id = remaining_children.popleft()
1662
1560
            ie = cache[file_id]
1712
1610
        self._fileid_to_entry_cache[result.file_id] = result
1713
1611
        return result
1714
1612
 
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
 
 
1723
1613
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1724
1614
        propagate_caches=False):
1725
1615
        """Create a new CHKInventory by applying inventory_delta to this one.
2066
1956
 
2067
1957
    def iter_just_entries(self):
2068
1958
        """Iterate over all entries.
2069
 
        
 
1959
 
2070
1960
        Unlike iter_entries(), just the entries are returned (not (path, ie))
2071
1961
        and the order of entries is undefined.
2072
1962
 
2080
1970
                self._fileid_to_entry_cache[file_id] = ie
2081
1971
            yield ie
2082
1972
 
 
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
 
2083
2026
    def iter_changes(self, basis):
2084
2027
        """Generate a Tree.iter_changes change list between this and basis.
2085
2028
 
2245
2188
class CHKInventoryDirectory(InventoryDirectory):
2246
2189
    """A directory in an inventory."""
2247
2190
 
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']
 
2191
    __slots__ = ['_children', '_chk_inventory']
2252
2192
 
2253
2193
    def __init__(self, file_id, name, parent_id, chk_inventory):
2254
2194
        # Don't call InventoryDirectory.__init__ - it isn't right for this
2255
2195
        # class.
2256
2196
        InventoryEntry.__init__(self, file_id, name, parent_id)
2257
2197
        self._children = None
2258
 
        self.kind = 'directory'
2259
2198
        self._chk_inventory = chk_inventory
2260
2199
 
2261
2200
    @property
2346
2285
    return name
2347
2286
 
2348
2287
 
2349
 
_NAME_RE = None
 
2288
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
2350
2289
 
2351
2290
def is_valid_name(name):
2352
 
    global _NAME_RE
2353
 
    if _NAME_RE is None:
2354
 
        _NAME_RE = re.compile(r'^[^/\\]+$')
2355
 
 
2356
2291
    return bool(_NAME_RE.match(name))
2357
2292
 
2358
2293
 
2448
2383
            raise errors.InconsistentDelta(new_path, item[1],
2449
2384
                "new_path with no entry")
2450
2385
        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