/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
26
28
# This should really be an id randomly assigned when the tree is
27
29
# created, but it's not for now.
28
 
ROOT_ID = b"TREE_ROOT"
29
 
 
30
 
try:
31
 
    from collections.abc import deque
32
 
except ImportError:  # python < 3.7
33
 
    from collections import deque
34
 
 
35
 
 
36
 
from ..lazy_import import lazy_import
 
30
ROOT_ID = "TREE_ROOT"
 
31
 
 
32
from bzrlib.lazy_import import lazy_import
37
33
lazy_import(globals(), """
 
34
import collections
 
35
import copy
 
36
import re
 
37
import tarfile
38
38
 
39
 
from breezy.bzr import (
 
39
from bzrlib import (
40
40
    chk_map,
 
41
    errors,
41
42
    generate_ids,
 
43
    osutils,
42
44
    )
43
45
""")
44
46
 
45
 
from .. import (
46
 
    errors,
 
47
from bzrlib import (
47
48
    lazy_regex,
48
 
    osutils,
49
49
    trace,
50
50
    )
51
 
from ..static_tuple import StaticTuple
52
 
 
53
 
 
54
 
class InvalidEntryName(errors.InternalBzrError):
55
 
 
56
 
    _fmt = "Invalid entry name: %(name)s"
57
 
 
58
 
    def __init__(self, name):
59
 
        errors.BzrError.__init__(self)
60
 
        self.name = name
61
 
 
62
 
 
63
 
class DuplicateFileId(errors.BzrError):
64
 
 
65
 
    _fmt = "File id {%(file_id)s} already exists in inventory as %(entry)s"
66
 
 
67
 
    def __init__(self, file_id, entry):
68
 
        errors.BzrError.__init__(self)
69
 
        self.file_id = file_id
70
 
        self.entry = entry
 
51
 
 
52
from bzrlib.static_tuple import StaticTuple
 
53
from bzrlib.symbol_versioning import (
 
54
    deprecated_in,
 
55
    deprecated_method,
 
56
    )
71
57
 
72
58
 
73
59
class InventoryEntry(object):
102
88
 
103
89
    >>> i = Inventory()
104
90
    >>> i.path2id('')
105
 
    b'TREE_ROOT'
106
 
    >>> i.add(InventoryDirectory(b'123', 'src', ROOT_ID))
107
 
    InventoryDirectory(b'123', 'src', parent_id=b'TREE_ROOT', revision=None)
108
 
    >>> i.add(InventoryFile(b'2323', 'hello.c', parent_id=b'123'))
109
 
    InventoryFile(b'2323', 'hello.c', parent_id=b'123', sha1=None, len=None, revision=None)
 
91
    'TREE_ROOT'
 
92
    >>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
 
93
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
 
94
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
 
95
    InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None, revision=None)
110
96
    >>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
111
97
    >>> for ix, j in enumerate(i.iter_entries()):
112
 
    ...   print(j[0] == shouldbe[ix], j[1])
 
98
    ...   print (j[0] == shouldbe[ix], j[1])
113
99
    ...
114
 
    True InventoryDirectory(b'TREE_ROOT', '', parent_id=None, revision=None)
115
 
    True InventoryDirectory(b'123', 'src', parent_id=b'TREE_ROOT', revision=None)
116
 
    True InventoryFile(b'2323', 'hello.c', parent_id=b'123', sha1=None, len=None, revision=None)
117
 
    >>> i.add(InventoryFile(b'2324', 'bye.c', b'123'))
118
 
    InventoryFile(b'2324', 'bye.c', parent_id=b'123', sha1=None, len=None, revision=None)
119
 
    >>> i.add(InventoryDirectory(b'2325', 'wibble', b'123'))
120
 
    InventoryDirectory(b'2325', 'wibble', parent_id=b'123', revision=None)
 
100
    (True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
 
101
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
 
102
    (True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None, revision=None))
 
103
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
 
104
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None, revision=None)
 
105
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
 
106
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
121
107
    >>> i.path2id('src/wibble')
122
 
    b'2325'
123
 
    >>> i.add(InventoryFile(b'2326', 'wibble.c', b'2325'))
124
 
    InventoryFile(b'2326', 'wibble.c', parent_id=b'2325', sha1=None, len=None, revision=None)
125
 
    >>> i.get_entry(b'2326')
126
 
    InventoryFile(b'2326', 'wibble.c', parent_id=b'2325', sha1=None, len=None, revision=None)
 
108
    '2325'
 
109
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
 
110
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
 
111
    >>> i['2326']
 
112
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
127
113
    >>> for path, entry in i.iter_entries():
128
 
    ...     print(path)
 
114
    ...     print path
129
115
    ...
130
116
    <BLANKLINE>
131
117
    src
133
119
    src/hello.c
134
120
    src/wibble
135
121
    src/wibble/wibble.c
136
 
    >>> i.id2path(b'2326')
 
122
    >>> i.id2path('2326')
137
123
    'src/wibble/wibble.c'
138
124
    """
139
125
 
162
148
    # Attributes that only vary for tree-references: reference_revision
163
149
    reference_revision = None
164
150
 
 
151
 
165
152
    def detect_changes(self, old_entry):
166
153
        """Return a (text_modified, meta_modified) from this to old_entry.
167
154
 
171
158
        return False, False
172
159
 
173
160
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
174
 
              output_to, reverse=False):
 
161
             output_to, reverse=False):
175
162
        """Perform a diff between two entries of the same kind."""
176
163
 
177
164
    def parent_candidates(self, previous_inventories):
187
174
        candidates = {}
188
175
        # identify candidate head revision ids.
189
176
        for inv in previous_inventories:
190
 
            try:
191
 
                ie = inv.get_entry(self.file_id)
192
 
            except errors.NoSuchId:
193
 
                pass
194
 
            else:
 
177
            if inv.has_id(self.file_id):
 
178
                ie = inv[self.file_id]
195
179
                if ie.revision in candidates:
196
180
                    # same revision value in two different inventories:
197
181
                    # correct possible inconsistencies:
225
209
        The filename must be a single component, relative to the
226
210
        parent directory; it cannot be a whole path or relative name.
227
211
 
228
 
        >>> e = InventoryFile(b'123', 'hello.c', ROOT_ID)
 
212
        >>> e = InventoryFile('123', 'hello.c', ROOT_ID)
229
213
        >>> e.name
230
214
        'hello.c'
231
215
        >>> e.file_id
232
 
        b'123'
233
 
        >>> e = InventoryFile(b'123', 'src/hello.c', ROOT_ID)
 
216
        '123'
 
217
        >>> e = InventoryFile('123', 'src/hello.c', ROOT_ID)
234
218
        Traceback (most recent call last):
235
 
        breezy.bzr.inventory.InvalidEntryName: Invalid entry name: src/hello.c
 
219
        InvalidEntryName: Invalid entry name: src/hello.c
236
220
        """
237
 
        if u'/' in name:
238
 
            raise InvalidEntryName(name=name)
239
 
        if not isinstance(file_id, bytes):
240
 
            raise TypeError(file_id)
 
221
        if '/' in name or '\\' in name:
 
222
            raise errors.InvalidEntryName(name=name)
241
223
        self.file_id = file_id
242
224
        self.revision = None
243
225
        self.name = name
249
231
 
250
232
    known_kinds = ('file', 'directory', 'symlink')
251
233
 
 
234
    def sorted_children(self):
 
235
        return sorted(self.children.items())
 
236
 
252
237
    @staticmethod
253
238
    def versionable_kind(kind):
254
239
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
333
318
                   self.parent_id,
334
319
                   self.revision))
335
320
 
336
 
    def is_unmodified(self, other):
337
 
        other_revision = getattr(other, 'revision', None)
338
 
        if other_revision is None:
339
 
            return False
340
 
        return self.revision == other.revision
341
 
 
342
321
    def __eq__(self, other):
343
322
        if other is self:
344
323
            # For the case when objects are cached
346
325
        if not isinstance(other, InventoryEntry):
347
326
            return NotImplemented
348
327
 
349
 
        return ((self.file_id == other.file_id) and
350
 
                (self.name == other.name) and
351
 
                (other.symlink_target == self.symlink_target) and
352
 
                (self.text_sha1 == other.text_sha1) and
353
 
                (self.text_size == other.text_size) and
354
 
                (self.text_id == other.text_id) and
355
 
                (self.parent_id == other.parent_id) and
356
 
                (self.kind == other.kind) and
357
 
                (self.revision == other.revision) and
358
 
                (self.executable == other.executable) and
359
 
                (self.reference_revision == other.reference_revision)
 
328
        return ((self.file_id == other.file_id)
 
329
                and (self.name == other.name)
 
330
                and (other.symlink_target == self.symlink_target)
 
331
                and (self.text_sha1 == other.text_sha1)
 
332
                and (self.text_size == other.text_size)
 
333
                and (self.text_id == other.text_id)
 
334
                and (self.parent_id == other.parent_id)
 
335
                and (self.kind == other.kind)
 
336
                and (self.revision == other.revision)
 
337
                and (self.executable == other.executable)
 
338
                and (self.reference_revision == other.reference_revision)
360
339
                )
361
340
 
362
341
    def __ne__(self, other):
413
392
        # to provide a per-fileid log. The hash of every directory content is
414
393
        # "da..." below (the sha1sum of '').
415
394
        checker.add_pending_item(rev_id,
416
 
                                 ('texts', self.file_id, self.revision), b'text',
417
 
                                 b'da39a3ee5e6b4b0d3255bfef95601890afd80709')
 
395
            ('texts', self.file_id, self.revision), 'text',
 
396
             'da39a3ee5e6b4b0d3255bfef95601890afd80709')
418
397
 
419
398
    def copy(self):
420
399
        other = InventoryDirectory(self.file_id, self.name, self.parent_id)
427
406
        super(InventoryDirectory, self).__init__(file_id, name, parent_id)
428
407
        self.children = {}
429
408
 
430
 
    def sorted_children(self):
431
 
        return sorted(self.children.items())
432
 
 
433
409
    def kind_character(self):
434
410
        """See InventoryEntry.kind_character."""
435
411
        return '/'
453
429
        """See InventoryEntry._check"""
454
430
        # TODO: check size too.
455
431
        checker.add_pending_item(tree_revision_id,
456
 
                                 ('texts', self.file_id, self.revision), b'text',
457
 
                                 self.text_sha1)
 
432
            ('texts', self.file_id, self.revision), 'text',
 
433
             self.text_sha1)
458
434
        if self.text_size is None:
459
435
            checker._report_items.append(
460
436
                'fileid {%s} in {%s} has None for text_size' % (self.file_id,
461
 
                                                                tree_revision_id))
 
437
                tree_revision_id))
462
438
 
463
439
    def copy(self):
464
440
        other = InventoryFile(self.file_id, self.name, self.parent_id)
476
452
        return text_modified, meta_modified
477
453
 
478
454
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
479
 
              output_to, reverse=False):
 
455
             output_to, reverse=False):
480
456
        """See InventoryEntry._diff."""
481
 
        from breezy.diff import DiffText
 
457
        from bzrlib.diff import DiffText
482
458
        from_file_id = self.file_id
483
459
        if to_entry:
484
460
            to_file_id = to_entry.file_id
485
 
            to_path = to_tree.id2path(to_file_id)
486
461
        else:
487
462
            to_file_id = None
488
 
            to_path = None
489
 
        if from_file_id is not None:
490
 
            from_path = tree.id2path(from_file_id)
491
 
        else:
492
 
            from_path = None
493
463
        if reverse:
494
464
            to_file_id, from_file_id = from_file_id, to_file_id
495
465
            tree, to_tree = to_tree, tree
496
466
            from_label, to_label = to_label, from_label
497
467
        differ = DiffText(tree, to_tree, output_to, 'utf-8', '', '',
498
468
                          text_diff)
499
 
        return differ.diff_text(from_path, to_path, from_label, to_label,
500
 
                                from_file_id, to_file_id)
 
469
        return differ.diff_text(from_file_id, to_file_id, from_label, to_label)
501
470
 
502
471
    def has_text(self):
503
472
        """See InventoryEntry.has_text."""
509
478
 
510
479
    def _read_tree_state(self, path, work_tree):
511
480
        """See InventoryEntry._read_tree_state."""
512
 
        self.text_sha1 = work_tree.get_file_sha1(path)
 
481
        self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
513
482
        # FIXME: 20050930 probe for the text size when getting sha1
514
483
        # in _read_tree_state
515
 
        self.executable = work_tree.is_executable(path)
 
484
        self.executable = work_tree.is_executable(self.file_id, path=path)
516
485
 
517
486
    def __repr__(self):
518
487
        return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s, revision=%s)"
557
526
        if self.symlink_target is None:
558
527
            checker._report_items.append(
559
528
                'symlink {%s} has no target in revision {%s}'
560
 
                % (self.file_id, tree_revision_id))
 
529
                    % (self.file_id, tree_revision_id))
561
530
        # Symlinks are stored as ''
562
531
        checker.add_pending_item(tree_revision_id,
563
 
                                 ('texts', self.file_id, self.revision), b'text',
564
 
                                 b'da39a3ee5e6b4b0d3255bfef95601890afd80709')
 
532
            ('texts', self.file_id, self.revision), 'text',
 
533
             'da39a3ee5e6b4b0d3255bfef95601890afd80709')
565
534
 
566
535
    def copy(self):
567
536
        other = InventoryLink(self.file_id, self.name, self.parent_id)
579
548
        return text_modified, meta_modified
580
549
 
581
550
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
582
 
              output_to, reverse=False):
 
551
             output_to, reverse=False):
583
552
        """See InventoryEntry._diff."""
584
 
        from breezy.diff import DiffSymlink
 
553
        from bzrlib.diff import DiffSymlink
585
554
        old_target = self.symlink_target
586
555
        if to_entry is not None:
587
556
            new_target = to_entry.symlink_target
603
572
 
604
573
    def _read_tree_state(self, path, work_tree):
605
574
        """See InventoryEntry._read_tree_state."""
606
 
        self.symlink_target = work_tree.get_symlink_target(
607
 
            work_tree.id2path(self.file_id), self.file_id)
 
575
        self.symlink_target = work_tree.get_symlink_target(self.file_id)
608
576
 
609
577
    def _forget_tree_state(self):
610
578
        self.symlink_target = None
637
605
        """Populate fields in the inventory entry from the given tree.
638
606
        """
639
607
        self.reference_revision = work_tree.get_reference_revision(
640
 
            path, self.file_id)
 
608
            self.file_id, path)
641
609
 
642
610
    def _forget_tree_state(self):
643
611
        self.reference_revision = None
649
617
            compatible = False
650
618
        return compatible
651
619
 
652
 
    def kind_character(self):
653
 
        """See InventoryEntry.kind_character."""
654
 
        return '+'
655
 
 
656
620
 
657
621
class CommonInventory(object):
658
622
    """Basic inventory logic, defined in terms of primitives like has_id.
676
640
        """Return as a string the path to file_id.
677
641
 
678
642
        >>> i = Inventory()
679
 
        >>> e = i.add(InventoryDirectory(b'src-id', 'src', ROOT_ID))
680
 
        >>> e = i.add(InventoryFile(b'foo-id', 'foo.c', parent_id=b'src-id'))
681
 
        >>> print(i.id2path(b'foo-id'))
 
643
        >>> e = i.add(InventoryDirectory('src-id', 'src', ROOT_ID))
 
644
        >>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
 
645
        >>> print i.id2path('foo-id')
682
646
        src/foo.c
683
647
 
684
648
        :raises NoSuchId: If file_id is not present in the inventory.
690
654
 
691
655
    def iter_entries(self, from_dir=None, recursive=True):
692
656
        """Return (path, entry) pairs, in order by name.
693
 
 
 
657
        
694
658
        :param from_dir: if None, start from the root,
695
659
          otherwise start from this directory (either file-id or entry)
696
660
        :param recursive: recurse into directories or not
700
664
                return
701
665
            from_dir = self.root
702
666
            yield '', self.root
703
 
        elif isinstance(from_dir, bytes):
704
 
            from_dir = self.get_entry(from_dir)
 
667
        elif isinstance(from_dir, basestring):
 
668
            from_dir = self[from_dir]
705
669
 
706
670
        # unrolling the recursive called changed the time from
707
671
        # 440ms/663ms (inline/total) to 116ms/116ms
708
 
        children = sorted(from_dir.children.items())
 
672
        children = from_dir.children.items()
 
673
        children.sort()
709
674
        if not recursive:
710
 
            yield from children
 
675
            for name, ie in children:
 
676
                yield name, ie
711
677
            return
712
 
        children = deque(children)
 
678
        children = collections.deque(children)
713
679
        stack = [(u'', children)]
714
680
        while stack:
715
681
            from_dir_relpath, children = stack[-1]
729
695
                    continue
730
696
 
731
697
                # But do this child first
732
 
                new_children = sorted(ie.children.items())
733
 
                new_children = deque(new_children)
 
698
                new_children = ie.children.items()
 
699
                new_children.sort()
 
700
                new_children = collections.deque(new_children)
734
701
                stack.append((path, new_children))
735
702
                # Break out of inner loop, so that we start outer loop with child
736
703
                break
740
707
 
741
708
    def _preload_cache(self):
742
709
        """Populate any caches, we are about to access all items.
743
 
 
 
710
        
744
711
        The default implementation does nothing, because CommonInventory doesn't
745
712
        have a cache.
746
713
        """
747
714
        pass
748
 
 
749
 
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
 
715
    
 
716
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
 
717
        yield_parents=False):
750
718
        """Iterate over the entries in a directory first order.
751
719
 
752
720
        This returns all entries for a directory before returning
754
722
        lexicographically sorted order, and is a hybrid between
755
723
        depth-first and breadth-first.
756
724
 
 
725
        :param yield_parents: If True, yield the parents from the root leading
 
726
            down to specific_file_ids that have been requested. This has no
 
727
            impact if specific_file_ids is None.
757
728
        :return: This yields (path, entry) pairs
758
729
        """
759
730
        if specific_file_ids and not isinstance(specific_file_ids, set):
769
740
            if self.root is None:
770
741
                return
771
742
            # Optimize a common case
772
 
            if (specific_file_ids is not None
773
 
                    and len(specific_file_ids) == 1):
 
743
            if (not yield_parents and specific_file_ids is not None and
 
744
                len(specific_file_ids) == 1):
774
745
                file_id = list(specific_file_ids)[0]
775
 
                if file_id is not None:
776
 
                    try:
777
 
                        path = self.id2path(file_id)
778
 
                    except errors.NoSuchId:
779
 
                        pass
780
 
                    else:
781
 
                        yield path, self.get_entry(file_id)
 
746
                if self.has_id(file_id):
 
747
                    yield self.id2path(file_id), self[file_id]
782
748
                return
783
749
            from_dir = self.root
784
 
            if (specific_file_ids is None
785
 
                    or self.root.file_id in specific_file_ids):
 
750
            if (specific_file_ids is None or yield_parents or
 
751
                self.root.file_id in specific_file_ids):
786
752
                yield u'', self.root
787
 
        elif isinstance(from_dir, bytes):
788
 
            from_dir = self.get_entry(from_dir)
789
 
        else:
790
 
            raise TypeError(from_dir)
 
753
        elif isinstance(from_dir, basestring):
 
754
            from_dir = self[from_dir]
791
755
 
792
756
        if specific_file_ids is not None:
793
757
            # TODO: jam 20070302 This could really be done as a loop rather
794
758
            #       than a bunch of recursive calls.
795
759
            parents = set()
796
760
            byid = self
797
 
 
798
761
            def add_ancestors(file_id):
799
762
                if not byid.has_id(file_id):
800
763
                    return
801
 
                parent_id = byid.get_entry(file_id).parent_id
 
764
                parent_id = byid[file_id].parent_id
802
765
                if parent_id is None:
803
766
                    return
804
767
                if parent_id not in parents:
814
777
            cur_relpath, cur_dir = stack.pop()
815
778
 
816
779
            child_dirs = []
817
 
            for child_name, child_ie in sorted(cur_dir.children.items()):
 
780
            for child_name, child_ie in sorted(cur_dir.children.iteritems()):
818
781
 
819
782
                child_relpath = cur_relpath + child_name
820
783
 
821
 
                if (specific_file_ids is None
822
 
                        or child_ie.file_id in specific_file_ids):
 
784
                if (specific_file_ids is None or
 
785
                    child_ie.file_id in specific_file_ids or
 
786
                    (yield_parents and child_ie.file_id in parents)):
823
787
                    yield child_relpath, child_ie
824
788
 
825
789
                if child_ie.kind == 'directory':
826
790
                    if parents is None or child_ie.file_id in parents:
827
 
                        child_dirs.append((child_relpath + '/', child_ie))
 
791
                        child_dirs.append((child_relpath+'/', child_ie))
828
792
            stack.extend(reversed(child_dirs))
829
793
 
830
794
    def _make_delta(self, old):
831
795
        """Make an inventory delta from two inventories."""
832
 
        old_ids = set(old.iter_all_ids())
833
 
        new_ids = set(self.iter_all_ids())
 
796
        old_ids = set(old)
 
797
        new_ids = set(self)
834
798
        adds = new_ids - old_ids
835
799
        deletes = old_ids - new_ids
836
800
        common = old_ids.intersection(new_ids)
838
802
        for file_id in deletes:
839
803
            delta.append((old.id2path(file_id), None, file_id, None))
840
804
        for file_id in adds:
841
 
            delta.append((None, self.id2path(file_id),
842
 
                          file_id, self.get_entry(file_id)))
 
805
            delta.append((None, self.id2path(file_id), file_id, self[file_id]))
843
806
        for file_id in common:
844
 
            if old.get_entry(file_id) != self.get_entry(file_id):
 
807
            if old[file_id] != self[file_id]:
845
808
                delta.append((old.id2path(file_id), self.id2path(file_id),
846
 
                              file_id, self.get_entry(file_id)))
 
809
                    file_id, self[file_id]))
847
810
        return delta
848
811
 
849
812
    def make_entry(self, kind, name, parent_id, file_id=None):
850
 
        """Simple thunk to breezy.bzr.inventory.make_entry."""
 
813
        """Simple thunk to bzrlib.inventory.make_entry."""
851
814
        return make_entry(kind, name, parent_id, file_id)
852
815
 
853
816
    def entries(self):
856
819
        This may be faster than iter_entries.
857
820
        """
858
821
        accum = []
859
 
 
860
822
        def descend(dir_ie, dir_path):
861
 
            kids = sorted(dir_ie.children.items())
 
823
            kids = dir_ie.children.items()
 
824
            kids.sort()
862
825
            for name, ie in kids:
863
826
                child_path = osutils.pathjoin(dir_path, name)
864
827
                accum.append((child_path, ie))
869
832
            descend(self.root, u'')
870
833
        return accum
871
834
 
872
 
    def get_entry_by_path_partial(self, relpath):
873
 
        """Like get_entry_by_path, but return TreeReference objects.
874
 
 
875
 
        :param relpath: Path to resolve, either as string with / as separators,
876
 
            or as list of elements.
877
 
        :return: tuple with ie, resolved elements and elements left to resolve
878
 
        """
879
 
        if isinstance(relpath, str):
880
 
            names = osutils.splitpath(relpath)
881
 
        else:
882
 
            names = relpath
883
 
 
884
 
        try:
885
 
            parent = self.root
886
 
        except errors.NoSuchId:
887
 
            # root doesn't exist yet so nothing else can
888
 
            return None, None, None
889
 
        if parent is None:
890
 
            return None, None, None
891
 
        for i, f in enumerate(names):
892
 
            try:
893
 
                children = getattr(parent, 'children', None)
894
 
                if children is None:
895
 
                    return None, None, None
896
 
                cie = children[f]
897
 
                if cie.kind == 'tree-reference':
898
 
                    return cie, names[:i + 1], names[i + 1:]
899
 
                parent = cie
900
 
            except KeyError:
901
 
                # or raise an error?
902
 
                return None, None, None
903
 
        return parent, names, []
904
 
 
905
 
    def get_entry_by_path(self, relpath):
906
 
        """Return an inventory entry by path.
 
835
    def path2id(self, relpath):
 
836
        """Walk down through directories to return entry of last component.
907
837
 
908
838
        :param relpath: may be either a list of path components, or a single
909
839
            string, in which case it is automatically split.
913
843
 
914
844
        Returns None IFF the path is not found.
915
845
        """
916
 
        if isinstance(relpath, str):
 
846
        if isinstance(relpath, basestring):
917
847
            names = osutils.splitpath(relpath)
918
848
        else:
919
849
            names = relpath
935
865
            except KeyError:
936
866
                # or raise an error?
937
867
                return None
938
 
        return parent
939
 
 
940
 
    def path2id(self, relpath):
941
 
        """Walk down through directories to return entry of last component.
942
 
 
943
 
        :param relpath: may be either a list of path components, or a single
944
 
            string, in which case it is automatically split.
945
 
 
946
 
        This returns the entry of the last component in the path,
947
 
        which may be either a file or a directory.
948
 
 
949
 
        Returns None IFF the path is not found.
950
 
        """
951
 
        ie = self.get_entry_by_path(relpath)
952
 
        if ie is None:
953
 
            return None
954
 
        return ie.file_id
 
868
 
 
869
        return parent.file_id
955
870
 
956
871
    def filter(self, specific_fileids):
957
872
        """Get an inventory view filtered against a set of file-ids.
971
886
        entries = self.iter_entries()
972
887
        if self.root is None:
973
888
            return Inventory(root_id=None)
974
 
        other = Inventory(next(entries)[1].file_id)
 
889
        other = Inventory(entries.next()[1].file_id)
975
890
        other.root.revision = self.root.revision
976
891
        other.revision_id = self.revision_id
977
892
        directories_to_expand = set()
978
893
        for path, entry in entries:
979
894
            file_id = entry.file_id
980
 
            if (file_id in specific_fileids or
981
 
                    entry.parent_id in directories_to_expand):
 
895
            if (file_id in specific_fileids
 
896
                or entry.parent_id in directories_to_expand):
982
897
                if entry.kind == 'directory':
983
898
                    directories_to_expand.add(file_id)
984
899
            elif file_id not in interesting_parents:
1009
924
    returned quickly.
1010
925
 
1011
926
    >>> inv = Inventory()
1012
 
    >>> inv.add(InventoryFile(b'123-123', 'hello.c', ROOT_ID))
1013
 
    InventoryFile(b'123-123', 'hello.c', parent_id=b'TREE_ROOT', sha1=None, len=None, revision=None)
1014
 
    >>> inv.get_entry(b'123-123').name
 
927
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
 
928
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
 
929
    >>> inv['123-123'].name
1015
930
    'hello.c'
1016
931
 
1017
932
    Id's may be looked up from paths:
1018
933
 
1019
934
    >>> inv.path2id('hello.c')
1020
 
    b'123-123'
1021
 
    >>> inv.has_id(b'123-123')
 
935
    '123-123'
 
936
    >>> inv.has_id('123-123')
1022
937
    True
1023
938
 
1024
939
    There are iterators over the contents:
1025
940
 
1026
941
    >>> [entry[0] for entry in inv.iter_entries()]
1027
 
    ['', 'hello.c']
 
942
    ['', u'hello.c']
1028
943
    """
1029
944
 
1030
945
    def __init__(self, root_id=ROOT_ID, revision_id=None):
1050
965
        closing = '...}'
1051
966
        contents = repr(self._byid)
1052
967
        if len(contents) > max_len:
1053
 
            contents = contents[:(max_len - len(closing))] + closing
 
968
            contents = contents[:(max_len-len(closing))] + closing
1054
969
        return "<Inventory object at %x, contents=%r>" % (id(self), contents)
1055
970
 
1056
971
    def apply_delta(self, delta):
1066
981
            applied the final inventory must be internally consistent, but it
1067
982
            is ok to supply changes which, if only half-applied would have an
1068
983
            invalid result - such as supplying two changes which rename two
1069
 
            files, 'A' and 'B' with each other : [('A', 'B', b'A-id', a_entry),
1070
 
            ('B', 'A', b'B-id', b_entry)].
 
984
            files, 'A' and 'B' with each other : [('A', 'B', 'A-id', a_entry),
 
985
            ('B', 'A', 'B-id', b_entry)].
1071
986
 
1072
987
            Each change is a tuple, of the form (old_path, new_path, file_id,
1073
988
            new_entry).
1102
1017
        # facility.
1103
1018
        list(_check_delta_unique_ids(_check_delta_unique_new_paths(
1104
1019
            _check_delta_unique_old_paths(_check_delta_ids_match_entry(
1105
 
                _check_delta_ids_are_valid(
1106
 
                    _check_delta_new_path_entry_both_or_None(
1107
 
                        delta)))))))
 
1020
            _check_delta_ids_are_valid(
 
1021
            _check_delta_new_path_entry_both_or_None(
 
1022
            delta)))))))
1108
1023
 
1109
1024
        children = {}
1110
1025
        # Remove all affected items which were in the original inventory,
1112
1027
        # after their children, which means that everything we examine has no
1113
1028
        # modified children remaining by the time we examine it.
1114
1029
        for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
1115
 
                                         if op is not None), reverse=True):
 
1030
                                        if op is not None), reverse=True):
1116
1031
            # Preserve unaltered children of file_id for later reinsertion.
1117
 
            file_id_children = getattr(self.get_entry(file_id), 'children', {})
 
1032
            file_id_children = getattr(self[file_id], 'children', {})
1118
1033
            if len(file_id_children):
1119
1034
                children[file_id] = file_id_children
1120
1035
            if self.id2path(file_id) != old_path:
1121
1036
                raise errors.InconsistentDelta(old_path, file_id,
1122
 
                                               "Entry was at wrong other path %r." % self.id2path(file_id))
 
1037
                    "Entry was at wrong other path %r." % self.id2path(file_id))
1123
1038
            # Remove file_id and the unaltered children. If file_id is not
1124
1039
            # being deleted it will be reinserted back later.
1125
1040
            self.remove_recursive_id(file_id)
1129
1044
        # the resulting inventory were also modified, are inserted after their
1130
1045
        # parents.
1131
1046
        for new_path, f, new_entry in sorted((np, f, e) for op, np, f, e in
1132
 
                                             delta if np is not None):
 
1047
                                          delta if np is not None):
1133
1048
            if new_entry.kind == 'directory':
1134
1049
                # Pop the child which to allow detection of children whose
1135
1050
                # parents were deleted and which were not reattached to a new
1136
1051
                # parent.
1137
1052
                replacement = InventoryDirectory(new_entry.file_id,
1138
 
                                                 new_entry.name, new_entry.parent_id)
 
1053
                    new_entry.name, new_entry.parent_id)
1139
1054
                replacement.revision = new_entry.revision
1140
1055
                replacement.children = children.pop(replacement.file_id, {})
1141
1056
                new_entry = replacement
1142
1057
            try:
1143
1058
                self.add(new_entry)
1144
 
            except DuplicateFileId:
 
1059
            except errors.DuplicateFileId:
1145
1060
                raise errors.InconsistentDelta(new_path, new_entry.file_id,
1146
 
                                               "New id is already present in target.")
 
1061
                    "New id is already present in target.")
1147
1062
            except AttributeError:
1148
1063
                raise errors.InconsistentDelta(new_path, new_entry.file_id,
1149
 
                                               "Parent is not a directory.")
 
1064
                    "Parent is not a directory.")
1150
1065
            if self.id2path(new_entry.file_id) != new_path:
1151
1066
                raise errors.InconsistentDelta(new_path, new_entry.file_id,
1152
 
                                               "New path is not consistent with parent path.")
 
1067
                    "New path is not consistent with parent path.")
1153
1068
        if len(children):
1154
1069
            # Get the parent id that was deleted
1155
1070
            parent_id, children = children.popitem()
1156
1071
            raise errors.InconsistentDelta("<deleted>", parent_id,
1157
 
                                           "The file id was deleted but its children were not deleted.")
 
1072
                "The file id was deleted but its children were not deleted.")
1158
1073
 
1159
1074
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1160
1075
                              propagate_caches=False):
1173
1088
        entries = self.iter_entries()
1174
1089
        if self.root is None:
1175
1090
            return Inventory(root_id=None)
1176
 
        other = Inventory(next(entries)[1].file_id)
 
1091
        other = Inventory(entries.next()[1].file_id)
1177
1092
        other.root.revision = self.root.revision
1178
1093
        # copy recursively so we know directories will be added before
1179
1094
        # their children.  There are more efficient ways than this...
1181
1096
            other.add(entry.copy())
1182
1097
        return other
1183
1098
 
1184
 
    def iter_all_ids(self):
 
1099
    def __iter__(self):
1185
1100
        """Iterate over all file-ids."""
1186
1101
        return iter(self._byid)
1187
1102
 
1188
1103
    def iter_just_entries(self):
1189
1104
        """Iterate over all entries.
1190
 
 
 
1105
        
1191
1106
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1192
1107
        and the order of entries is undefined.
1193
1108
 
1194
1109
        XXX: We may not want to merge this into bzr.dev.
1195
1110
        """
1196
1111
        if self.root is None:
1197
 
            return ()
1198
 
        return self._byid.values()
 
1112
            return
 
1113
        for _, ie in self._byid.iteritems():
 
1114
            yield ie
1199
1115
 
1200
1116
    def __len__(self):
1201
1117
        """Returns number of entries."""
1202
1118
        return len(self._byid)
1203
1119
 
1204
 
    def get_entry(self, file_id):
 
1120
    def __getitem__(self, file_id):
1205
1121
        """Return the entry for given file_id.
1206
1122
 
1207
1123
        >>> inv = Inventory()
1208
 
        >>> inv.add(InventoryFile(b'123123', 'hello.c', ROOT_ID))
1209
 
        InventoryFile(b'123123', 'hello.c', parent_id=b'TREE_ROOT', sha1=None, len=None, revision=None)
1210
 
        >>> inv.get_entry(b'123123').name
 
1124
        >>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
 
1125
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
 
1126
        >>> inv['123123'].name
1211
1127
        'hello.c'
1212
1128
        """
1213
 
        if not isinstance(file_id, bytes):
1214
 
            raise TypeError(file_id)
1215
1129
        try:
1216
1130
            return self._byid[file_id]
1217
1131
        except KeyError:
1222
1136
        return self._byid[file_id].kind
1223
1137
 
1224
1138
    def get_child(self, parent_id, filename):
1225
 
        return self.get_entry(parent_id).children.get(filename)
 
1139
        return self[parent_id].children.get(filename)
1226
1140
 
1227
1141
    def _add_child(self, entry):
1228
1142
        """Add an entry to the inventory, without adding it to its parent"""
1231
1145
                "inventory already contains entry with id {%s}" %
1232
1146
                entry.file_id)
1233
1147
        self._byid[entry.file_id] = entry
1234
 
        children = getattr(entry, 'children', {})
1235
 
        if children is not None:
1236
 
            for child in children.values():
1237
 
                self._add_child(child)
 
1148
        for child in getattr(entry, 'children', {}).itervalues():
 
1149
            self._add_child(child)
1238
1150
        return entry
1239
1151
 
1240
1152
    def add(self, entry):
1243
1155
        :return: entry
1244
1156
        """
1245
1157
        if entry.file_id in self._byid:
1246
 
            raise DuplicateFileId(entry.file_id, self._byid[entry.file_id])
 
1158
            raise errors.DuplicateFileId(entry.file_id,
 
1159
                                         self._byid[entry.file_id])
1247
1160
        if entry.parent_id is None:
1248
1161
            self.root = entry
1249
1162
        else:
1251
1164
                parent = self._byid[entry.parent_id]
1252
1165
            except KeyError:
1253
1166
                raise errors.InconsistentDelta("<unknown>", entry.parent_id,
1254
 
                                               "Parent not in inventory.")
 
1167
                    "Parent not in inventory.")
1255
1168
            if entry.name in parent.children:
1256
1169
                raise errors.InconsistentDelta(
1257
1170
                    self.id2path(parent.children[entry.name].file_id),
1283
1196
        ie = make_entry(kind, parts[-1], parent_id, file_id)
1284
1197
        return self.add(ie)
1285
1198
 
1286
 
    def delete(self, file_id):
 
1199
    def __delitem__(self, file_id):
1287
1200
        """Remove entry by id.
1288
1201
 
1289
1202
        >>> inv = Inventory()
1290
 
        >>> inv.add(InventoryFile(b'123', 'foo.c', ROOT_ID))
1291
 
        InventoryFile(b'123', 'foo.c', parent_id=b'TREE_ROOT', sha1=None, len=None, revision=None)
1292
 
        >>> inv.has_id(b'123')
 
1203
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
 
1204
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
 
1205
        >>> inv.has_id('123')
1293
1206
        True
1294
 
        >>> inv.delete(b'123')
1295
 
        >>> inv.has_id(b'123')
 
1207
        >>> del inv['123']
 
1208
        >>> inv.has_id('123')
1296
1209
        False
1297
1210
        """
1298
 
        ie = self.get_entry(file_id)
 
1211
        ie = self[file_id]
1299
1212
        del self._byid[file_id]
1300
1213
        if ie.parent_id is not None:
1301
 
            del self.get_entry(ie.parent_id).children[ie.name]
 
1214
            del self[ie.parent_id].children[ie.name]
1302
1215
 
1303
1216
    def __eq__(self, other):
1304
1217
        """Compare two sets by comparing their contents.
1307
1220
        >>> i2 = Inventory()
1308
1221
        >>> i1 == i2
1309
1222
        True
1310
 
        >>> i1.add(InventoryFile(b'123', 'foo', ROOT_ID))
1311
 
        InventoryFile(b'123', 'foo', parent_id=b'TREE_ROOT', sha1=None, len=None, revision=None)
 
1223
        >>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
 
1224
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1312
1225
        >>> i1 == i2
1313
1226
        False
1314
 
        >>> i2.add(InventoryFile(b'123', 'foo', ROOT_ID))
1315
 
        InventoryFile(b'123', 'foo', parent_id=b'TREE_ROOT', sha1=None, len=None, revision=None)
 
1227
        >>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
 
1228
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1316
1229
        >>> i1 == i2
1317
1230
        True
1318
1231
        """
1342
1255
 
1343
1256
    def _make_delta(self, old):
1344
1257
        """Make an inventory delta from two inventories."""
1345
 
        old_getter = old.get_entry
1346
 
        new_getter = self.get_entry
1347
 
        old_ids = set(old.iter_all_ids())
1348
 
        new_ids = set(self.iter_all_ids())
 
1258
        old_getter = getattr(old, '_byid', old)
 
1259
        new_getter = self._byid
 
1260
        old_ids = set(old_getter)
 
1261
        new_ids = set(new_getter)
1349
1262
        adds = new_ids - old_ids
1350
1263
        deletes = old_ids - new_ids
1351
1264
        if not adds and not deletes:
1356
1269
        for file_id in deletes:
1357
1270
            delta.append((old.id2path(file_id), None, file_id, None))
1358
1271
        for file_id in adds:
1359
 
            delta.append((None, self.id2path(file_id),
1360
 
                          file_id, self.get_entry(file_id)))
 
1272
            delta.append((None, self.id2path(file_id), file_id, self[file_id]))
1361
1273
        for file_id in common:
1362
 
            new_ie = new_getter(file_id)
1363
 
            old_ie = old_getter(file_id)
 
1274
            new_ie = new_getter[file_id]
 
1275
            old_ie = old_getter[file_id]
1364
1276
            # If xml_serializer returns the cached InventoryEntries (rather
1365
1277
            # than always doing .copy()), inlining the 'is' check saves 2.7M
1366
1278
            # calls to __eq__.  Under lsprof this saves 20s => 6s.
1385
1297
            if ie.kind == 'directory':
1386
1298
                to_find_delete.extend(ie.children.values())
1387
1299
        for file_id in reversed(to_delete):
1388
 
            ie = self.get_entry(file_id)
 
1300
            ie = self[file_id]
1389
1301
            del self._byid[file_id]
1390
1302
        if ie.parent_id is not None:
1391
 
            del self.get_entry(ie.parent_id).children[ie.name]
 
1303
            del self[ie.parent_id].children[ie.name]
1392
1304
        else:
1393
1305
            self.root = None
1394
1306
 
1406
1318
        new_parent = self._byid[new_parent_id]
1407
1319
        if new_name in new_parent.children:
1408
1320
            raise errors.BzrError("%r already exists in %r" %
1409
 
                                  (new_name, self.id2path(new_parent_id)))
 
1321
                (new_name, self.id2path(new_parent_id)))
1410
1322
 
1411
1323
        new_parent_idpath = self.get_idpath(new_parent_id)
1412
1324
        if file_id in new_parent_idpath:
1413
1325
            raise errors.BzrError(
1414
1326
                "cannot move directory %r into a subdirectory of itself, %r"
1415
 
                % (self.id2path(file_id), self.id2path(new_parent_id)))
 
1327
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1416
1328
 
1417
1329
        file_ie = self._byid[file_id]
1418
1330
        old_parent = self._byid[file_ie.parent_id]
1489
1401
        if entry.parent_id is not None:
1490
1402
            parent_str = entry.parent_id
1491
1403
        else:
1492
 
            parent_str = b''
 
1404
            parent_str = ''
1493
1405
        name_str = entry.name.encode("utf8")
1494
1406
        if entry.kind == 'file':
1495
1407
            if entry.executable:
1496
 
                exec_str = b"Y"
 
1408
                exec_str = "Y"
1497
1409
            else:
1498
 
                exec_str = b"N"
1499
 
            return b"file: %s\n%s\n%s\n%s\n%s\n%d\n%s" % (
 
1410
                exec_str = "N"
 
1411
            return "file: %s\n%s\n%s\n%s\n%s\n%d\n%s" % (
1500
1412
                entry.file_id, parent_str, name_str, entry.revision,
1501
1413
                entry.text_sha1, entry.text_size, exec_str)
1502
1414
        elif entry.kind == 'directory':
1503
 
            return b"dir: %s\n%s\n%s\n%s" % (
 
1415
            return "dir: %s\n%s\n%s\n%s" % (
1504
1416
                entry.file_id, parent_str, name_str, entry.revision)
1505
1417
        elif entry.kind == 'symlink':
1506
 
            return b"symlink: %s\n%s\n%s\n%s\n%s" % (
 
1418
            return "symlink: %s\n%s\n%s\n%s\n%s" % (
1507
1419
                entry.file_id, parent_str, name_str, entry.revision,
1508
1420
                entry.symlink_target.encode("utf8"))
1509
1421
        elif entry.kind == 'tree-reference':
1510
 
            return b"tree: %s\n%s\n%s\n%s\n%s" % (
 
1422
            return "tree: %s\n%s\n%s\n%s\n%s" % (
1511
1423
                entry.file_id, parent_str, name_str, entry.revision,
1512
1424
                entry.reference_revision)
1513
1425
        else:
1531
1443
 
1532
1444
        if given [foo-id] we will include
1533
1445
            TREE_ROOT as interesting parents
1534
 
        and
 
1446
        and 
1535
1447
            foo-id, baz-id, frob-id, fringle-id
1536
1448
        As interesting ids.
1537
1449
        """
1555
1467
        remaining_parents = interesting.difference(file_ids)
1556
1468
        # When we hit the TREE_ROOT, we'll get an interesting parent of None,
1557
1469
        # but we don't actually want to recurse into that
1558
 
        interesting.add(None)  # this will auto-filter it in the loop
1559
 
        remaining_parents.discard(None)
 
1470
        interesting.add(None) # this will auto-filter it in the loop
 
1471
        remaining_parents.discard(None) 
1560
1472
        while remaining_parents:
1561
1473
            next_parents = set()
1562
1474
            for entry in self._getitems(remaining_parents):
1575
1487
            keys = [StaticTuple(f,).intern() for f in directories_to_expand]
1576
1488
            directories_to_expand = set()
1577
1489
            items = self.parent_id_basename_to_file_id.iteritems(keys)
1578
 
            next_file_ids = {item[1] for item in items}
 
1490
            next_file_ids = set([item[1] for item in items])
1579
1491
            next_file_ids = next_file_ids.difference(interesting)
1580
1492
            interesting.update(next_file_ids)
1581
1493
            for entry in self._getitems(next_file_ids):
1595
1507
        """
1596
1508
        (interesting,
1597
1509
         parent_to_children) = self._expand_fileids_to_parents_and_children(
1598
 
            specific_fileids)
 
1510
                                specific_fileids)
1599
1511
        # There is some overlap here, but we assume that all interesting items
1600
1512
        # are in the _fileid_to_entry_cache because we had to read them to
1601
1513
        # determine if they were a dir we wanted to recurse, or just a file
1610
1522
            # parent_to_children with at least the tree root.)
1611
1523
            return other
1612
1524
        cache = self._fileid_to_entry_cache
1613
 
        remaining_children = deque(
1614
 
            parent_to_children[self.root_id])
 
1525
        remaining_children = collections.deque(parent_to_children[self.root_id])
1615
1526
        while remaining_children:
1616
1527
            file_id = remaining_children.popleft()
1617
1528
            ie = cache[file_id]
1618
1529
            if ie.kind == 'directory':
1619
 
                ie = ie.copy()  # We create a copy to depopulate the .children attribute
 
1530
                ie = ie.copy() # We create a copy to depopulate the .children attribute
1620
1531
            # TODO: depending on the uses of 'other' we should probably alwyas
1621
1532
            #       '.copy()' to prevent someone from mutating other and
1622
1533
            #       invaliding our internal cache
1626
1537
        return other
1627
1538
 
1628
1539
    @staticmethod
1629
 
    def _bytes_to_utf8name_key(data):
1630
 
        """Get the file_id, revision_id key out of data."""
 
1540
    def _bytes_to_utf8name_key(bytes):
 
1541
        """Get the file_id, revision_id key out of bytes."""
1631
1542
        # We don't normally care about name, except for times when we want
1632
1543
        # to filter out empty names because of non rich-root...
1633
 
        sections = data.split(b'\n')
1634
 
        kind, file_id = sections[0].split(b': ')
1635
 
        return (sections[2], file_id, sections[3])
 
1544
        sections = bytes.split('\n')
 
1545
        kind, file_id = sections[0].split(': ')
 
1546
        return (sections[2], intern(file_id), intern(sections[3]))
1636
1547
 
1637
1548
    def _bytes_to_entry(self, bytes):
1638
1549
        """Deserialise a serialised entry."""
1639
 
        sections = bytes.split(b'\n')
1640
 
        if sections[0].startswith(b"file: "):
 
1550
        sections = bytes.split('\n')
 
1551
        if sections[0].startswith("file: "):
1641
1552
            result = InventoryFile(sections[0][6:],
1642
 
                                   sections[2].decode('utf8'),
1643
 
                                   sections[1])
 
1553
                sections[2].decode('utf8'),
 
1554
                sections[1])
1644
1555
            result.text_sha1 = sections[4]
1645
1556
            result.text_size = int(sections[5])
1646
 
            result.executable = sections[6] == b"Y"
1647
 
        elif sections[0].startswith(b"dir: "):
 
1557
            result.executable = sections[6] == "Y"
 
1558
        elif sections[0].startswith("dir: "):
1648
1559
            result = CHKInventoryDirectory(sections[0][5:],
1649
 
                                           sections[2].decode('utf8'),
1650
 
                                           sections[1], self)
1651
 
        elif sections[0].startswith(b"symlink: "):
 
1560
                sections[2].decode('utf8'),
 
1561
                sections[1], self)
 
1562
        elif sections[0].startswith("symlink: "):
1652
1563
            result = InventoryLink(sections[0][9:],
1653
 
                                   sections[2].decode('utf8'),
1654
 
                                   sections[1])
 
1564
                sections[2].decode('utf8'),
 
1565
                sections[1])
1655
1566
            result.symlink_target = sections[4].decode('utf8')
1656
 
        elif sections[0].startswith(b"tree: "):
 
1567
        elif sections[0].startswith("tree: "):
1657
1568
            result = TreeReference(sections[0][6:],
1658
 
                                   sections[2].decode('utf8'),
1659
 
                                   sections[1])
 
1569
                sections[2].decode('utf8'),
 
1570
                sections[1])
1660
1571
            result.reference_revision = sections[4]
1661
1572
        else:
1662
1573
            raise ValueError("Not a serialised entry %r" % bytes)
1663
 
        result.file_id = result.file_id
1664
 
        result.revision = sections[3]
1665
 
        if result.parent_id == b'':
 
1574
        result.file_id = intern(result.file_id)
 
1575
        result.revision = intern(sections[3])
 
1576
        if result.parent_id == '':
1666
1577
            result.parent_id = None
1667
1578
        self._fileid_to_entry_cache[result.file_id] = result
1668
1579
        return result
1669
1580
 
1670
1581
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1671
 
                              propagate_caches=False):
 
1582
        propagate_caches=False):
1672
1583
        """Create a new CHKInventory by applying inventory_delta to this one.
1673
1584
 
1674
1585
        See the inventory developers documentation for the theory behind
1685
1596
        result = CHKInventory(self._search_key_name)
1686
1597
        if propagate_caches:
1687
1598
            # Just propagate the path-to-fileid cache for now
1688
 
            result._path_to_fileid_cache = self._path_to_fileid_cache.copy()
1689
 
        search_key_func = chk_map.search_key_registry.get(
1690
 
            self._search_key_name)
 
1599
            result._path_to_fileid_cache = dict(self._path_to_fileid_cache.iteritems())
 
1600
        search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1691
1601
        self.id_to_entry._ensure_root()
1692
1602
        maximum_size = self.id_to_entry._root_node.maximum_size
1693
1603
        result.revision_id = new_revision_id
1770
1680
                old_key = StaticTuple(file_id,)
1771
1681
                if self.id2path(file_id) != old_path:
1772
1682
                    raise errors.InconsistentDelta(old_path, file_id,
1773
 
                                                   "Entry was at wrong other path %r." %
1774
 
                                                   self.id2path(file_id))
 
1683
                        "Entry was at wrong other path %r." %
 
1684
                        self.id2path(file_id))
1775
1685
                altered.add(file_id)
1776
1686
            id_to_entry_delta.append(StaticTuple(old_key, new_key, new_value))
1777
1687
            if result.parent_id_basename_to_file_id is not None:
1779
1689
                if old_path is None:
1780
1690
                    old_key = None
1781
1691
                else:
1782
 
                    old_entry = self.get_entry(file_id)
 
1692
                    old_entry = self[file_id]
1783
1693
                    old_key = self._parent_id_basename_key(old_entry)
1784
1694
                if new_path is None:
1785
1695
                    new_key = None
1800
1710
                            new_key, [None, None])[1] = new_value
1801
1711
        # validate that deletes are complete.
1802
1712
        for file_id in deletes:
1803
 
            entry = self.get_entry(file_id)
 
1713
            entry = self[file_id]
1804
1714
            if entry.kind != 'directory':
1805
1715
                continue
1806
1716
            # This loop could potentially be better by using the id_basename
1808
1718
            for child in entry.children.values():
1809
1719
                if child.file_id not in altered:
1810
1720
                    raise errors.InconsistentDelta(self.id2path(child.file_id),
1811
 
                                                   child.file_id, "Child not deleted or reparented when "
1812
 
                                                   "parent deleted.")
 
1721
                        child.file_id, "Child not deleted or reparented when "
 
1722
                        "parent deleted.")
1813
1723
        result.id_to_entry.apply_delta(id_to_entry_delta)
1814
1724
        if parent_id_basename_delta:
1815
1725
            # Transform the parent_id_basename delta data into a linear delta
1817
1727
            # re-keying, but its simpler to just output that as a delete+add
1818
1728
            # to spend less time calculating the delta.
1819
1729
            delta_list = []
1820
 
            for key, (old_key, value) in parent_id_basename_delta.items():
 
1730
            for key, (old_key, value) in parent_id_basename_delta.iteritems():
1821
1731
                if value is not None:
1822
1732
                    delta_list.append((old_key, key, value))
1823
1733
                else:
1826
1736
        parents.discard(('', None))
1827
1737
        for parent_path, parent in parents:
1828
1738
            try:
1829
 
                if result.get_entry(parent).kind != 'directory':
 
1739
                if result[parent].kind != 'directory':
1830
1740
                    raise errors.InconsistentDelta(result.id2path(parent), parent,
1831
 
                                                   'Not a directory, but given children')
 
1741
                        'Not a directory, but given children')
1832
1742
            except errors.NoSuchId:
1833
1743
                raise errors.InconsistentDelta("<unknown>", parent,
1834
 
                                               "Parent is not present in resulting inventory.")
 
1744
                    "Parent is not present in resulting inventory.")
1835
1745
            if result.path2id(parent_path) != parent:
1836
1746
                raise errors.InconsistentDelta(parent_path, parent,
1837
 
                                               "Parent has wrong path %r." % result.path2id(parent_path))
 
1747
                    "Parent has wrong path %r." % result.path2id(parent_path))
1838
1748
        return result
1839
1749
 
1840
1750
    @classmethod
1841
 
    def deserialise(klass, chk_store, lines, expected_revision_id):
 
1751
    def deserialise(klass, chk_store, bytes, expected_revision_id):
1842
1752
        """Deserialise a CHKInventory.
1843
1753
 
1844
1754
        :param chk_store: A CHK capable VersionedFiles instance.
1847
1757
            for.
1848
1758
        :return: A CHKInventory
1849
1759
        """
1850
 
        if not lines[-1].endswith(b'\n'):
1851
 
            raise ValueError("last line should have trailing eol\n")
1852
 
        if lines[0] != b'chkinventory:\n':
 
1760
        lines = bytes.split('\n')
 
1761
        if lines[-1] != '':
 
1762
            raise AssertionError('bytes to deserialize must end with an eol')
 
1763
        lines.pop()
 
1764
        if lines[0] != 'chkinventory:':
1853
1765
            raise ValueError("not a serialised CHKInventory: %r" % bytes)
1854
1766
        info = {}
1855
 
        allowed_keys = frozenset((b'root_id', b'revision_id',
1856
 
                                  b'parent_id_basename_to_file_id',
1857
 
                                  b'search_key_name', b'id_to_entry'))
 
1767
        allowed_keys = frozenset(['root_id', 'revision_id', 'search_key_name',
 
1768
                                  'parent_id_basename_to_file_id',
 
1769
                                  'id_to_entry'])
1858
1770
        for line in lines[1:]:
1859
 
            key, value = line.rstrip(b'\n').split(b': ', 1)
 
1771
            key, value = line.split(': ', 1)
1860
1772
            if key not in allowed_keys:
1861
1773
                raise errors.BzrError('Unknown key in inventory: %r\n%r'
1862
1774
                                      % (key, bytes))
1864
1776
                raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1865
1777
                                      % (key, bytes))
1866
1778
            info[key] = value
1867
 
        revision_id = info[b'revision_id']
1868
 
        root_id = info[b'root_id']
1869
 
        search_key_name = info.get(b'search_key_name', b'plain')
1870
 
        parent_id_basename_to_file_id = info.get(
1871
 
            b'parent_id_basename_to_file_id', None)
1872
 
        if not parent_id_basename_to_file_id.startswith(b'sha1:'):
 
1779
        revision_id = intern(info['revision_id'])
 
1780
        root_id = intern(info['root_id'])
 
1781
        search_key_name = intern(info.get('search_key_name', 'plain'))
 
1782
        parent_id_basename_to_file_id = intern(info.get(
 
1783
            'parent_id_basename_to_file_id', None))
 
1784
        if not parent_id_basename_to_file_id.startswith('sha1:'):
1873
1785
            raise ValueError('parent_id_basename_to_file_id should be a sha1'
1874
1786
                             ' key not %r' % (parent_id_basename_to_file_id,))
1875
 
        id_to_entry = info[b'id_to_entry']
1876
 
        if not id_to_entry.startswith(b'sha1:'):
 
1787
        id_to_entry = info['id_to_entry']
 
1788
        if not id_to_entry.startswith('sha1:'):
1877
1789
            raise ValueError('id_to_entry should be a sha1'
1878
1790
                             ' key not %r' % (id_to_entry,))
1879
1791
 
1881
1793
        result.revision_id = revision_id
1882
1794
        result.root_id = root_id
1883
1795
        search_key_func = chk_map.search_key_registry.get(
1884
 
            result._search_key_name)
 
1796
                            result._search_key_name)
1885
1797
        if parent_id_basename_to_file_id is not None:
1886
1798
            result.parent_id_basename_to_file_id = chk_map.CHKMap(
1887
1799
                chk_store, StaticTuple(parent_id_basename_to_file_id,),
1894
1806
                                            search_key_func=search_key_func)
1895
1807
        if (result.revision_id,) != expected_revision_id:
1896
1808
            raise ValueError("Mismatched revision id and expected: %r, %r" %
1897
 
                             (result.revision_id, expected_revision_id))
 
1809
                (result.revision_id, expected_revision_id))
1898
1810
        return result
1899
1811
 
1900
1812
    @classmethod
1901
 
    def from_inventory(klass, chk_store, inventory, maximum_size=0, search_key_name=b'plain'):
 
1813
    def from_inventory(klass, chk_store, inventory, maximum_size=0, search_key_name='plain'):
1902
1814
        """Create a CHKInventory from an existing inventory.
1903
1815
 
1904
1816
        The content of inventory is copied into the chk_store, and a
1924
1836
            parent_id_basename_dict[p_id_key] = entry.file_id
1925
1837
 
1926
1838
        result._populate_from_dicts(chk_store, id_to_entry_dict,
1927
 
                                    parent_id_basename_dict, maximum_size=maximum_size)
 
1839
            parent_id_basename_dict, maximum_size=maximum_size)
1928
1840
        return result
1929
1841
 
1930
1842
    def _populate_from_dicts(self, chk_store, id_to_entry_dict,
1931
1843
                             parent_id_basename_dict, maximum_size):
1932
 
        search_key_func = chk_map.search_key_registry.get(
1933
 
            self._search_key_name)
 
1844
        search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1934
1845
        root_key = chk_map.CHKMap.from_dict(chk_store, id_to_entry_dict,
1935
 
                                            maximum_size=maximum_size, key_width=1,
1936
 
                                            search_key_func=search_key_func)
 
1846
                   maximum_size=maximum_size, key_width=1,
 
1847
                   search_key_func=search_key_func)
1937
1848
        self.id_to_entry = chk_map.CHKMap(chk_store, root_key,
1938
1849
                                          search_key_func)
1939
1850
        root_key = chk_map.CHKMap.from_dict(chk_store,
1940
 
                                            parent_id_basename_dict,
1941
 
                                            maximum_size=maximum_size, key_width=2,
1942
 
                                            search_key_func=search_key_func)
 
1851
                   parent_id_basename_dict,
 
1852
                   maximum_size=maximum_size, key_width=2,
 
1853
                   search_key_func=search_key_func)
1943
1854
        self.parent_id_basename_to_file_id = chk_map.CHKMap(chk_store,
1944
 
                                                            root_key, search_key_func)
 
1855
                                                    root_key, search_key_func)
1945
1856
 
1946
1857
    def _parent_id_basename_key(self, entry):
1947
1858
        """Create a key for a entry in a parent_id_basename_to_file_id index."""
1948
1859
        if entry.parent_id is not None:
1949
1860
            parent_id = entry.parent_id
1950
1861
        else:
1951
 
            parent_id = b''
 
1862
            parent_id = ''
1952
1863
        return StaticTuple(parent_id, entry.name.encode('utf8')).intern()
1953
1864
 
1954
 
    def get_entry(self, file_id):
 
1865
    def __getitem__(self, file_id):
1955
1866
        """map a single file_id -> InventoryEntry."""
1956
1867
        if file_id is None:
1957
1868
            raise errors.NoSuchId(self, file_id)
1960
1871
            return result
1961
1872
        try:
1962
1873
            return self._bytes_to_entry(
1963
 
                next(self.id_to_entry.iteritems([StaticTuple(file_id,)]))[1])
 
1874
                self.id_to_entry.iteritems([StaticTuple(file_id,)]).next()[1])
1964
1875
        except StopIteration:
1965
1876
            # really we're passing an inventory, not a tree...
1966
1877
            raise errors.NoSuchId(self, file_id)
1967
1878
 
1968
1879
    def _getitems(self, file_ids):
1969
 
        """Similar to get_entry, but lets you query for multiple.
1970
 
 
 
1880
        """Similar to __getitem__, but lets you query for multiple.
 
1881
        
1971
1882
        The returned order is undefined. And currently if an item doesn't
1972
1883
        exist, it isn't included in the output.
1973
1884
        """
2000
1911
        """Yield the parents of file_id up to the root."""
2001
1912
        while file_id is not None:
2002
1913
            try:
2003
 
                ie = self.get_entry(file_id)
 
1914
                ie = self[file_id]
2004
1915
            except KeyError:
2005
1916
                raise errors.NoSuchId(tree=self, file_id=file_id)
2006
1917
            yield ie
2007
1918
            file_id = ie.parent_id
2008
1919
 
2009
 
    def iter_all_ids(self):
 
1920
    def __iter__(self):
2010
1921
        """Iterate over all file-ids."""
2011
1922
        for key, _ in self.id_to_entry.iteritems():
2012
1923
            yield key[-1]
2030
1941
    def _preload_cache(self):
2031
1942
        """Make sure all file-ids are in _fileid_to_entry_cache"""
2032
1943
        if self._fully_cached:
2033
 
            return  # No need to do it again
 
1944
            return # No need to do it again
2034
1945
        # The optimal sort order is to use iteritems() directly
2035
1946
        cache = self._fileid_to_entry_cache
2036
1947
        for key, entry in self.id_to_entry.iteritems():
2043
1954
        last_parent_id = last_parent_ie = None
2044
1955
        pid_items = self.parent_id_basename_to_file_id.iteritems()
2045
1956
        for key, child_file_id in pid_items:
2046
 
            if key == (b'', b''):  # This is the root
 
1957
            if key == ('', ''): # This is the root
2047
1958
                if child_file_id != self.root_id:
2048
1959
                    raise ValueError('Data inconsistency detected.'
2049
 
                                     ' We expected data with key ("","") to match'
2050
 
                                     ' the root id, but %s != %s'
2051
 
                                     % (child_file_id, self.root_id))
 
1960
                        ' We expected data with key ("","") to match'
 
1961
                        ' the root id, but %s != %s'
 
1962
                        % (child_file_id, self.root_id))
2052
1963
                continue
2053
1964
            parent_id, basename = key
2054
1965
            ie = cache[child_file_id]
2058
1969
                parent_ie = cache[parent_id]
2059
1970
            if parent_ie.kind != 'directory':
2060
1971
                raise ValueError('Data inconsistency detected.'
2061
 
                                 ' An entry in the parent_id_basename_to_file_id map'
2062
 
                                 ' has parent_id {%s} but the kind of that object'
2063
 
                                 ' is %r not "directory"' % (parent_id, parent_ie.kind))
 
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))
2064
1975
            if parent_ie._children is None:
2065
1976
                parent_ie._children = {}
2066
1977
            basename = basename.decode('utf-8')
2068
1979
                existing_ie = parent_ie._children[basename]
2069
1980
                if existing_ie != ie:
2070
1981
                    raise ValueError('Data inconsistency detected.'
2071
 
                                     ' Two entries with basename %r were found'
2072
 
                                     ' in the parent entry {%s}'
2073
 
                                     % (basename, parent_id))
 
1982
                        ' Two entries with basename %r were found'
 
1983
                        ' in the parent entry {%s}'
 
1984
                        % (basename, parent_id))
2074
1985
            if basename != ie.name:
2075
1986
                raise ValueError('Data inconsistency detected.'
2076
 
                                 ' In the parent_id_basename_to_file_id map, file_id'
2077
 
                                 ' {%s} is listed as having basename %r, but in the'
2078
 
                                 ' id_to_entry map it is %r'
2079
 
                                 % (child_file_id, basename, ie.name))
 
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))
2080
1991
            parent_ie._children[basename] = ie
2081
1992
        self._fully_cached = True
2082
1993
 
2091
2002
        # changed_content, versioned, parent, name, kind,
2092
2003
        # executable)
2093
2004
        for key, basis_value, self_value in \
2094
 
                self.id_to_entry.iter_changes(basis.id_to_entry):
 
2005
            self.id_to_entry.iter_changes(basis.id_to_entry):
2095
2006
            file_id = key[0]
2096
2007
            if basis_value is not None:
2097
2008
                basis_entry = basis._bytes_to_entry(basis_value)
2130
2041
            if kind[0] != kind[1]:
2131
2042
                changed_content = True
2132
2043
            elif kind[0] == 'file':
2133
 
                if (self_entry.text_size != basis_entry.text_size
2134
 
                        or self_entry.text_sha1 != basis_entry.text_sha1):
 
2044
                if (self_entry.text_size != basis_entry.text_size or
 
2045
                    self_entry.text_sha1 != basis_entry.text_sha1):
2135
2046
                    changed_content = True
2136
2047
            elif kind[0] == 'symlink':
2137
2048
                if self_entry.symlink_target != basis_entry.symlink_target:
2138
2049
                    changed_content = True
2139
2050
            elif kind[0] == 'tree-reference':
2140
 
                if (self_entry.reference_revision
2141
 
                        != basis_entry.reference_revision):
 
2051
                if (self_entry.reference_revision !=
 
2052
                    basis_entry.reference_revision):
2142
2053
                    changed_content = True
2143
2054
            parent = (basis_parent, self_parent)
2144
2055
            name = (basis_name, self_name)
2145
2056
            executable = (basis_executable, self_executable)
2146
 
            if (not changed_content and
2147
 
                parent[0] == parent[1] and
2148
 
                name[0] == name[1] and
2149
 
                    executable[0] == executable[1]):
 
2057
            if (not changed_content
 
2058
                and parent[0] == parent[1]
 
2059
                and name[0] == name[1]
 
2060
                and executable[0] == executable[1]):
2150
2061
                # Could happen when only the revision changed for a directory
2151
2062
                # for instance.
2152
2063
                continue
2153
 
            yield (
2154
 
                file_id, (path_in_source, path_in_target), changed_content,
 
2064
            yield (file_id, (path_in_source, path_in_target), changed_content,
2155
2065
                versioned, parent, name, kind, executable)
2156
2066
 
2157
2067
    def __len__(self):
2160
2070
 
2161
2071
    def _make_delta(self, old):
2162
2072
        """Make an inventory delta from two inventories."""
2163
 
        if not isinstance(old, CHKInventory):
 
2073
        if type(old) != CHKInventory:
2164
2074
            return CommonInventory._make_delta(self, old)
2165
2075
        delta = []
2166
2076
        for key, old_value, self_value in \
2167
 
                self.id_to_entry.iter_changes(old.id_to_entry):
 
2077
            self.id_to_entry.iter_changes(old.id_to_entry):
2168
2078
            file_id = key[0]
2169
2079
            if old_value is not None:
2170
2080
                old_path = old.id2path(file_id)
2183
2093
    def path2id(self, relpath):
2184
2094
        """See CommonInventory.path2id()."""
2185
2095
        # TODO: perhaps support negative hits?
2186
 
        if isinstance(relpath, str):
 
2096
        if isinstance(relpath, basestring):
2187
2097
            names = osutils.splitpath(relpath)
2188
2098
        else:
2189
2099
            names = relpath
2211
2121
                for (parent_id, name_utf8), file_id in items:
2212
2122
                    if parent_id != current_id or name_utf8 != basename_utf8:
2213
2123
                        raise errors.BzrError("corrupt inventory lookup! "
2214
 
                                              "%r %r %r %r" % (parent_id, current_id, name_utf8,
2215
 
                                                               basename_utf8))
 
2124
                            "%r %r %r %r" % (parent_id, current_id, name_utf8,
 
2125
                            basename_utf8))
2216
2126
                if file_id is None:
2217
2127
                    return None
2218
2128
                else:
2222
2132
 
2223
2133
    def to_lines(self):
2224
2134
        """Serialise the inventory to lines."""
2225
 
        lines = [b"chkinventory:\n"]
2226
 
        if self._search_key_name != b'plain':
 
2135
        lines = ["chkinventory:\n"]
 
2136
        if self._search_key_name != 'plain':
2227
2137
            # custom ordering grouping things that don't change together
2228
 
            lines.append(b'search_key_name: %s\n' % (
2229
 
                self._search_key_name))
2230
 
            lines.append(b"root_id: %s\n" % self.root_id)
2231
 
            lines.append(b'parent_id_basename_to_file_id: %s\n' %
2232
 
                         (self.parent_id_basename_to_file_id.key()[0],))
2233
 
            lines.append(b"revision_id: %s\n" % self.revision_id)
2234
 
            lines.append(b"id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
 
2138
            lines.append('search_key_name: %s\n' % (self._search_key_name,))
 
2139
            lines.append("root_id: %s\n" % self.root_id)
 
2140
            lines.append('parent_id_basename_to_file_id: %s\n' %
 
2141
                (self.parent_id_basename_to_file_id.key()[0],))
 
2142
            lines.append("revision_id: %s\n" % self.revision_id)
 
2143
            lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2235
2144
        else:
2236
 
            lines.append(b"revision_id: %s\n" % self.revision_id)
2237
 
            lines.append(b"root_id: %s\n" % self.root_id)
 
2145
            lines.append("revision_id: %s\n" % self.revision_id)
 
2146
            lines.append("root_id: %s\n" % self.root_id)
2238
2147
            if self.parent_id_basename_to_file_id is not None:
2239
 
                lines.append(b'parent_id_basename_to_file_id: %s\n' %
2240
 
                             (self.parent_id_basename_to_file_id.key()[0],))
2241
 
            lines.append(b"id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
 
2148
                lines.append('parent_id_basename_to_file_id: %s\n' %
 
2149
                    (self.parent_id_basename_to_file_id.key()[0],))
 
2150
            lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2242
2151
        return lines
2243
2152
 
2244
2153
    @property
2245
2154
    def root(self):
2246
2155
        """Get the root entry."""
2247
 
        return self.get_entry(self.root_id)
 
2156
        return self[self.root_id]
2248
2157
 
2249
2158
 
2250
2159
class CHKInventoryDirectory(InventoryDirectory):
2274
2183
        # No longer supported
2275
2184
        if self._chk_inventory.parent_id_basename_to_file_id is None:
2276
2185
            raise AssertionError("Inventories without"
2277
 
                                 " parent_id_basename_to_file_id are no longer supported")
 
2186
                " parent_id_basename_to_file_id are no longer supported")
2278
2187
        result = {}
2279
2188
        # XXX: Todo - use proxy objects for the children rather than loading
2280
2189
        # all when the attribute is referenced.
2281
2190
        parent_id_index = self._chk_inventory.parent_id_basename_to_file_id
2282
2191
        child_keys = set()
2283
2192
        for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2284
 
                key_filter=[StaticTuple(self.file_id,)]):
 
2193
            key_filter=[StaticTuple(self.file_id,)]):
2285
2194
            child_keys.add(StaticTuple(file_id,))
2286
2195
        cached = set()
2287
2196
        for file_id_key in child_keys:
2300
2209
        self._children = result
2301
2210
        return result
2302
2211
 
2303
 
 
2304
2212
entry_factory = {
2305
2213
    'directory': InventoryDirectory,
2306
2214
    'file': InventoryFile,
2308
2216
    'tree-reference': TreeReference
2309
2217
}
2310
2218
 
2311
 
 
2312
2219
def make_entry(kind, name, parent_id, file_id=None):
2313
2220
    """Create an inventory entry.
2314
2221
 
2334
2241
        accessed on this platform by the normalized path.
2335
2242
    :return: The NFC normalised version of name.
2336
2243
    """
2337
 
    # ------- This has been copied to breezy.dirstate.DirState.add, please
 
2244
    #------- This has been copied to bzrlib.dirstate.DirState.add, please
2338
2245
    # keep them synchronised.
2339
2246
    # we dont import normalized_filename directly because we want to be
2340
2247
    # able to change the implementation at runtime for tests.
2351
2258
 
2352
2259
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
2353
2260
 
2354
 
 
2355
2261
def is_valid_name(name):
2356
2262
    return bool(_NAME_RE.match(name))
2357
2263
 
2367
2273
        ids.add(item[2])
2368
2274
        if len(ids) != length:
2369
2275
            raise errors.InconsistentDelta(item[0] or item[1], item[2],
2370
 
                                           "repeated file_id")
 
2276
                "repeated file_id")
2371
2277
        yield item
2372
2278
 
2373
2279
 
2412
2318
        entry = item[3]
2413
2319
        if item[2] is None:
2414
2320
            raise errors.InconsistentDelta(item[0] or item[1], item[2],
2415
 
                                           "entry with file_id None %r" % entry)
2416
 
        if not isinstance(item[2], bytes):
 
2321
                "entry with file_id None %r" % entry)
 
2322
        if type(item[2]) != str:
2417
2323
            raise errors.InconsistentDelta(item[0] or item[1], item[2],
2418
 
                                           "entry with non bytes file_id %r" % entry)
 
2324
                "entry with non bytes file_id %r" % entry)
2419
2325
        yield item
2420
2326
 
2421
2327
 
2429
2335
        if entry is not None:
2430
2336
            if entry.file_id != item[2]:
2431
2337
                raise errors.InconsistentDelta(item[0] or item[1], item[2],
2432
 
                                               "mismatched id with %r" % entry)
 
2338
                    "mismatched id with %r" % entry)
2433
2339
        yield item
2434
2340
 
2435
2341
 
2443
2349
        entry = item[3]
2444
2350
        if new_path is None and entry is not None:
2445
2351
            raise errors.InconsistentDelta(item[0], item[1],
2446
 
                                           "Entry with no new_path")
 
2352
                "Entry with no new_path")
2447
2353
        if new_path is not None and entry is None:
2448
2354
            raise errors.InconsistentDelta(new_path, item[1],
2449
 
                                           "new_path with no entry")
 
2355
                "new_path with no entry")
2450
2356
        yield item
2451
2357
 
2452
2358