~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-03-25 00:02:51 UTC
  • mfrom: (5106.1.1 version-bump)
  • Revision ID: pqm@pqm.ubuntu.com-20100325000251-bwsv5c5d3l9x3lnn
(Jelmer) Bump API version for 2.2.0.

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
 
 
736
    def __contains__(self, file_id):
 
737
        """True if this entry contains a file with given id.
 
738
 
 
739
        >>> inv = Inventory()
 
740
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
 
741
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
 
742
        >>> '123' in inv
 
743
        True
 
744
        >>> '456' in inv
 
745
        False
 
746
 
 
747
        Note that this method along with __iter__ are not encouraged for use as
 
748
        they are less clear than specific query methods - they may be rmeoved
 
749
        in the future.
 
750
        """
 
751
        return self.has_id(file_id)
 
752
 
636
753
    def has_filename(self, filename):
637
754
        return bool(self.path2id(filename))
638
755
 
705
822
                # if we finished all children, pop it off the stack
706
823
                stack.pop()
707
824
 
708
 
    def _preload_cache(self):
709
 
        """Populate any caches, we are about to access all items.
710
 
        
711
 
        The default implementation does nothing, because CommonInventory doesn't
712
 
        have a cache.
713
 
        """
714
 
        pass
715
 
    
716
825
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
717
826
        yield_parents=False):
718
827
        """Iterate over the entries in a directory first order.
731
840
            specific_file_ids = set(specific_file_ids)
732
841
        # TODO? Perhaps this should return the from_dir so that the root is
733
842
        # yielded? or maybe an option?
734
 
        if from_dir is None and specific_file_ids is None:
735
 
            # They are iterating from the root, and have not specified any
736
 
            # specific entries to look at. All current callers fully consume the
737
 
            # iterator, so we can safely assume we are accessing all entries
738
 
            self._preload_cache()
739
843
        if from_dir is None:
740
844
            if self.root is None:
741
845
                return
743
847
            if (not yield_parents and specific_file_ids is not None and
744
848
                len(specific_file_ids) == 1):
745
849
                file_id = list(specific_file_ids)[0]
746
 
                if self.has_id(file_id):
 
850
                if file_id in self:
747
851
                    yield self.id2path(file_id), self[file_id]
748
852
                return
749
853
            from_dir = self.root
759
863
            parents = set()
760
864
            byid = self
761
865
            def add_ancestors(file_id):
762
 
                if not byid.has_id(file_id):
 
866
                if file_id not in byid:
763
867
                    return
764
868
                parent_id = byid[file_id].parent_id
765
869
                if parent_id is None:
809
913
                    file_id, self[file_id]))
810
914
        return delta
811
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
 
812
924
    def make_entry(self, kind, name, parent_id, file_id=None):
813
925
        """Simple thunk to bzrlib.inventory.make_entry."""
814
926
        return make_entry(kind, name, parent_id, file_id)
828
940
                if ie.kind == 'directory':
829
941
                    descend(ie, child_path)
830
942
 
831
 
        if self.root is not None:
832
 
            descend(self.root, u'')
 
943
        descend(self.root, u'')
 
944
        return accum
 
945
 
 
946
    def directories(self):
 
947
        """Return (path, entry) pairs for all directories, including the root.
 
948
        """
 
949
        accum = []
 
950
        def descend(parent_ie, parent_path):
 
951
            accum.append((parent_path, parent_ie))
 
952
 
 
953
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
 
954
            kids.sort()
 
955
 
 
956
            for name, child_ie in kids:
 
957
                child_path = osutils.pathjoin(parent_path, name)
 
958
                descend(child_ie, child_path)
 
959
        descend(self.root, u'')
833
960
        return accum
834
961
 
835
962
    def path2id(self, relpath):
933
1060
 
934
1061
    >>> inv.path2id('hello.c')
935
1062
    '123-123'
936
 
    >>> inv.has_id('123-123')
 
1063
    >>> '123-123' in inv
937
1064
    True
938
1065
 
939
1066
    There are iterators over the contents:
1096
1223
            other.add(entry.copy())
1097
1224
        return other
1098
1225
 
 
1226
    def _get_mutable_inventory(self):
 
1227
        """See CommonInventory._get_mutable_inventory."""
 
1228
        return copy.deepcopy(self)
 
1229
 
1099
1230
    def __iter__(self):
1100
1231
        """Iterate over all file-ids."""
1101
1232
        return iter(self._byid)
1141
1272
    def _add_child(self, entry):
1142
1273
        """Add an entry to the inventory, without adding it to its parent"""
1143
1274
        if entry.file_id in self._byid:
1144
 
            raise errors.BzrError(
1145
 
                "inventory already contains entry with id {%s}" %
1146
 
                entry.file_id)
 
1275
            raise BzrError("inventory already contains entry with id {%s}" %
 
1276
                           entry.file_id)
1147
1277
        self._byid[entry.file_id] = entry
1148
1278
        for child in getattr(entry, 'children', {}).itervalues():
1149
1279
            self._add_child(child)
1152
1282
    def add(self, entry):
1153
1283
        """Add entry to inventory.
1154
1284
 
 
1285
        To add  a file to a branch ready to be committed, use Branch.add,
 
1286
        which calls this.
 
1287
 
1155
1288
        :return: entry
1156
1289
        """
1157
1290
        if entry.file_id in self._byid:
1202
1335
        >>> inv = Inventory()
1203
1336
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1204
1337
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1205
 
        >>> inv.has_id('123')
 
1338
        >>> '123' in inv
1206
1339
        True
1207
1340
        >>> del inv['123']
1208
 
        >>> inv.has_id('123')
 
1341
        >>> '123' in inv
1209
1342
        False
1210
1343
        """
1211
1344
        ie = self[file_id]
1313
1446
        """
1314
1447
        new_name = ensure_normalized_name(new_name)
1315
1448
        if not is_valid_name(new_name):
1316
 
            raise errors.BzrError("not an acceptable filename: %r" % new_name)
 
1449
            raise BzrError("not an acceptable filename: %r" % new_name)
1317
1450
 
1318
1451
        new_parent = self._byid[new_parent_id]
1319
1452
        if new_name in new_parent.children:
1320
 
            raise errors.BzrError("%r already exists in %r" %
1321
 
                (new_name, self.id2path(new_parent_id)))
 
1453
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
1322
1454
 
1323
1455
        new_parent_idpath = self.get_idpath(new_parent_id)
1324
1456
        if file_id in new_parent_idpath:
1325
 
            raise errors.BzrError(
1326
 
                "cannot move directory %r into a subdirectory of itself, %r"
 
1457
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
1327
1458
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1328
1459
 
1329
1460
        file_ie = self._byid[file_id]
1365
1496
    def __init__(self, search_key_name):
1366
1497
        CommonInventory.__init__(self)
1367
1498
        self._fileid_to_entry_cache = {}
1368
 
        self._fully_cached = False
1369
1499
        self._path_to_fileid_cache = {}
1370
1500
        self._search_key_name = search_key_name
1371
1501
        self.root_id = None
1458
1588
            if entry.kind == 'directory':
1459
1589
                directories_to_expand.add(entry.file_id)
1460
1590
            interesting.add(entry.parent_id)
1461
 
            children_of_parent_id.setdefault(entry.parent_id, set()
1462
 
                                             ).add(entry.file_id)
 
1591
            children_of_parent_id.setdefault(entry.parent_id, []
 
1592
                                             ).append(entry.file_id)
1463
1593
 
1464
1594
        # Now, interesting has all of the direct parents, but not the
1465
1595
        # parents of those parents. It also may have some duplicates with
1473
1603
            next_parents = set()
1474
1604
            for entry in self._getitems(remaining_parents):
1475
1605
                next_parents.add(entry.parent_id)
1476
 
                children_of_parent_id.setdefault(entry.parent_id, set()
1477
 
                                                 ).add(entry.file_id)
 
1606
                children_of_parent_id.setdefault(entry.parent_id, []
 
1607
                                                 ).append(entry.file_id)
1478
1608
            # Remove any search tips we've already processed
1479
1609
            remaining_parents = next_parents.difference(interesting)
1480
1610
            interesting.update(remaining_parents)
1493
1623
            for entry in self._getitems(next_file_ids):
1494
1624
                if entry.kind == 'directory':
1495
1625
                    directories_to_expand.add(entry.file_id)
1496
 
                children_of_parent_id.setdefault(entry.parent_id, set()
1497
 
                                                 ).add(entry.file_id)
 
1626
                children_of_parent_id.setdefault(entry.parent_id, []
 
1627
                                                 ).append(entry.file_id)
1498
1628
        return interesting, children_of_parent_id
1499
1629
 
1500
1630
    def filter(self, specific_fileids):
1522
1652
            # parent_to_children with at least the tree root.)
1523
1653
            return other
1524
1654
        cache = self._fileid_to_entry_cache
1525
 
        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
1526
1660
        while remaining_children:
1527
1661
            file_id = remaining_children.popleft()
1528
1662
            ie = cache[file_id]
1578
1712
        self._fileid_to_entry_cache[result.file_id] = result
1579
1713
        return result
1580
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
 
1581
1723
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1582
1724
        propagate_caches=False):
1583
1725
        """Create a new CHKInventory by applying inventory_delta to this one.
1924
2066
 
1925
2067
    def iter_just_entries(self):
1926
2068
        """Iterate over all entries.
1927
 
 
 
2069
        
1928
2070
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1929
2071
        and the order of entries is undefined.
1930
2072
 
1938
2080
                self._fileid_to_entry_cache[file_id] = ie
1939
2081
            yield ie
1940
2082
 
1941
 
    def _preload_cache(self):
1942
 
        """Make sure all file-ids are in _fileid_to_entry_cache"""
1943
 
        if self._fully_cached:
1944
 
            return # No need to do it again
1945
 
        # The optimal sort order is to use iteritems() directly
1946
 
        cache = self._fileid_to_entry_cache
1947
 
        for key, entry in self.id_to_entry.iteritems():
1948
 
            file_id = key[0]
1949
 
            if file_id not in cache:
1950
 
                ie = self._bytes_to_entry(entry)
1951
 
                cache[file_id] = ie
1952
 
            else:
1953
 
                ie = cache[file_id]
1954
 
        last_parent_id = last_parent_ie = None
1955
 
        pid_items = self.parent_id_basename_to_file_id.iteritems()
1956
 
        for key, child_file_id in pid_items:
1957
 
            if key == ('', ''): # This is the root
1958
 
                if child_file_id != self.root_id:
1959
 
                    raise ValueError('Data inconsistency detected.'
1960
 
                        ' We expected data with key ("","") to match'
1961
 
                        ' the root id, but %s != %s'
1962
 
                        % (child_file_id, self.root_id))
1963
 
                continue
1964
 
            parent_id, basename = key
1965
 
            ie = cache[child_file_id]
1966
 
            if parent_id == last_parent_id:
1967
 
                parent_ie = last_parent_ie
1968
 
            else:
1969
 
                parent_ie = cache[parent_id]
1970
 
            if parent_ie.kind != 'directory':
1971
 
                raise ValueError('Data inconsistency detected.'
1972
 
                    ' An entry in the parent_id_basename_to_file_id map'
1973
 
                    ' has parent_id {%s} but the kind of that object'
1974
 
                    ' is %r not "directory"' % (parent_id, parent_ie.kind))
1975
 
            if parent_ie._children is None:
1976
 
                parent_ie._children = {}
1977
 
            basename = basename.decode('utf-8')
1978
 
            if basename in parent_ie._children:
1979
 
                existing_ie = parent_ie._children[basename]
1980
 
                if existing_ie != ie:
1981
 
                    raise ValueError('Data inconsistency detected.'
1982
 
                        ' Two entries with basename %r were found'
1983
 
                        ' in the parent entry {%s}'
1984
 
                        % (basename, parent_id))
1985
 
            if basename != ie.name:
1986
 
                raise ValueError('Data inconsistency detected.'
1987
 
                    ' In the parent_id_basename_to_file_id map, file_id'
1988
 
                    ' {%s} is listed as having basename %r, but in the'
1989
 
                    ' id_to_entry map it is %r'
1990
 
                    % (child_file_id, basename, ie.name))
1991
 
            parent_ie._children[basename] = ie
1992
 
        self._fully_cached = True
1993
 
 
1994
2083
    def iter_changes(self, basis):
1995
2084
        """Generate a Tree.iter_changes change list between this and basis.
1996
2085
 
2093
2182
    def path2id(self, relpath):
2094
2183
        """See CommonInventory.path2id()."""
2095
2184
        # TODO: perhaps support negative hits?
2096
 
        if isinstance(relpath, basestring):
2097
 
            names = osutils.splitpath(relpath)
2098
 
        else:
2099
 
            names = relpath
2100
 
            if relpath == []:
2101
 
                relpath = [""]
2102
 
            relpath = osutils.pathjoin(*relpath)
2103
2185
        result = self._path_to_fileid_cache.get(relpath, None)
2104
2186
        if result is not None:
2105
2187
            return result
 
2188
        if isinstance(relpath, basestring):
 
2189
            names = osutils.splitpath(relpath)
 
2190
        else:
 
2191
            names = relpath
2106
2192
        current_id = self.root_id
2107
2193
        if current_id is None:
2108
2194
            return None
2159
2245
class CHKInventoryDirectory(InventoryDirectory):
2160
2246
    """A directory in an inventory."""
2161
2247
 
2162
 
    __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']
2163
2252
 
2164
2253
    def __init__(self, file_id, name, parent_id, chk_inventory):
2165
2254
        # Don't call InventoryDirectory.__init__ - it isn't right for this
2166
2255
        # class.
2167
2256
        InventoryEntry.__init__(self, file_id, name, parent_id)
2168
2257
        self._children = None
 
2258
        self.kind = 'directory'
2169
2259
        self._chk_inventory = chk_inventory
2170
2260
 
2171
2261
    @property
2256
2346
    return name
2257
2347
 
2258
2348
 
2259
 
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
 
2349
_NAME_RE = None
2260
2350
 
2261
2351
def is_valid_name(name):
 
2352
    global _NAME_RE
 
2353
    if _NAME_RE is None:
 
2354
        _NAME_RE = re.compile(r'^[^/\\]+$')
 
2355
 
2262
2356
    return bool(_NAME_RE.match(name))
2263
2357
 
2264
2358
 
2354
2448
            raise errors.InconsistentDelta(new_path, item[1],
2355
2449
                "new_path with no entry")
2356
2450
        yield item
2357
 
 
2358
 
 
2359
 
def mutable_inventory_from_tree(tree):
2360
 
    """Create a new inventory that has the same contents as a specified tree.
2361
 
 
2362
 
    :param tree: Revision tree to create inventory from
2363
 
    """
2364
 
    entries = tree.iter_entries_by_dir()
2365
 
    inv = Inventory(None, tree.get_revision_id())
2366
 
    for path, inv_entry in entries:
2367
 
        inv.add(inv_entry.copy())
2368
 
    return inv