/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 breezy/bzr/inventory.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2017-06-20 22:54:06 UTC
  • mfrom: (6700.2.5 remove-record-entry-contents)
  • Revision ID: breezy.the.bot@gmail.com-20170620225406-0my0i6wr2ow0o7g7
Remove remaining implementations of CommitBuilder.record_{entry_contents,delete}.

Merged from https://code.launchpad.net/~jelmer/brz/remove-record-entry-contents/+merge/325968

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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 = "TREE_ROOT"
 
30
ROOT_ID = b"TREE_ROOT"
29
31
 
30
 
from bzrlib.lazy_import import lazy_import
 
32
from ..lazy_import import lazy_import
31
33
lazy_import(globals(), """
32
34
import collections
33
35
import copy
34
 
import os
35
36
import re
36
37
import tarfile
37
38
 
38
 
import bzrlib
39
 
from bzrlib import (
40
 
    chk_map,
 
39
from breezy import (
41
40
    errors,
42
41
    generate_ids,
43
42
    osutils,
44
 
    symbol_versioning,
 
43
    )
 
44
from breezy.bzr import (
 
45
    chk_map,
45
46
    )
46
47
""")
47
48
 
48
 
from bzrlib.errors import (
49
 
    BzrCheckError,
50
 
    BzrError,
51
 
    )
52
 
from bzrlib.symbol_versioning import deprecated_in, deprecated_method
53
 
from bzrlib.trace import mutter
54
 
from bzrlib.static_tuple import StaticTuple
 
49
from .. import (
 
50
    lazy_regex,
 
51
    trace,
 
52
    )
 
53
from ..sixish import (
 
54
    bytesintern,
 
55
    PY3,
 
56
    text_type,
 
57
    viewitems,
 
58
    viewvalues,
 
59
    )
 
60
from ..static_tuple import StaticTuple
55
61
 
56
62
 
57
63
class InventoryEntry(object):
104
110
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
105
111
    >>> i.path2id('src/wibble')
106
112
    '2325'
107
 
    >>> '2325' in i
108
 
    True
109
113
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
110
114
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
111
115
    >>> i['2326']
131
135
    RENAMED = 'renamed'
132
136
    MODIFIED_AND_RENAMED = 'modified and renamed'
133
137
 
134
 
    __slots__ = []
 
138
    __slots__ = ['file_id', 'revision', 'parent_id', 'name']
 
139
 
 
140
    # Attributes that all InventoryEntry instances are expected to have, but
 
141
    # that don't vary for all kinds of entry.  (e.g. symlink_target is only
 
142
    # relevant to InventoryLink, so there's no reason to make every
 
143
    # InventoryFile instance allocate space to hold a value for it.)
 
144
    # Attributes that only vary for files: executable, text_sha1, text_size,
 
145
    # text_id
 
146
    executable = False
 
147
    text_sha1 = None
 
148
    text_size = None
 
149
    text_id = None
 
150
    # Attributes that only vary for symlinks: symlink_target
 
151
    symlink_target = None
 
152
    # Attributes that only vary for tree-references: reference_revision
 
153
    reference_revision = None
 
154
 
135
155
 
136
156
    def detect_changes(self, old_entry):
137
157
        """Return a (text_modified, meta_modified) from this to old_entry.
158
178
        candidates = {}
159
179
        # identify candidate head revision ids.
160
180
        for inv in previous_inventories:
161
 
            if self.file_id in inv:
 
181
            if inv.has_id(self.file_id):
162
182
                ie = inv[self.file_id]
163
183
                if ie.revision in candidates:
164
184
                    # same revision value in two different inventories:
176
196
                    candidates[ie.revision] = ie
177
197
        return candidates
178
198
 
179
 
    @deprecated_method(deprecated_in((1, 6, 0)))
180
 
    def get_tar_item(self, root, dp, now, tree):
181
 
        """Get a tarfile item and a file stream for its content."""
182
 
        item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
183
 
        # TODO: would be cool to actually set it to the timestamp of the
184
 
        # revision it was last changed
185
 
        item.mtime = now
186
 
        fileobj = self._put_in_tar(item, tree)
187
 
        return item, fileobj
188
 
 
189
199
    def has_text(self):
190
200
        """Return true if the object this entry represents has textual data.
191
201
 
197
207
        """
198
208
        return False
199
209
 
200
 
    def __init__(self, file_id, name, parent_id, text_id=None):
 
210
    def __init__(self, file_id, name, parent_id):
201
211
        """Create an InventoryEntry
202
212
 
203
213
        The filename must be a single component, relative to the
212
222
        Traceback (most recent call last):
213
223
        InvalidEntryName: Invalid entry name: src/hello.c
214
224
        """
215
 
        if '/' in name or '\\' in name:
 
225
        if u'/' in name or u'\\' in name:
216
226
            raise errors.InvalidEntryName(name=name)
217
 
        self.executable = False
 
227
        self.file_id = file_id
218
228
        self.revision = None
219
 
        self.text_sha1 = None
220
 
        self.text_size = None
221
 
        self.file_id = file_id
222
229
        self.name = name
223
 
        self.text_id = text_id
224
230
        self.parent_id = parent_id
225
 
        self.symlink_target = None
226
 
        self.reference_revision = None
227
231
 
228
232
    def kind_character(self):
229
233
        """Return a short kind indicator useful for appending to names."""
230
 
        raise BzrError('unknown kind %r' % self.kind)
 
234
        raise errors.BzrError('unknown kind %r' % self.kind)
231
235
 
232
236
    known_kinds = ('file', 'directory', 'symlink')
233
237
 
234
 
    def _put_in_tar(self, item, tree):
235
 
        """populate item for stashing in a tar, and return the content stream.
236
 
 
237
 
        If no content is available, return None.
238
 
        """
239
 
        raise BzrError("don't know how to export {%s} of kind %r" %
240
 
                       (self.file_id, self.kind))
241
 
 
242
 
    @deprecated_method(deprecated_in((1, 6, 0)))
243
 
    def put_on_disk(self, dest, dp, tree):
244
 
        """Create a representation of self on disk in the prefix dest.
245
 
 
246
 
        This is a template method - implement _put_on_disk in subclasses.
247
 
        """
248
 
        fullpath = osutils.pathjoin(dest, dp)
249
 
        self._put_on_disk(fullpath, tree)
250
 
        # mutter("  export {%s} kind %s to %s", self.file_id,
251
 
        #         self.kind, fullpath)
252
 
 
253
 
    def _put_on_disk(self, fullpath, tree):
254
 
        """Put this entry onto disk at fullpath, from tree tree."""
255
 
        raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
256
 
 
257
 
    def sorted_children(self):
258
 
        return sorted(self.children.items())
259
 
 
260
238
    @staticmethod
261
239
    def versionable_kind(kind):
262
240
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
276
254
        """
277
255
        if self.parent_id is not None:
278
256
            if not inv.has_id(self.parent_id):
279
 
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
280
 
                        % (self.parent_id, rev_id))
 
257
                raise errors.BzrCheckError(
 
258
                    'missing parent {%s} in inventory for revision {%s}' % (
 
259
                        self.parent_id, rev_id))
281
260
        checker._add_entry_to_text_key_references(inv, self)
282
261
        self._check(checker, rev_id)
283
262
 
397
376
        pass
398
377
 
399
378
 
400
 
class RootEntry(InventoryEntry):
401
 
 
402
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
403
 
                 'text_id', 'parent_id', 'children', 'executable',
404
 
                 'revision', 'symlink_target', 'reference_revision']
405
 
 
406
 
    def _check(self, checker, rev_id):
407
 
        """See InventoryEntry._check"""
408
 
 
409
 
    def __init__(self, file_id):
410
 
        self.file_id = file_id
411
 
        self.children = {}
412
 
        self.kind = 'directory'
413
 
        self.parent_id = None
414
 
        self.name = u''
415
 
        self.revision = None
416
 
        symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
417
 
                               '  Please use InventoryDirectory instead.',
418
 
                               DeprecationWarning, stacklevel=2)
419
 
 
420
 
    def __eq__(self, other):
421
 
        if not isinstance(other, RootEntry):
422
 
            return NotImplemented
423
 
 
424
 
        return (self.file_id == other.file_id) \
425
 
               and (self.children == other.children)
426
 
 
427
 
 
428
379
class InventoryDirectory(InventoryEntry):
429
380
    """A directory in an inventory."""
430
381
 
431
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
432
 
                 'text_id', 'parent_id', 'children', 'executable',
433
 
                 'revision', 'symlink_target', 'reference_revision']
 
382
    __slots__ = ['children']
 
383
 
 
384
    kind = 'directory'
434
385
 
435
386
    def _check(self, checker, rev_id):
436
387
        """See InventoryEntry._check"""
437
 
        if (self.text_sha1 is not None or self.text_size is not None or
438
 
            self.text_id is not None):
439
 
            checker._report_items.append('directory {%s} has text in revision {%s}'
440
 
                                % (self.file_id, rev_id))
441
388
        # In non rich root repositories we do not expect a file graph for the
442
389
        # root.
443
390
        if self.name == '' and not checker.rich_roots:
446
393
        # to provide a per-fileid log. The hash of every directory content is
447
394
        # "da..." below (the sha1sum of '').
448
395
        checker.add_pending_item(rev_id,
449
 
            ('texts', self.file_id, self.revision), 'text',
450
 
             'da39a3ee5e6b4b0d3255bfef95601890afd80709')
 
396
            (b'texts', self.file_id, self.revision), b'text',
 
397
             b'da39a3ee5e6b4b0d3255bfef95601890afd80709')
451
398
 
452
399
    def copy(self):
453
400
        other = InventoryDirectory(self.file_id, self.name, self.parent_id)
459
406
    def __init__(self, file_id, name, parent_id):
460
407
        super(InventoryDirectory, self).__init__(file_id, name, parent_id)
461
408
        self.children = {}
462
 
        self.kind = 'directory'
 
409
 
 
410
    def sorted_children(self):
 
411
        return sorted(viewitems(self.children))
463
412
 
464
413
    def kind_character(self):
465
414
        """See InventoryEntry.kind_character."""
466
415
        return '/'
467
416
 
468
 
    def _put_in_tar(self, item, tree):
469
 
        """See InventoryEntry._put_in_tar."""
470
 
        item.type = tarfile.DIRTYPE
471
 
        fileobj = None
472
 
        item.name += '/'
473
 
        item.size = 0
474
 
        item.mode = 0755
475
 
        return fileobj
476
 
 
477
 
    def _put_on_disk(self, fullpath, tree):
478
 
        """See InventoryEntry._put_on_disk."""
479
 
        os.mkdir(fullpath)
480
 
 
481
417
 
482
418
class InventoryFile(InventoryEntry):
483
419
    """A file in an inventory."""
484
420
 
485
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
486
 
                 'text_id', 'parent_id', 'children', 'executable',
487
 
                 'revision', 'symlink_target', 'reference_revision']
 
421
    __slots__ = ['text_sha1', 'text_size', 'text_id', 'executable']
 
422
 
 
423
    kind = 'file'
 
424
 
 
425
    def __init__(self, file_id, name, parent_id):
 
426
        super(InventoryFile, self).__init__(file_id, name, parent_id)
 
427
        self.text_sha1 = None
 
428
        self.text_size = None
 
429
        self.text_id = None
 
430
        self.executable = False
488
431
 
489
432
    def _check(self, checker, tree_revision_id):
490
433
        """See InventoryEntry._check"""
491
434
        # TODO: check size too.
492
435
        checker.add_pending_item(tree_revision_id,
493
 
            ('texts', self.file_id, self.revision), 'text',
 
436
            (b'texts', self.file_id, self.revision), b'text',
494
437
             self.text_sha1)
495
438
        if self.text_size is None:
496
439
            checker._report_items.append(
515
458
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
516
459
             output_to, reverse=False):
517
460
        """See InventoryEntry._diff."""
518
 
        from bzrlib.diff import DiffText
 
461
        from breezy.diff import DiffText
519
462
        from_file_id = self.file_id
520
463
        if to_entry:
521
464
            to_file_id = to_entry.file_id
533
476
        """See InventoryEntry.has_text."""
534
477
        return True
535
478
 
536
 
    def __init__(self, file_id, name, parent_id):
537
 
        super(InventoryFile, self).__init__(file_id, name, parent_id)
538
 
        self.kind = 'file'
539
 
 
540
479
    def kind_character(self):
541
480
        """See InventoryEntry.kind_character."""
542
481
        return ''
543
482
 
544
 
    def _put_in_tar(self, item, tree):
545
 
        """See InventoryEntry._put_in_tar."""
546
 
        item.type = tarfile.REGTYPE
547
 
        fileobj = tree.get_file(self.file_id)
548
 
        item.size = self.text_size
549
 
        if tree.is_executable(self.file_id):
550
 
            item.mode = 0755
551
 
        else:
552
 
            item.mode = 0644
553
 
        return fileobj
554
 
 
555
 
    def _put_on_disk(self, fullpath, tree):
556
 
        """See InventoryEntry._put_on_disk."""
557
 
        osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
558
 
        if tree.is_executable(self.file_id):
559
 
            os.chmod(fullpath, 0755)
560
 
 
561
483
    def _read_tree_state(self, path, work_tree):
562
484
        """See InventoryEntry._read_tree_state."""
563
485
        self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
595
517
class InventoryLink(InventoryEntry):
596
518
    """A file in an inventory."""
597
519
 
598
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
599
 
                 'text_id', 'parent_id', 'children', 'executable',
600
 
                 'revision', 'symlink_target', 'reference_revision']
 
520
    __slots__ = ['symlink_target']
 
521
 
 
522
    kind = 'symlink'
 
523
 
 
524
    def __init__(self, file_id, name, parent_id):
 
525
        super(InventoryLink, self).__init__(file_id, name, parent_id)
 
526
        self.symlink_target = None
601
527
 
602
528
    def _check(self, checker, tree_revision_id):
603
529
        """See InventoryEntry._check"""
604
 
        if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
605
 
            checker._report_items.append(
606
 
               'symlink {%s} has text in revision {%s}'
607
 
                    % (self.file_id, tree_revision_id))
608
530
        if self.symlink_target is None:
609
531
            checker._report_items.append(
610
532
                'symlink {%s} has no target in revision {%s}'
611
533
                    % (self.file_id, tree_revision_id))
612
534
        # Symlinks are stored as ''
613
535
        checker.add_pending_item(tree_revision_id,
614
 
            ('texts', self.file_id, self.revision), 'text',
615
 
             'da39a3ee5e6b4b0d3255bfef95601890afd80709')
 
536
            (b'texts', self.file_id, self.revision), b'text',
 
537
             b'da39a3ee5e6b4b0d3255bfef95601890afd80709')
616
538
 
617
539
    def copy(self):
618
540
        other = InventoryLink(self.file_id, self.name, self.parent_id)
625
547
        # FIXME: which _modified field should we use ? RBC 20051003
626
548
        text_modified = (self.symlink_target != old_entry.symlink_target)
627
549
        if text_modified:
628
 
            mutter("    symlink target changed")
 
550
            trace.mutter("    symlink target changed")
629
551
        meta_modified = False
630
552
        return text_modified, meta_modified
631
553
 
632
554
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
633
555
             output_to, reverse=False):
634
556
        """See InventoryEntry._diff."""
635
 
        from bzrlib.diff import DiffSymlink
 
557
        from breezy.diff import DiffSymlink
636
558
        old_target = self.symlink_target
637
559
        if to_entry is not None:
638
560
            new_target = to_entry.symlink_target
648
570
        differ = DiffSymlink(old_tree, new_tree, output_to)
649
571
        return differ.diff_symlink(old_target, new_target)
650
572
 
651
 
    def __init__(self, file_id, name, parent_id):
652
 
        super(InventoryLink, self).__init__(file_id, name, parent_id)
653
 
        self.kind = 'symlink'
654
 
 
655
573
    def kind_character(self):
656
574
        """See InventoryEntry.kind_character."""
657
575
        return ''
658
576
 
659
 
    def _put_in_tar(self, item, tree):
660
 
        """See InventoryEntry._put_in_tar."""
661
 
        item.type = tarfile.SYMTYPE
662
 
        fileobj = None
663
 
        item.size = 0
664
 
        item.mode = 0755
665
 
        item.linkname = self.symlink_target
666
 
        return fileobj
667
 
 
668
 
    def _put_on_disk(self, fullpath, tree):
669
 
        """See InventoryEntry._put_on_disk."""
670
 
        try:
671
 
            os.symlink(self.symlink_target, fullpath)
672
 
        except OSError,e:
673
 
            raise BzrError("Failed to create symlink %r -> %r, error: %s" % (fullpath, self.symlink_target, e))
674
 
 
675
577
    def _read_tree_state(self, path, work_tree):
676
578
        """See InventoryEntry._read_tree_state."""
677
579
        self.symlink_target = work_tree.get_symlink_target(self.file_id)
689
591
 
690
592
class TreeReference(InventoryEntry):
691
593
 
 
594
    __slots__ = ['reference_revision']
 
595
 
692
596
    kind = 'tree-reference'
693
597
 
694
598
    def __init__(self, file_id, name, parent_id, revision=None,
733
637
    inserted, other than through the Inventory API.
734
638
    """
735
639
 
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
 
 
753
640
    def has_filename(self, filename):
754
641
        return bool(self.path2id(filename))
755
642
 
781
668
                return
782
669
            from_dir = self.root
783
670
            yield '', self.root
784
 
        elif isinstance(from_dir, basestring):
 
671
        elif isinstance(from_dir, (str, text_type)):
785
672
            from_dir = self[from_dir]
786
673
 
787
674
        # unrolling the recursive called changed the time from
788
675
        # 440ms/663ms (inline/total) to 116ms/116ms
789
 
        children = from_dir.children.items()
790
 
        children.sort()
 
676
        children = sorted(viewitems(from_dir.children))
791
677
        if not recursive:
792
678
            for name, ie in children:
793
679
                yield name, ie
812
698
                    continue
813
699
 
814
700
                # But do this child first
815
 
                new_children = ie.children.items()
816
 
                new_children.sort()
 
701
                new_children = sorted(viewitems(ie.children))
817
702
                new_children = collections.deque(new_children)
818
703
                stack.append((path, new_children))
819
704
                # Break out of inner loop, so that we start outer loop with child
822
707
                # if we finished all children, pop it off the stack
823
708
                stack.pop()
824
709
 
 
710
    def _preload_cache(self):
 
711
        """Populate any caches, we are about to access all items.
 
712
        
 
713
        The default implementation does nothing, because CommonInventory doesn't
 
714
        have a cache.
 
715
        """
 
716
        pass
 
717
    
825
718
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
826
719
        yield_parents=False):
827
720
        """Iterate over the entries in a directory first order.
840
733
            specific_file_ids = set(specific_file_ids)
841
734
        # TODO? Perhaps this should return the from_dir so that the root is
842
735
        # yielded? or maybe an option?
 
736
        if from_dir is None and specific_file_ids is None:
 
737
            # They are iterating from the root, and have not specified any
 
738
            # specific entries to look at. All current callers fully consume the
 
739
            # iterator, so we can safely assume we are accessing all entries
 
740
            self._preload_cache()
843
741
        if from_dir is None:
844
742
            if self.root is None:
845
743
                return
847
745
            if (not yield_parents and specific_file_ids is not None and
848
746
                len(specific_file_ids) == 1):
849
747
                file_id = list(specific_file_ids)[0]
850
 
                if file_id in self:
 
748
                if self.has_id(file_id):
851
749
                    yield self.id2path(file_id), self[file_id]
852
750
                return
853
751
            from_dir = self.root
854
752
            if (specific_file_ids is None or yield_parents or
855
753
                self.root.file_id in specific_file_ids):
856
754
                yield u'', self.root
857
 
        elif isinstance(from_dir, basestring):
 
755
        elif isinstance(from_dir, (str, text_type)):
858
756
            from_dir = self[from_dir]
859
757
 
860
758
        if specific_file_ids is not None:
863
761
            parents = set()
864
762
            byid = self
865
763
            def add_ancestors(file_id):
866
 
                if file_id not in byid:
 
764
                if not byid.has_id(file_id):
867
765
                    return
868
766
                parent_id = byid[file_id].parent_id
869
767
                if parent_id is None:
881
779
            cur_relpath, cur_dir = stack.pop()
882
780
 
883
781
            child_dirs = []
884
 
            for child_name, child_ie in sorted(cur_dir.children.iteritems()):
 
782
            for child_name, child_ie in sorted(viewitems(cur_dir.children)):
885
783
 
886
784
                child_relpath = cur_relpath + child_name
887
785
 
913
811
                    file_id, self[file_id]))
914
812
        return delta
915
813
 
916
 
    def _get_mutable_inventory(self):
917
 
        """Returns a mutable copy of the object.
918
 
 
919
 
        Some inventories are immutable, yet working trees, for example, needs
920
 
        to mutate exisiting inventories instead of creating a new one.
921
 
        """
922
 
        raise NotImplementedError(self._get_mutable_inventory)
923
 
 
924
814
    def make_entry(self, kind, name, parent_id, file_id=None):
925
 
        """Simple thunk to bzrlib.inventory.make_entry."""
 
815
        """Simple thunk to breezy.bzr.inventory.make_entry."""
926
816
        return make_entry(kind, name, parent_id, file_id)
927
817
 
928
818
    def entries(self):
932
822
        """
933
823
        accum = []
934
824
        def descend(dir_ie, dir_path):
935
 
            kids = dir_ie.children.items()
936
 
            kids.sort()
 
825
            kids = sorted(viewitems(dir_ie.children))
937
826
            for name, ie in kids:
938
827
                child_path = osutils.pathjoin(dir_path, name)
939
828
                accum.append((child_path, ie))
940
829
                if ie.kind == 'directory':
941
830
                    descend(ie, child_path)
942
831
 
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'')
 
832
        if self.root is not None:
 
833
            descend(self.root, u'')
960
834
        return accum
961
835
 
962
836
    def path2id(self, relpath):
970
844
 
971
845
        Returns None IFF the path is not found.
972
846
        """
973
 
        if isinstance(relpath, basestring):
 
847
        if isinstance(relpath, (str, text_type)):
974
848
            names = osutils.splitpath(relpath)
975
849
        else:
976
850
            names = relpath
1060
934
 
1061
935
    >>> inv.path2id('hello.c')
1062
936
    '123-123'
1063
 
    >>> '123-123' in inv
 
937
    >>> inv.has_id('123-123')
1064
938
    True
1065
939
 
1066
940
    There are iterators over the contents:
1223
1097
            other.add(entry.copy())
1224
1098
        return other
1225
1099
 
1226
 
    def _get_mutable_inventory(self):
1227
 
        """See CommonInventory._get_mutable_inventory."""
1228
 
        return copy.deepcopy(self)
1229
 
 
1230
1100
    def __iter__(self):
1231
1101
        """Iterate over all file-ids."""
1232
1102
        return iter(self._byid)
1240
1110
        XXX: We may not want to merge this into bzr.dev.
1241
1111
        """
1242
1112
        if self.root is None:
1243
 
            return
1244
 
        for _, ie in self._byid.iteritems():
1245
 
            yield ie
 
1113
            return ()
 
1114
        return iter(viewvalues(self._byid))
1246
1115
 
1247
1116
    def __len__(self):
1248
1117
        """Returns number of entries."""
1272
1141
    def _add_child(self, entry):
1273
1142
        """Add an entry to the inventory, without adding it to its parent"""
1274
1143
        if entry.file_id in self._byid:
1275
 
            raise BzrError("inventory already contains entry with id {%s}" %
1276
 
                           entry.file_id)
 
1144
            raise errors.BzrError(
 
1145
                "inventory already contains entry with id {%s}" %
 
1146
                entry.file_id)
1277
1147
        self._byid[entry.file_id] = entry
1278
 
        for child in getattr(entry, 'children', {}).itervalues():
1279
 
            self._add_child(child)
 
1148
        children = getattr(entry, 'children', {})
 
1149
        if children is not None:
 
1150
            for child in viewvalues(children):
 
1151
                self._add_child(child)
1280
1152
        return entry
1281
1153
 
1282
1154
    def add(self, entry):
1283
1155
        """Add entry to inventory.
1284
1156
 
1285
 
        To add  a file to a branch ready to be committed, use Branch.add,
1286
 
        which calls this.
1287
 
 
1288
1157
        :return: entry
1289
1158
        """
1290
1159
        if entry.file_id in self._byid:
1335
1204
        >>> inv = Inventory()
1336
1205
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1337
1206
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1338
 
        >>> '123' in inv
 
1207
        >>> inv.has_id('123')
1339
1208
        True
1340
1209
        >>> del inv['123']
1341
 
        >>> '123' in inv
 
1210
        >>> inv.has_id('123')
1342
1211
        False
1343
1212
        """
1344
1213
        ie = self[file_id]
1428
1297
            ie = to_find_delete.pop()
1429
1298
            to_delete.append(ie.file_id)
1430
1299
            if ie.kind == 'directory':
1431
 
                to_find_delete.extend(ie.children.values())
 
1300
                to_find_delete.extend(viewvalues(ie.children))
1432
1301
        for file_id in reversed(to_delete):
1433
1302
            ie = self[file_id]
1434
1303
            del self._byid[file_id]
1446
1315
        """
1447
1316
        new_name = ensure_normalized_name(new_name)
1448
1317
        if not is_valid_name(new_name):
1449
 
            raise BzrError("not an acceptable filename: %r" % new_name)
 
1318
            raise errors.BzrError("not an acceptable filename: %r" % new_name)
1450
1319
 
1451
1320
        new_parent = self._byid[new_parent_id]
1452
1321
        if new_name in new_parent.children:
1453
 
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
1322
            raise errors.BzrError("%r already exists in %r" %
 
1323
                (new_name, self.id2path(new_parent_id)))
1454
1324
 
1455
1325
        new_parent_idpath = self.get_idpath(new_parent_id)
1456
1326
        if file_id in new_parent_idpath:
1457
 
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
 
1327
            raise errors.BzrError(
 
1328
                "cannot move directory %r into a subdirectory of itself, %r"
1458
1329
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1459
1330
 
1460
1331
        file_ie = self._byid[file_id]
1496
1367
    def __init__(self, search_key_name):
1497
1368
        CommonInventory.__init__(self)
1498
1369
        self._fileid_to_entry_cache = {}
 
1370
        self._fully_cached = False
1499
1371
        self._path_to_fileid_cache = {}
1500
1372
        self._search_key_name = search_key_name
1501
1373
        self.root_id = None
1531
1403
        if entry.parent_id is not None:
1532
1404
            parent_str = entry.parent_id
1533
1405
        else:
1534
 
            parent_str = ''
 
1406
            parent_str = b''
1535
1407
        name_str = entry.name.encode("utf8")
1536
1408
        if entry.kind == 'file':
1537
1409
            if entry.executable:
1538
 
                exec_str = "Y"
 
1410
                exec_str = b"Y"
1539
1411
            else:
1540
 
                exec_str = "N"
1541
 
            return "file: %s\n%s\n%s\n%s\n%s\n%d\n%s" % (
 
1412
                exec_str = b"N"
 
1413
            return b"file: %s\n%s\n%s\n%s\n%s\n%d\n%s" % (
1542
1414
                entry.file_id, parent_str, name_str, entry.revision,
1543
1415
                entry.text_sha1, entry.text_size, exec_str)
1544
1416
        elif entry.kind == 'directory':
1545
 
            return "dir: %s\n%s\n%s\n%s" % (
 
1417
            return b"dir: %s\n%s\n%s\n%s" % (
1546
1418
                entry.file_id, parent_str, name_str, entry.revision)
1547
1419
        elif entry.kind == 'symlink':
1548
 
            return "symlink: %s\n%s\n%s\n%s\n%s" % (
 
1420
            return b"symlink: %s\n%s\n%s\n%s\n%s" % (
1549
1421
                entry.file_id, parent_str, name_str, entry.revision,
1550
1422
                entry.symlink_target.encode("utf8"))
1551
1423
        elif entry.kind == 'tree-reference':
1552
 
            return "tree: %s\n%s\n%s\n%s\n%s" % (
 
1424
            return b"tree: %s\n%s\n%s\n%s\n%s" % (
1553
1425
                entry.file_id, parent_str, name_str, entry.revision,
1554
1426
                entry.reference_revision)
1555
1427
        else:
1588
1460
            if entry.kind == 'directory':
1589
1461
                directories_to_expand.add(entry.file_id)
1590
1462
            interesting.add(entry.parent_id)
1591
 
            children_of_parent_id.setdefault(entry.parent_id, []
1592
 
                                             ).append(entry.file_id)
 
1463
            children_of_parent_id.setdefault(entry.parent_id, set()
 
1464
                                             ).add(entry.file_id)
1593
1465
 
1594
1466
        # Now, interesting has all of the direct parents, but not the
1595
1467
        # parents of those parents. It also may have some duplicates with
1603
1475
            next_parents = set()
1604
1476
            for entry in self._getitems(remaining_parents):
1605
1477
                next_parents.add(entry.parent_id)
1606
 
                children_of_parent_id.setdefault(entry.parent_id, []
1607
 
                                                 ).append(entry.file_id)
 
1478
                children_of_parent_id.setdefault(entry.parent_id, set()
 
1479
                                                 ).add(entry.file_id)
1608
1480
            # Remove any search tips we've already processed
1609
1481
            remaining_parents = next_parents.difference(interesting)
1610
1482
            interesting.update(remaining_parents)
1617
1489
            keys = [StaticTuple(f,).intern() for f in directories_to_expand]
1618
1490
            directories_to_expand = set()
1619
1491
            items = self.parent_id_basename_to_file_id.iteritems(keys)
1620
 
            next_file_ids = set([item[1] for item in items])
 
1492
            next_file_ids = {item[1] for item in items}
1621
1493
            next_file_ids = next_file_ids.difference(interesting)
1622
1494
            interesting.update(next_file_ids)
1623
1495
            for entry in self._getitems(next_file_ids):
1624
1496
                if entry.kind == 'directory':
1625
1497
                    directories_to_expand.add(entry.file_id)
1626
 
                children_of_parent_id.setdefault(entry.parent_id, []
1627
 
                                                 ).append(entry.file_id)
 
1498
                children_of_parent_id.setdefault(entry.parent_id, set()
 
1499
                                                 ).add(entry.file_id)
1628
1500
        return interesting, children_of_parent_id
1629
1501
 
1630
1502
    def filter(self, specific_fileids):
1652
1524
            # parent_to_children with at least the tree root.)
1653
1525
            return other
1654
1526
        cache = self._fileid_to_entry_cache
1655
 
        try:
1656
 
            remaining_children = collections.deque(parent_to_children[self.root_id])
1657
 
        except:
1658
 
            import pdb; pdb.set_trace()
1659
 
            raise
 
1527
        remaining_children = collections.deque(parent_to_children[self.root_id])
1660
1528
        while remaining_children:
1661
1529
            file_id = remaining_children.popleft()
1662
1530
            ie = cache[file_id]
1671
1539
        return other
1672
1540
 
1673
1541
    @staticmethod
1674
 
    def _bytes_to_utf8name_key(bytes):
1675
 
        """Get the file_id, revision_id key out of bytes."""
 
1542
    def _bytes_to_utf8name_key(data):
 
1543
        """Get the file_id, revision_id key out of data."""
1676
1544
        # We don't normally care about name, except for times when we want
1677
1545
        # to filter out empty names because of non rich-root...
1678
 
        sections = bytes.split('\n')
1679
 
        kind, file_id = sections[0].split(': ')
1680
 
        return (sections[2], intern(file_id), intern(sections[3]))
 
1546
        sections = data.split(b'\n')
 
1547
        kind, file_id = sections[0].split(b': ')
 
1548
        return (sections[2], bytesintern(file_id), bytesintern(sections[3]))
1681
1549
 
1682
1550
    def _bytes_to_entry(self, bytes):
1683
1551
        """Deserialise a serialised entry."""
1684
 
        sections = bytes.split('\n')
1685
 
        if sections[0].startswith("file: "):
 
1552
        sections = bytes.split(b'\n')
 
1553
        if sections[0].startswith(b"file: "):
1686
1554
            result = InventoryFile(sections[0][6:],
1687
1555
                sections[2].decode('utf8'),
1688
1556
                sections[1])
1689
1557
            result.text_sha1 = sections[4]
1690
1558
            result.text_size = int(sections[5])
1691
 
            result.executable = sections[6] == "Y"
1692
 
        elif sections[0].startswith("dir: "):
 
1559
            result.executable = sections[6] == b"Y"
 
1560
        elif sections[0].startswith(b"dir: "):
1693
1561
            result = CHKInventoryDirectory(sections[0][5:],
1694
1562
                sections[2].decode('utf8'),
1695
1563
                sections[1], self)
1696
 
        elif sections[0].startswith("symlink: "):
 
1564
        elif sections[0].startswith(b"symlink: "):
1697
1565
            result = InventoryLink(sections[0][9:],
1698
1566
                sections[2].decode('utf8'),
1699
1567
                sections[1])
1700
1568
            result.symlink_target = sections[4].decode('utf8')
1701
 
        elif sections[0].startswith("tree: "):
 
1569
        elif sections[0].startswith(b"tree: "):
1702
1570
            result = TreeReference(sections[0][6:],
1703
1571
                sections[2].decode('utf8'),
1704
1572
                sections[1])
1705
1573
            result.reference_revision = sections[4]
1706
1574
        else:
1707
1575
            raise ValueError("Not a serialised entry %r" % bytes)
1708
 
        result.file_id = intern(result.file_id)
1709
 
        result.revision = intern(sections[3])
1710
 
        if result.parent_id == '':
 
1576
        result.file_id = bytesintern(result.file_id)
 
1577
        result.revision = bytesintern(sections[3])
 
1578
        if result.parent_id == b'':
1711
1579
            result.parent_id = None
1712
1580
        self._fileid_to_entry_cache[result.file_id] = result
1713
1581
        return result
1714
1582
 
1715
 
    def _get_mutable_inventory(self):
1716
 
        """See CommonInventory._get_mutable_inventory."""
1717
 
        entries = self.iter_entries()
1718
 
        inv = Inventory(None, self.revision_id)
1719
 
        for path, inv_entry in entries:
1720
 
            inv.add(inv_entry.copy())
1721
 
        return inv
1722
 
 
1723
1583
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1724
1584
        propagate_caches=False):
1725
1585
        """Create a new CHKInventory by applying inventory_delta to this one.
1738
1598
        result = CHKInventory(self._search_key_name)
1739
1599
        if propagate_caches:
1740
1600
            # Just propagate the path-to-fileid cache for now
1741
 
            result._path_to_fileid_cache = dict(self._path_to_fileid_cache.iteritems())
 
1601
            result._path_to_fileid_cache = self._path_to_fileid_cache.copy()
1742
1602
        search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1743
1603
        self.id_to_entry._ensure_root()
1744
1604
        maximum_size = self.id_to_entry._root_node.maximum_size
1857
1717
                continue
1858
1718
            # This loop could potentially be better by using the id_basename
1859
1719
            # map to just get the child file ids.
1860
 
            for child in entry.children.values():
 
1720
            for child in viewvalues(entry.children):
1861
1721
                if child.file_id not in altered:
1862
1722
                    raise errors.InconsistentDelta(self.id2path(child.file_id),
1863
1723
                        child.file_id, "Child not deleted or reparented when "
1869
1729
            # re-keying, but its simpler to just output that as a delete+add
1870
1730
            # to spend less time calculating the delta.
1871
1731
            delta_list = []
1872
 
            for key, (old_key, value) in parent_id_basename_delta.iteritems():
 
1732
            for key, (old_key, value) in viewitems(parent_id_basename_delta):
1873
1733
                if value is not None:
1874
1734
                    delta_list.append((old_key, key, value))
1875
1735
                else:
1899
1759
            for.
1900
1760
        :return: A CHKInventory
1901
1761
        """
1902
 
        lines = bytes.split('\n')
1903
 
        if lines[-1] != '':
 
1762
        lines = bytes.split(b'\n')
 
1763
        if lines[-1] != b'':
1904
1764
            raise AssertionError('bytes to deserialize must end with an eol')
1905
1765
        lines.pop()
1906
 
        if lines[0] != 'chkinventory:':
 
1766
        if lines[0] != b'chkinventory:':
1907
1767
            raise ValueError("not a serialised CHKInventory: %r" % bytes)
1908
1768
        info = {}
1909
 
        allowed_keys = frozenset(['root_id', 'revision_id', 'search_key_name',
1910
 
                                  'parent_id_basename_to_file_id',
1911
 
                                  'id_to_entry'])
 
1769
        allowed_keys = frozenset((b'root_id', b'revision_id',
 
1770
                                  b'parent_id_basename_to_file_id',
 
1771
                                  b'search_key_name', b'id_to_entry'))
1912
1772
        for line in lines[1:]:
1913
 
            key, value = line.split(': ', 1)
 
1773
            key, value = line.split(b': ', 1)
1914
1774
            if key not in allowed_keys:
1915
1775
                raise errors.BzrError('Unknown key in inventory: %r\n%r'
1916
1776
                                      % (key, bytes))
1918
1778
                raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1919
1779
                                      % (key, bytes))
1920
1780
            info[key] = value
1921
 
        revision_id = intern(info['revision_id'])
1922
 
        root_id = intern(info['root_id'])
1923
 
        search_key_name = intern(info.get('search_key_name', 'plain'))
1924
 
        parent_id_basename_to_file_id = intern(info.get(
1925
 
            'parent_id_basename_to_file_id', None))
1926
 
        if not parent_id_basename_to_file_id.startswith('sha1:'):
 
1781
        revision_id = bytesintern(info[b'revision_id'])
 
1782
        root_id = bytesintern(info[b'root_id'])
 
1783
        search_key_name = bytesintern(info.get(b'search_key_name', b'plain'))
 
1784
        parent_id_basename_to_file_id = bytesintern(info.get(
 
1785
            b'parent_id_basename_to_file_id', None))
 
1786
        if not parent_id_basename_to_file_id.startswith(b'sha1:'):
1927
1787
            raise ValueError('parent_id_basename_to_file_id should be a sha1'
1928
1788
                             ' key not %r' % (parent_id_basename_to_file_id,))
1929
 
        id_to_entry = info['id_to_entry']
1930
 
        if not id_to_entry.startswith('sha1:'):
 
1789
        id_to_entry = info[b'id_to_entry']
 
1790
        if not id_to_entry.startswith(b'sha1:'):
1931
1791
            raise ValueError('id_to_entry should be a sha1'
1932
1792
                             ' key not %r' % (id_to_entry,))
1933
1793
 
1935
1795
        result.revision_id = revision_id
1936
1796
        result.root_id = root_id
1937
1797
        search_key_func = chk_map.search_key_registry.get(
1938
 
                            result._search_key_name)
 
1798
            result._search_key_name.decode("ascii"))
1939
1799
        if parent_id_basename_to_file_id is not None:
1940
1800
            result.parent_id_basename_to_file_id = chk_map.CHKMap(
1941
1801
                chk_store, StaticTuple(parent_id_basename_to_file_id,),
2001
1861
        if entry.parent_id is not None:
2002
1862
            parent_id = entry.parent_id
2003
1863
        else:
2004
 
            parent_id = ''
 
1864
            parent_id = b''
2005
1865
        return StaticTuple(parent_id, entry.name.encode('utf8')).intern()
2006
1866
 
2007
1867
    def __getitem__(self, file_id):
2013
1873
            return result
2014
1874
        try:
2015
1875
            return self._bytes_to_entry(
2016
 
                self.id_to_entry.iteritems([StaticTuple(file_id,)]).next()[1])
 
1876
                next(self.id_to_entry.iteritems([StaticTuple(file_id,)]))[1])
2017
1877
        except StopIteration:
2018
1878
            # really we're passing an inventory, not a tree...
2019
1879
            raise errors.NoSuchId(self, file_id)
2066
1926
 
2067
1927
    def iter_just_entries(self):
2068
1928
        """Iterate over all entries.
2069
 
        
 
1929
 
2070
1930
        Unlike iter_entries(), just the entries are returned (not (path, ie))
2071
1931
        and the order of entries is undefined.
2072
1932
 
2080
1940
                self._fileid_to_entry_cache[file_id] = ie
2081
1941
            yield ie
2082
1942
 
 
1943
    def _preload_cache(self):
 
1944
        """Make sure all file-ids are in _fileid_to_entry_cache"""
 
1945
        if self._fully_cached:
 
1946
            return # No need to do it again
 
1947
        # The optimal sort order is to use iteritems() directly
 
1948
        cache = self._fileid_to_entry_cache
 
1949
        for key, entry in self.id_to_entry.iteritems():
 
1950
            file_id = key[0]
 
1951
            if file_id not in cache:
 
1952
                ie = self._bytes_to_entry(entry)
 
1953
                cache[file_id] = ie
 
1954
            else:
 
1955
                ie = cache[file_id]
 
1956
        last_parent_id = last_parent_ie = None
 
1957
        pid_items = self.parent_id_basename_to_file_id.iteritems()
 
1958
        for key, child_file_id in pid_items:
 
1959
            if key == (b'', b''): # This is the root
 
1960
                if child_file_id != self.root_id:
 
1961
                    raise ValueError('Data inconsistency detected.'
 
1962
                        ' We expected data with key ("","") to match'
 
1963
                        ' the root id, but %s != %s'
 
1964
                        % (child_file_id, self.root_id))
 
1965
                continue
 
1966
            parent_id, basename = key
 
1967
            ie = cache[child_file_id]
 
1968
            if parent_id == last_parent_id:
 
1969
                parent_ie = last_parent_ie
 
1970
            else:
 
1971
                parent_ie = cache[parent_id]
 
1972
            if parent_ie.kind != 'directory':
 
1973
                raise ValueError('Data inconsistency detected.'
 
1974
                    ' An entry in the parent_id_basename_to_file_id map'
 
1975
                    ' has parent_id {%s} but the kind of that object'
 
1976
                    ' is %r not "directory"' % (parent_id, parent_ie.kind))
 
1977
            if parent_ie._children is None:
 
1978
                parent_ie._children = {}
 
1979
            basename = basename.decode('utf-8')
 
1980
            if basename in parent_ie._children:
 
1981
                existing_ie = parent_ie._children[basename]
 
1982
                if existing_ie != ie:
 
1983
                    raise ValueError('Data inconsistency detected.'
 
1984
                        ' Two entries with basename %r were found'
 
1985
                        ' in the parent entry {%s}'
 
1986
                        % (basename, parent_id))
 
1987
            if basename != ie.name:
 
1988
                raise ValueError('Data inconsistency detected.'
 
1989
                    ' In the parent_id_basename_to_file_id map, file_id'
 
1990
                    ' {%s} is listed as having basename %r, but in the'
 
1991
                    ' id_to_entry map it is %r'
 
1992
                    % (child_file_id, basename, ie.name))
 
1993
            parent_ie._children[basename] = ie
 
1994
        self._fully_cached = True
 
1995
 
2083
1996
    def iter_changes(self, basis):
2084
1997
        """Generate a Tree.iter_changes change list between this and basis.
2085
1998
 
2159
2072
 
2160
2073
    def _make_delta(self, old):
2161
2074
        """Make an inventory delta from two inventories."""
2162
 
        if type(old) != CHKInventory:
 
2075
        if not isinstance(old, CHKInventory):
2163
2076
            return CommonInventory._make_delta(self, old)
2164
2077
        delta = []
2165
2078
        for key, old_value, self_value in \
2182
2095
    def path2id(self, relpath):
2183
2096
        """See CommonInventory.path2id()."""
2184
2097
        # TODO: perhaps support negative hits?
 
2098
        if isinstance(relpath, (str, text_type)):
 
2099
            names = osutils.splitpath(relpath)
 
2100
        else:
 
2101
            names = relpath
 
2102
            if relpath == []:
 
2103
                relpath = [""]
 
2104
            relpath = osutils.pathjoin(*relpath)
2185
2105
        result = self._path_to_fileid_cache.get(relpath, None)
2186
2106
        if result is not None:
2187
2107
            return result
2188
 
        if isinstance(relpath, basestring):
2189
 
            names = osutils.splitpath(relpath)
2190
 
        else:
2191
 
            names = relpath
2192
2108
        current_id = self.root_id
2193
2109
        if current_id is None:
2194
2110
            return None
2218
2134
 
2219
2135
    def to_lines(self):
2220
2136
        """Serialise the inventory to lines."""
2221
 
        lines = ["chkinventory:\n"]
 
2137
        lines = [b"chkinventory:\n"]
2222
2138
        if self._search_key_name != 'plain':
2223
2139
            # custom ordering grouping things that don't change together
2224
 
            lines.append('search_key_name: %s\n' % (self._search_key_name,))
2225
 
            lines.append("root_id: %s\n" % self.root_id)
2226
 
            lines.append('parent_id_basename_to_file_id: %s\n' %
 
2140
            lines.append(b'search_key_name: %s\n' % (
 
2141
                self._search_key_name.encode('ascii')))
 
2142
            lines.append(b"root_id: %s\n" % self.root_id)
 
2143
            lines.append(b'parent_id_basename_to_file_id: %s\n' %
2227
2144
                (self.parent_id_basename_to_file_id.key()[0],))
2228
 
            lines.append("revision_id: %s\n" % self.revision_id)
2229
 
            lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
 
2145
            lines.append(b"revision_id: %s\n" % self.revision_id)
 
2146
            lines.append(b"id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2230
2147
        else:
2231
 
            lines.append("revision_id: %s\n" % self.revision_id)
2232
 
            lines.append("root_id: %s\n" % self.root_id)
 
2148
            lines.append(b"revision_id: %s\n" % self.revision_id)
 
2149
            lines.append(b"root_id: %s\n" % self.root_id)
2233
2150
            if self.parent_id_basename_to_file_id is not None:
2234
 
                lines.append('parent_id_basename_to_file_id: %s\n' %
 
2151
                lines.append(b'parent_id_basename_to_file_id: %s\n' %
2235
2152
                    (self.parent_id_basename_to_file_id.key()[0],))
2236
 
            lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
 
2153
            lines.append(b"id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2237
2154
        return lines
2238
2155
 
2239
2156
    @property
2245
2162
class CHKInventoryDirectory(InventoryDirectory):
2246
2163
    """A directory in an inventory."""
2247
2164
 
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']
 
2165
    __slots__ = ['_children', '_chk_inventory']
2252
2166
 
2253
2167
    def __init__(self, file_id, name, parent_id, chk_inventory):
2254
2168
        # Don't call InventoryDirectory.__init__ - it isn't right for this
2255
2169
        # class.
2256
2170
        InventoryEntry.__init__(self, file_id, name, parent_id)
2257
2171
        self._children = None
2258
 
        self.kind = 'directory'
2259
2172
        self._chk_inventory = chk_inventory
2260
2173
 
2261
2174
    @property
2331
2244
        accessed on this platform by the normalized path.
2332
2245
    :return: The NFC normalised version of name.
2333
2246
    """
2334
 
    #------- This has been copied to bzrlib.dirstate.DirState.add, please
 
2247
    #------- This has been copied to breezy.dirstate.DirState.add, please
2335
2248
    # keep them synchronised.
2336
2249
    # we dont import normalized_filename directly because we want to be
2337
2250
    # able to change the implementation at runtime for tests.
2346
2259
    return name
2347
2260
 
2348
2261
 
2349
 
_NAME_RE = None
 
2262
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
2350
2263
 
2351
2264
def is_valid_name(name):
2352
 
    global _NAME_RE
2353
 
    if _NAME_RE is None:
2354
 
        _NAME_RE = re.compile(r'^[^/\\]+$')
2355
 
 
2356
2265
    return bool(_NAME_RE.match(name))
2357
2266
 
2358
2267
 
2413
2322
        if item[2] is None:
2414
2323
            raise errors.InconsistentDelta(item[0] or item[1], item[2],
2415
2324
                "entry with file_id None %r" % entry)
2416
 
        if type(item[2]) != str:
 
2325
        if not isinstance(item[2], str):
2417
2326
            raise errors.InconsistentDelta(item[0] or item[1], item[2],
2418
2327
                "entry with non bytes file_id %r" % entry)
2419
2328
        yield item
2448
2357
            raise errors.InconsistentDelta(new_path, item[1],
2449
2358
                "new_path with no entry")
2450
2359
        yield item
 
2360
 
 
2361
 
 
2362
def mutable_inventory_from_tree(tree):
 
2363
    """Create a new inventory that has the same contents as a specified tree.
 
2364
 
 
2365
    :param tree: Revision tree to create inventory from
 
2366
    """
 
2367
    entries = tree.iter_entries_by_dir()
 
2368
    inv = Inventory(None, tree.get_revision_id())
 
2369
    for path, inv_entry in entries:
 
2370
        inv.add(inv_entry.copy())
 
2371
    return inv