/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: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
23
23
# But those depend on its position within a particular inventory, and
24
24
# it would be nice not to need to hold the backpointer here.
25
25
 
26
 
from __future__ import absolute_import
27
 
 
28
26
# This should really be an id randomly assigned when the tree is
29
27
# created, but it's not for now.
30
 
ROOT_ID = b"TREE_ROOT"
 
28
ROOT_ID = "TREE_ROOT"
31
29
 
32
 
from ..lazy_import import lazy_import
 
30
from bzrlib.lazy_import import lazy_import
33
31
lazy_import(globals(), """
34
32
import collections
 
33
import copy
 
34
import os
 
35
import re
 
36
import tarfile
35
37
 
36
 
from breezy import (
 
38
import bzrlib
 
39
from bzrlib import (
 
40
    chk_map,
 
41
    errors,
37
42
    generate_ids,
38
43
    osutils,
39
 
    )
40
 
from breezy.bzr import (
41
 
    chk_map,
 
44
    symbol_versioning,
42
45
    )
43
46
""")
44
47
 
45
 
from .. import (
46
 
    errors,
47
 
    lazy_regex,
48
 
    trace,
49
 
    )
50
 
from ..sixish import (
51
 
    bytesintern,
52
 
    text_type,
53
 
    viewitems,
54
 
    viewvalues,
55
 
    )
56
 
from ..static_tuple import StaticTuple
 
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
57
55
 
58
56
 
59
57
class InventoryEntry(object):
89
87
    >>> i = Inventory()
90
88
    >>> i.path2id('')
91
89
    'TREE_ROOT'
92
 
    >>> i.add(InventoryDirectory(b'123', 'src', ROOT_ID))
 
90
    >>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
93
91
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
94
 
    >>> i.add(InventoryFile(b'2323', 'hello.c', parent_id='123'))
 
92
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
95
93
    InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None, revision=None)
96
94
    >>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
97
95
    >>> for ix, j in enumerate(i.iter_entries()):
106
104
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
107
105
    >>> i.path2id('src/wibble')
108
106
    '2325'
 
107
    >>> '2325' in i
 
108
    True
109
109
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
110
110
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
111
 
    >>> i.get_entry('2326')
 
111
    >>> i['2326']
112
112
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
113
113
    >>> for path, entry in i.iter_entries():
114
114
    ...     print path
119
119
    src/hello.c
120
120
    src/wibble
121
121
    src/wibble/wibble.c
122
 
    >>> i.id2path(b'2326')
 
122
    >>> i.id2path('2326')
123
123
    'src/wibble/wibble.c'
124
124
    """
125
125
 
131
131
    RENAMED = 'renamed'
132
132
    MODIFIED_AND_RENAMED = 'modified and renamed'
133
133
 
134
 
    __slots__ = ['file_id', 'revision', 'parent_id', 'name']
135
 
 
136
 
    # Attributes that all InventoryEntry instances are expected to have, but
137
 
    # that don't vary for all kinds of entry.  (e.g. symlink_target is only
138
 
    # relevant to InventoryLink, so there's no reason to make every
139
 
    # InventoryFile instance allocate space to hold a value for it.)
140
 
    # Attributes that only vary for files: executable, text_sha1, text_size,
141
 
    # text_id
142
 
    executable = False
143
 
    text_sha1 = None
144
 
    text_size = None
145
 
    text_id = None
146
 
    # Attributes that only vary for symlinks: symlink_target
147
 
    symlink_target = None
148
 
    # Attributes that only vary for tree-references: reference_revision
149
 
    reference_revision = None
 
134
    __slots__ = []
150
135
 
151
136
    def detect_changes(self, old_entry):
152
137
        """Return a (text_modified, meta_modified) from this to old_entry.
157
142
        return False, False
158
143
 
159
144
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
160
 
              output_to, reverse=False):
 
145
             output_to, reverse=False):
161
146
        """Perform a diff between two entries of the same kind."""
162
147
 
163
148
    def parent_candidates(self, previous_inventories):
173
158
        candidates = {}
174
159
        # identify candidate head revision ids.
175
160
        for inv in previous_inventories:
176
 
            try:
177
 
                ie = inv.get_entry(self.file_id)
178
 
            except errors.NoSuchId:
179
 
                pass
180
 
            else:
 
161
            if self.file_id in inv:
 
162
                ie = inv[self.file_id]
181
163
                if ie.revision in candidates:
182
164
                    # same revision value in two different inventories:
183
165
                    # correct possible inconsistencies:
194
176
                    candidates[ie.revision] = ie
195
177
        return candidates
196
178
 
 
179
    @deprecated_method(deprecated_in((1, 6, 0)))
 
180
    def get_tar_item(self, root, dp, now, tree):
 
181
        """Get a tarfile item and a file stream for its content."""
 
182
        item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
 
183
        # TODO: would be cool to actually set it to the timestamp of the
 
184
        # revision it was last changed
 
185
        item.mtime = now
 
186
        fileobj = self._put_in_tar(item, tree)
 
187
        return item, fileobj
 
188
 
197
189
    def has_text(self):
198
190
        """Return true if the object this entry represents has textual data.
199
191
 
205
197
        """
206
198
        return False
207
199
 
208
 
    def __init__(self, file_id, name, parent_id):
 
200
    def __init__(self, file_id, name, parent_id, text_id=None):
209
201
        """Create an InventoryEntry
210
202
 
211
203
        The filename must be a single component, relative to the
212
204
        parent directory; it cannot be a whole path or relative name.
213
205
 
214
 
        >>> e = InventoryFile(b'123', 'hello.c', ROOT_ID)
 
206
        >>> e = InventoryFile('123', 'hello.c', ROOT_ID)
215
207
        >>> e.name
216
208
        'hello.c'
217
209
        >>> e.file_id
218
210
        '123'
219
 
        >>> e = InventoryFile(b'123', 'src/hello.c', ROOT_ID)
 
211
        >>> e = InventoryFile('123', 'src/hello.c', ROOT_ID)
220
212
        Traceback (most recent call last):
221
213
        InvalidEntryName: Invalid entry name: src/hello.c
222
214
        """
223
 
        if u'/' in name:
 
215
        if '/' in name or '\\' in name:
224
216
            raise errors.InvalidEntryName(name=name)
225
 
        if not isinstance(file_id, bytes):
226
 
            raise TypeError(file_id)
 
217
        self.executable = False
 
218
        self.revision = None
 
219
        self.text_sha1 = None
 
220
        self.text_size = None
227
221
        self.file_id = file_id
228
 
        self.revision = None
229
222
        self.name = name
 
223
        self.text_id = text_id
230
224
        self.parent_id = parent_id
 
225
        self.symlink_target = None
 
226
        self.reference_revision = None
231
227
 
232
228
    def kind_character(self):
233
229
        """Return a short kind indicator useful for appending to names."""
234
 
        raise errors.BzrError('unknown kind %r' % self.kind)
 
230
        raise BzrError('unknown kind %r' % self.kind)
235
231
 
236
232
    known_kinds = ('file', 'directory', 'symlink')
237
233
 
 
234
    def _put_in_tar(self, item, tree):
 
235
        """populate item for stashing in a tar, and return the content stream.
 
236
 
 
237
        If no content is available, return None.
 
238
        """
 
239
        raise BzrError("don't know how to export {%s} of kind %r" %
 
240
                       (self.file_id, self.kind))
 
241
 
 
242
    @deprecated_method(deprecated_in((1, 6, 0)))
 
243
    def put_on_disk(self, dest, dp, tree):
 
244
        """Create a representation of self on disk in the prefix dest.
 
245
 
 
246
        This is a template method - implement _put_on_disk in subclasses.
 
247
        """
 
248
        fullpath = osutils.pathjoin(dest, dp)
 
249
        self._put_on_disk(fullpath, tree)
 
250
        # mutter("  export {%s} kind %s to %s", self.file_id,
 
251
        #         self.kind, fullpath)
 
252
 
 
253
    def _put_on_disk(self, fullpath, tree):
 
254
        """Put this entry onto disk at fullpath, from tree tree."""
 
255
        raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
 
256
 
 
257
    def sorted_children(self):
 
258
        return sorted(self.children.items())
 
259
 
238
260
    @staticmethod
239
261
    def versionable_kind(kind):
240
262
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
254
276
        """
255
277
        if self.parent_id is not None:
256
278
            if not inv.has_id(self.parent_id):
257
 
                raise errors.BzrCheckError(
258
 
                    'missing parent {%s} in inventory for revision {%s}' % (
259
 
                        self.parent_id, rev_id))
 
279
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
 
280
                        % (self.parent_id, rev_id))
260
281
        checker._add_entry_to_text_key_references(inv, self)
261
282
        self._check(checker, rev_id)
262
283
 
326
347
        if not isinstance(other, InventoryEntry):
327
348
            return NotImplemented
328
349
 
329
 
        return ((self.file_id == other.file_id) and
330
 
                (self.name == other.name) and
331
 
                (other.symlink_target == self.symlink_target) and
332
 
                (self.text_sha1 == other.text_sha1) and
333
 
                (self.text_size == other.text_size) and
334
 
                (self.text_id == other.text_id) and
335
 
                (self.parent_id == other.parent_id) and
336
 
                (self.kind == other.kind) and
337
 
                (self.revision == other.revision) and
338
 
                (self.executable == other.executable) and
339
 
                (self.reference_revision == other.reference_revision)
 
350
        return ((self.file_id == other.file_id)
 
351
                and (self.name == other.name)
 
352
                and (other.symlink_target == self.symlink_target)
 
353
                and (self.text_sha1 == other.text_sha1)
 
354
                and (self.text_size == other.text_size)
 
355
                and (self.text_id == other.text_id)
 
356
                and (self.parent_id == other.parent_id)
 
357
                and (self.kind == other.kind)
 
358
                and (self.revision == other.revision)
 
359
                and (self.executable == other.executable)
 
360
                and (self.reference_revision == other.reference_revision)
340
361
                )
341
362
 
342
363
    def __ne__(self, other):
376
397
        pass
377
398
 
378
399
 
 
400
class RootEntry(InventoryEntry):
 
401
 
 
402
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
403
                 'text_id', 'parent_id', 'children', 'executable',
 
404
                 'revision', 'symlink_target', 'reference_revision']
 
405
 
 
406
    def _check(self, checker, rev_id):
 
407
        """See InventoryEntry._check"""
 
408
 
 
409
    def __init__(self, file_id):
 
410
        self.file_id = file_id
 
411
        self.children = {}
 
412
        self.kind = 'directory'
 
413
        self.parent_id = None
 
414
        self.name = u''
 
415
        self.revision = None
 
416
        symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
 
417
                               '  Please use InventoryDirectory instead.',
 
418
                               DeprecationWarning, stacklevel=2)
 
419
 
 
420
    def __eq__(self, other):
 
421
        if not isinstance(other, RootEntry):
 
422
            return NotImplemented
 
423
 
 
424
        return (self.file_id == other.file_id) \
 
425
               and (self.children == other.children)
 
426
 
 
427
 
379
428
class InventoryDirectory(InventoryEntry):
380
429
    """A directory in an inventory."""
381
430
 
382
 
    __slots__ = ['children']
383
 
 
384
 
    kind = 'directory'
 
431
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
432
                 'text_id', 'parent_id', 'children', 'executable',
 
433
                 'revision', 'symlink_target', 'reference_revision']
385
434
 
386
435
    def _check(self, checker, rev_id):
387
436
        """See InventoryEntry._check"""
 
437
        if (self.text_sha1 is not None or self.text_size is not None or
 
438
            self.text_id is not None):
 
439
            checker._report_items.append('directory {%s} has text in revision {%s}'
 
440
                                % (self.file_id, rev_id))
388
441
        # In non rich root repositories we do not expect a file graph for the
389
442
        # root.
390
443
        if self.name == '' and not checker.rich_roots:
393
446
        # to provide a per-fileid log. The hash of every directory content is
394
447
        # "da..." below (the sha1sum of '').
395
448
        checker.add_pending_item(rev_id,
396
 
                                 ('texts', self.file_id, self.revision), b'text',
397
 
                                 b'da39a3ee5e6b4b0d3255bfef95601890afd80709')
 
449
            ('texts', self.file_id, self.revision), 'text',
 
450
             'da39a3ee5e6b4b0d3255bfef95601890afd80709')
398
451
 
399
452
    def copy(self):
400
453
        other = InventoryDirectory(self.file_id, self.name, self.parent_id)
406
459
    def __init__(self, file_id, name, parent_id):
407
460
        super(InventoryDirectory, self).__init__(file_id, name, parent_id)
408
461
        self.children = {}
409
 
 
410
 
    def sorted_children(self):
411
 
        return sorted(viewitems(self.children))
 
462
        self.kind = 'directory'
412
463
 
413
464
    def kind_character(self):
414
465
        """See InventoryEntry.kind_character."""
415
466
        return '/'
416
467
 
 
468
    def _put_in_tar(self, item, tree):
 
469
        """See InventoryEntry._put_in_tar."""
 
470
        item.type = tarfile.DIRTYPE
 
471
        fileobj = None
 
472
        item.name += '/'
 
473
        item.size = 0
 
474
        item.mode = 0755
 
475
        return fileobj
 
476
 
 
477
    def _put_on_disk(self, fullpath, tree):
 
478
        """See InventoryEntry._put_on_disk."""
 
479
        os.mkdir(fullpath)
 
480
 
417
481
 
418
482
class InventoryFile(InventoryEntry):
419
483
    """A file in an inventory."""
420
484
 
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
 
485
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
486
                 'text_id', 'parent_id', 'children', 'executable',
 
487
                 'revision', 'symlink_target', 'reference_revision']
431
488
 
432
489
    def _check(self, checker, tree_revision_id):
433
490
        """See InventoryEntry._check"""
434
491
        # TODO: check size too.
435
492
        checker.add_pending_item(tree_revision_id,
436
 
                                 ('texts', self.file_id, self.revision), b'text',
437
 
                                 self.text_sha1)
 
493
            ('texts', self.file_id, self.revision), 'text',
 
494
             self.text_sha1)
438
495
        if self.text_size is None:
439
496
            checker._report_items.append(
440
497
                'fileid {%s} in {%s} has None for text_size' % (self.file_id,
441
 
                                                                tree_revision_id))
 
498
                tree_revision_id))
442
499
 
443
500
    def copy(self):
444
501
        other = InventoryFile(self.file_id, self.name, self.parent_id)
456
513
        return text_modified, meta_modified
457
514
 
458
515
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
459
 
              output_to, reverse=False):
 
516
             output_to, reverse=False):
460
517
        """See InventoryEntry._diff."""
461
 
        from breezy.diff import DiffText
 
518
        from bzrlib.diff import DiffText
462
519
        from_file_id = self.file_id
463
520
        if to_entry:
464
521
            to_file_id = to_entry.file_id
465
 
            to_path = to_tree.id2path(to_file_id)
466
522
        else:
467
523
            to_file_id = None
468
 
            to_path = None
469
 
        if from_file_id is not None:
470
 
            from_path = tree.id2path(from_file_id)
471
 
        else:
472
 
            from_path = None
473
524
        if reverse:
474
525
            to_file_id, from_file_id = from_file_id, to_file_id
475
526
            tree, to_tree = to_tree, tree
476
527
            from_label, to_label = to_label, from_label
477
528
        differ = DiffText(tree, to_tree, output_to, 'utf-8', '', '',
478
529
                          text_diff)
479
 
        return differ.diff_text(from_path, to_path, from_label, to_label,
480
 
                                from_file_id, to_file_id)
 
530
        return differ.diff_text(from_file_id, to_file_id, from_label, to_label)
481
531
 
482
532
    def has_text(self):
483
533
        """See InventoryEntry.has_text."""
484
534
        return True
485
535
 
 
536
    def __init__(self, file_id, name, parent_id):
 
537
        super(InventoryFile, self).__init__(file_id, name, parent_id)
 
538
        self.kind = 'file'
 
539
 
486
540
    def kind_character(self):
487
541
        """See InventoryEntry.kind_character."""
488
542
        return ''
489
543
 
 
544
    def _put_in_tar(self, item, tree):
 
545
        """See InventoryEntry._put_in_tar."""
 
546
        item.type = tarfile.REGTYPE
 
547
        fileobj = tree.get_file(self.file_id)
 
548
        item.size = self.text_size
 
549
        if tree.is_executable(self.file_id):
 
550
            item.mode = 0755
 
551
        else:
 
552
            item.mode = 0644
 
553
        return fileobj
 
554
 
 
555
    def _put_on_disk(self, fullpath, tree):
 
556
        """See InventoryEntry._put_on_disk."""
 
557
        osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
 
558
        if tree.is_executable(self.file_id):
 
559
            os.chmod(fullpath, 0755)
 
560
 
490
561
    def _read_tree_state(self, path, work_tree):
491
562
        """See InventoryEntry._read_tree_state."""
492
 
        self.text_sha1 = work_tree.get_file_sha1(path, self.file_id)
 
563
        self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
493
564
        # FIXME: 20050930 probe for the text size when getting sha1
494
565
        # in _read_tree_state
495
 
        self.executable = work_tree.is_executable(path, self.file_id)
 
566
        self.executable = work_tree.is_executable(self.file_id, path=path)
496
567
 
497
568
    def __repr__(self):
498
569
        return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s, revision=%s)"
524
595
class InventoryLink(InventoryEntry):
525
596
    """A file in an inventory."""
526
597
 
527
 
    __slots__ = ['symlink_target']
528
 
 
529
 
    kind = 'symlink'
530
 
 
531
 
    def __init__(self, file_id, name, parent_id):
532
 
        super(InventoryLink, self).__init__(file_id, name, parent_id)
533
 
        self.symlink_target = None
 
598
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
599
                 'text_id', 'parent_id', 'children', 'executable',
 
600
                 'revision', 'symlink_target', 'reference_revision']
534
601
 
535
602
    def _check(self, checker, tree_revision_id):
536
603
        """See InventoryEntry._check"""
 
604
        if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
 
605
            checker._report_items.append(
 
606
               'symlink {%s} has text in revision {%s}'
 
607
                    % (self.file_id, tree_revision_id))
537
608
        if self.symlink_target is None:
538
609
            checker._report_items.append(
539
610
                'symlink {%s} has no target in revision {%s}'
540
 
                % (self.file_id, tree_revision_id))
 
611
                    % (self.file_id, tree_revision_id))
541
612
        # Symlinks are stored as ''
542
613
        checker.add_pending_item(tree_revision_id,
543
 
                                 ('texts', self.file_id, self.revision), b'text',
544
 
                                 b'da39a3ee5e6b4b0d3255bfef95601890afd80709')
 
614
            ('texts', self.file_id, self.revision), 'text',
 
615
             'da39a3ee5e6b4b0d3255bfef95601890afd80709')
545
616
 
546
617
    def copy(self):
547
618
        other = InventoryLink(self.file_id, self.name, self.parent_id)
554
625
        # FIXME: which _modified field should we use ? RBC 20051003
555
626
        text_modified = (self.symlink_target != old_entry.symlink_target)
556
627
        if text_modified:
557
 
            trace.mutter("    symlink target changed")
 
628
            mutter("    symlink target changed")
558
629
        meta_modified = False
559
630
        return text_modified, meta_modified
560
631
 
561
632
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
562
 
              output_to, reverse=False):
 
633
             output_to, reverse=False):
563
634
        """See InventoryEntry._diff."""
564
 
        from breezy.diff import DiffSymlink
 
635
        from bzrlib.diff import DiffSymlink
565
636
        old_target = self.symlink_target
566
637
        if to_entry is not None:
567
638
            new_target = to_entry.symlink_target
577
648
        differ = DiffSymlink(old_tree, new_tree, output_to)
578
649
        return differ.diff_symlink(old_target, new_target)
579
650
 
 
651
    def __init__(self, file_id, name, parent_id):
 
652
        super(InventoryLink, self).__init__(file_id, name, parent_id)
 
653
        self.kind = 'symlink'
 
654
 
580
655
    def kind_character(self):
581
656
        """See InventoryEntry.kind_character."""
582
657
        return ''
583
658
 
 
659
    def _put_in_tar(self, item, tree):
 
660
        """See InventoryEntry._put_in_tar."""
 
661
        item.type = tarfile.SYMTYPE
 
662
        fileobj = None
 
663
        item.size = 0
 
664
        item.mode = 0755
 
665
        item.linkname = self.symlink_target
 
666
        return fileobj
 
667
 
 
668
    def _put_on_disk(self, fullpath, tree):
 
669
        """See InventoryEntry._put_on_disk."""
 
670
        try:
 
671
            os.symlink(self.symlink_target, fullpath)
 
672
        except OSError,e:
 
673
            raise BzrError("Failed to create symlink %r -> %r, error: %s" % (fullpath, self.symlink_target, e))
 
674
 
584
675
    def _read_tree_state(self, path, work_tree):
585
676
        """See InventoryEntry._read_tree_state."""
586
 
        self.symlink_target = work_tree.get_symlink_target(
587
 
            work_tree.id2path(self.file_id), self.file_id)
 
677
        self.symlink_target = work_tree.get_symlink_target(self.file_id)
588
678
 
589
679
    def _forget_tree_state(self):
590
680
        self.symlink_target = None
599
689
 
600
690
class TreeReference(InventoryEntry):
601
691
 
602
 
    __slots__ = ['reference_revision']
603
 
 
604
692
    kind = 'tree-reference'
605
693
 
606
694
    def __init__(self, file_id, name, parent_id, revision=None,
617
705
        """Populate fields in the inventory entry from the given tree.
618
706
        """
619
707
        self.reference_revision = work_tree.get_reference_revision(
620
 
            path, self.file_id)
 
708
            self.file_id, path)
621
709
 
622
710
    def _forget_tree_state(self):
623
711
        self.reference_revision = None
629
717
            compatible = False
630
718
        return compatible
631
719
 
632
 
    def kind_character(self):
633
 
        """See InventoryEntry.kind_character."""
634
 
        return '+'
635
 
 
636
720
 
637
721
class CommonInventory(object):
638
722
    """Basic inventory logic, defined in terms of primitives like has_id.
649
733
    inserted, other than through the Inventory API.
650
734
    """
651
735
 
 
736
    def __contains__(self, file_id):
 
737
        """True if this entry contains a file with given id.
 
738
 
 
739
        >>> inv = Inventory()
 
740
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
 
741
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
 
742
        >>> '123' in inv
 
743
        True
 
744
        >>> '456' in inv
 
745
        False
 
746
 
 
747
        Note that this method along with __iter__ are not encouraged for use as
 
748
        they are less clear than specific query methods - they may be rmeoved
 
749
        in the future.
 
750
        """
 
751
        return self.has_id(file_id)
 
752
 
652
753
    def has_filename(self, filename):
653
754
        return bool(self.path2id(filename))
654
755
 
656
757
        """Return as a string the path to file_id.
657
758
 
658
759
        >>> i = Inventory()
659
 
        >>> e = i.add(InventoryDirectory(b'src-id', 'src', ROOT_ID))
660
 
        >>> e = i.add(InventoryFile(b'foo-id', 'foo.c', parent_id='src-id'))
661
 
        >>> print i.id2path(b'foo-id')
 
760
        >>> e = i.add(InventoryDirectory('src-id', 'src', ROOT_ID))
 
761
        >>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
 
762
        >>> print i.id2path('foo-id')
662
763
        src/foo.c
663
764
 
664
765
        :raises NoSuchId: If file_id is not present in the inventory.
670
771
 
671
772
    def iter_entries(self, from_dir=None, recursive=True):
672
773
        """Return (path, entry) pairs, in order by name.
673
 
 
 
774
        
674
775
        :param from_dir: if None, start from the root,
675
776
          otherwise start from this directory (either file-id or entry)
676
777
        :param recursive: recurse into directories or not
680
781
                return
681
782
            from_dir = self.root
682
783
            yield '', self.root
683
 
        elif isinstance(from_dir, bytes):
684
 
            from_dir = self.get_entry(from_dir)
 
784
        elif isinstance(from_dir, basestring):
 
785
            from_dir = self[from_dir]
685
786
 
686
787
        # unrolling the recursive called changed the time from
687
788
        # 440ms/663ms (inline/total) to 116ms/116ms
688
 
        children = sorted(viewitems(from_dir.children))
 
789
        children = from_dir.children.items()
 
790
        children.sort()
689
791
        if not recursive:
690
792
            for name, ie in children:
691
793
                yield name, ie
710
812
                    continue
711
813
 
712
814
                # But do this child first
713
 
                new_children = sorted(viewitems(ie.children))
 
815
                new_children = ie.children.items()
 
816
                new_children.sort()
714
817
                new_children = collections.deque(new_children)
715
818
                stack.append((path, new_children))
716
819
                # Break out of inner loop, so that we start outer loop with child
719
822
                # if we finished all children, pop it off the stack
720
823
                stack.pop()
721
824
 
722
 
    def _preload_cache(self):
723
 
        """Populate any caches, we are about to access all items.
724
 
 
725
 
        The default implementation does nothing, because CommonInventory doesn't
726
 
        have a cache.
727
 
        """
728
 
        pass
729
 
 
730
 
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
 
825
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
 
826
        yield_parents=False):
731
827
        """Iterate over the entries in a directory first order.
732
828
 
733
829
        This returns all entries for a directory before returning
735
831
        lexicographically sorted order, and is a hybrid between
736
832
        depth-first and breadth-first.
737
833
 
 
834
        :param yield_parents: If True, yield the parents from the root leading
 
835
            down to specific_file_ids that have been requested. This has no
 
836
            impact if specific_file_ids is None.
738
837
        :return: This yields (path, entry) pairs
739
838
        """
740
839
        if specific_file_ids and not isinstance(specific_file_ids, set):
741
840
            specific_file_ids = set(specific_file_ids)
742
841
        # TODO? Perhaps this should return the from_dir so that the root is
743
842
        # yielded? or maybe an option?
744
 
        if from_dir is None and specific_file_ids is None:
745
 
            # They are iterating from the root, and have not specified any
746
 
            # specific entries to look at. All current callers fully consume the
747
 
            # iterator, so we can safely assume we are accessing all entries
748
 
            self._preload_cache()
749
843
        if from_dir is None:
750
844
            if self.root is None:
751
845
                return
752
846
            # Optimize a common case
753
 
            if (specific_file_ids is not None
754
 
                    and len(specific_file_ids) == 1):
 
847
            if (not yield_parents and specific_file_ids is not None and
 
848
                len(specific_file_ids) == 1):
755
849
                file_id = list(specific_file_ids)[0]
756
 
                if file_id is not None:
757
 
                    try:
758
 
                        path = self.id2path(file_id)
759
 
                    except errors.NoSuchId:
760
 
                        pass
761
 
                    else:
762
 
                        yield path, self.get_entry(file_id)
 
850
                if file_id in self:
 
851
                    yield self.id2path(file_id), self[file_id]
763
852
                return
764
853
            from_dir = self.root
765
 
            if (specific_file_ids is None
766
 
                    or self.root.file_id in specific_file_ids):
 
854
            if (specific_file_ids is None or yield_parents or
 
855
                self.root.file_id in specific_file_ids):
767
856
                yield u'', self.root
768
 
        elif isinstance(from_dir, bytes):
769
 
            from_dir = self.get_entry(from_dir)
770
 
        else:
771
 
            raise TypeError(from_dir)
 
857
        elif isinstance(from_dir, basestring):
 
858
            from_dir = self[from_dir]
772
859
 
773
860
        if specific_file_ids is not None:
774
861
            # TODO: jam 20070302 This could really be done as a loop rather
775
862
            #       than a bunch of recursive calls.
776
863
            parents = set()
777
864
            byid = self
778
 
 
779
865
            def add_ancestors(file_id):
780
 
                if not byid.has_id(file_id):
 
866
                if file_id not in byid:
781
867
                    return
782
 
                parent_id = byid.get_entry(file_id).parent_id
 
868
                parent_id = byid[file_id].parent_id
783
869
                if parent_id is None:
784
870
                    return
785
871
                if parent_id not in parents:
795
881
            cur_relpath, cur_dir = stack.pop()
796
882
 
797
883
            child_dirs = []
798
 
            for child_name, child_ie in sorted(viewitems(cur_dir.children)):
 
884
            for child_name, child_ie in sorted(cur_dir.children.iteritems()):
799
885
 
800
886
                child_relpath = cur_relpath + child_name
801
887
 
802
 
                if (specific_file_ids is None
803
 
                        or child_ie.file_id in specific_file_ids):
 
888
                if (specific_file_ids is None or
 
889
                    child_ie.file_id in specific_file_ids or
 
890
                    (yield_parents and child_ie.file_id in parents)):
804
891
                    yield child_relpath, child_ie
805
892
 
806
893
                if child_ie.kind == 'directory':
807
894
                    if parents is None or child_ie.file_id in parents:
808
 
                        child_dirs.append((child_relpath + '/', child_ie))
 
895
                        child_dirs.append((child_relpath+'/', child_ie))
809
896
            stack.extend(reversed(child_dirs))
810
897
 
811
898
    def _make_delta(self, old):
812
899
        """Make an inventory delta from two inventories."""
813
 
        old_ids = set(old.iter_all_ids())
814
 
        new_ids = set(self.iter_all_ids())
 
900
        old_ids = set(old)
 
901
        new_ids = set(self)
815
902
        adds = new_ids - old_ids
816
903
        deletes = old_ids - new_ids
817
904
        common = old_ids.intersection(new_ids)
819
906
        for file_id in deletes:
820
907
            delta.append((old.id2path(file_id), None, file_id, None))
821
908
        for file_id in adds:
822
 
            delta.append((None, self.id2path(file_id),
823
 
                          file_id, self.get_entry(file_id)))
 
909
            delta.append((None, self.id2path(file_id), file_id, self[file_id]))
824
910
        for file_id in common:
825
 
            if old.get_entry(file_id) != self.get_entry(file_id):
 
911
            if old[file_id] != self[file_id]:
826
912
                delta.append((old.id2path(file_id), self.id2path(file_id),
827
 
                              file_id, self.get_entry(file_id)))
 
913
                    file_id, self[file_id]))
828
914
        return delta
829
915
 
 
916
    def _get_mutable_inventory(self):
 
917
        """Returns a mutable copy of the object.
 
918
 
 
919
        Some inventories are immutable, yet working trees, for example, needs
 
920
        to mutate exisiting inventories instead of creating a new one.
 
921
        """
 
922
        raise NotImplementedError(self._get_mutable_inventory)
 
923
 
830
924
    def make_entry(self, kind, name, parent_id, file_id=None):
831
 
        """Simple thunk to breezy.bzr.inventory.make_entry."""
 
925
        """Simple thunk to bzrlib.inventory.make_entry."""
832
926
        return make_entry(kind, name, parent_id, file_id)
833
927
 
834
928
    def entries(self):
837
931
        This may be faster than iter_entries.
838
932
        """
839
933
        accum = []
840
 
 
841
934
        def descend(dir_ie, dir_path):
842
 
            kids = sorted(viewitems(dir_ie.children))
 
935
            kids = dir_ie.children.items()
 
936
            kids.sort()
843
937
            for name, ie in kids:
844
938
                child_path = osutils.pathjoin(dir_path, name)
845
939
                accum.append((child_path, ie))
846
940
                if ie.kind == 'directory':
847
941
                    descend(ie, child_path)
848
942
 
849
 
        if self.root is not None:
850
 
            descend(self.root, u'')
851
 
        return accum
852
 
 
853
 
    def get_entry_by_path(self, relpath):
854
 
        """Return an inventory entry by path.
 
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'')
 
960
        return accum
 
961
 
 
962
    def path2id(self, relpath):
 
963
        """Walk down through directories to return entry of last component.
855
964
 
856
965
        :param relpath: may be either a list of path components, or a single
857
966
            string, in which case it is automatically split.
861
970
 
862
971
        Returns None IFF the path is not found.
863
972
        """
864
 
        if isinstance(relpath, (str, text_type)):
 
973
        if isinstance(relpath, basestring):
865
974
            names = osutils.splitpath(relpath)
866
975
        else:
867
976
            names = relpath
883
992
            except KeyError:
884
993
                # or raise an error?
885
994
                return None
886
 
        return parent
887
 
 
888
 
    def path2id(self, relpath):
889
 
        """Walk down through directories to return entry of last component.
890
 
 
891
 
        :param relpath: may be either a list of path components, or a single
892
 
            string, in which case it is automatically split.
893
 
 
894
 
        This returns the entry of the last component in the path,
895
 
        which may be either a file or a directory.
896
 
 
897
 
        Returns None IFF the path is not found.
898
 
        """
899
 
        ie = self.get_entry_by_path(relpath)
900
 
        if ie is None:
901
 
            return None
902
 
        return ie.file_id
 
995
 
 
996
        return parent.file_id
903
997
 
904
998
    def filter(self, specific_fileids):
905
999
        """Get an inventory view filtered against a set of file-ids.
919
1013
        entries = self.iter_entries()
920
1014
        if self.root is None:
921
1015
            return Inventory(root_id=None)
922
 
        other = Inventory(next(entries)[1].file_id)
 
1016
        other = Inventory(entries.next()[1].file_id)
923
1017
        other.root.revision = self.root.revision
924
1018
        other.revision_id = self.revision_id
925
1019
        directories_to_expand = set()
926
1020
        for path, entry in entries:
927
1021
            file_id = entry.file_id
928
 
            if (file_id in specific_fileids or
929
 
                    entry.parent_id in directories_to_expand):
 
1022
            if (file_id in specific_fileids
 
1023
                or entry.parent_id in directories_to_expand):
930
1024
                if entry.kind == 'directory':
931
1025
                    directories_to_expand.add(file_id)
932
1026
            elif file_id not in interesting_parents:
957
1051
    returned quickly.
958
1052
 
959
1053
    >>> inv = Inventory()
960
 
    >>> inv.add(InventoryFile(b'123-123', 'hello.c', ROOT_ID))
 
1054
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
961
1055
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
962
 
    >>> inv.get_entry(b'123-123').name
 
1056
    >>> inv['123-123'].name
963
1057
    'hello.c'
964
1058
 
965
1059
    Id's may be looked up from paths:
966
1060
 
967
1061
    >>> inv.path2id('hello.c')
968
1062
    '123-123'
969
 
    >>> inv.has_id(b'123-123')
 
1063
    >>> '123-123' in inv
970
1064
    True
971
1065
 
972
1066
    There are iterators over the contents:
998
1092
        closing = '...}'
999
1093
        contents = repr(self._byid)
1000
1094
        if len(contents) > max_len:
1001
 
            contents = contents[:(max_len - len(closing))] + closing
 
1095
            contents = contents[:(max_len-len(closing))] + closing
1002
1096
        return "<Inventory object at %x, contents=%r>" % (id(self), contents)
1003
1097
 
1004
1098
    def apply_delta(self, delta):
1014
1108
            applied the final inventory must be internally consistent, but it
1015
1109
            is ok to supply changes which, if only half-applied would have an
1016
1110
            invalid result - such as supplying two changes which rename two
1017
 
            files, 'A' and 'B' with each other : [('A', 'B', b'A-id', a_entry),
1018
 
            ('B', 'A', b'B-id', b_entry)].
 
1111
            files, 'A' and 'B' with each other : [('A', 'B', 'A-id', a_entry),
 
1112
            ('B', 'A', 'B-id', b_entry)].
1019
1113
 
1020
1114
            Each change is a tuple, of the form (old_path, new_path, file_id,
1021
1115
            new_entry).
1050
1144
        # facility.
1051
1145
        list(_check_delta_unique_ids(_check_delta_unique_new_paths(
1052
1146
            _check_delta_unique_old_paths(_check_delta_ids_match_entry(
1053
 
                _check_delta_ids_are_valid(
1054
 
                    _check_delta_new_path_entry_both_or_None(
1055
 
                        delta)))))))
 
1147
            _check_delta_ids_are_valid(
 
1148
            _check_delta_new_path_entry_both_or_None(
 
1149
            delta)))))))
1056
1150
 
1057
1151
        children = {}
1058
1152
        # Remove all affected items which were in the original inventory,
1060
1154
        # after their children, which means that everything we examine has no
1061
1155
        # modified children remaining by the time we examine it.
1062
1156
        for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
1063
 
                                         if op is not None), reverse=True):
 
1157
                                        if op is not None), reverse=True):
1064
1158
            # Preserve unaltered children of file_id for later reinsertion.
1065
 
            file_id_children = getattr(self.get_entry(file_id), 'children', {})
 
1159
            file_id_children = getattr(self[file_id], 'children', {})
1066
1160
            if len(file_id_children):
1067
1161
                children[file_id] = file_id_children
1068
1162
            if self.id2path(file_id) != old_path:
1069
1163
                raise errors.InconsistentDelta(old_path, file_id,
1070
 
                                               "Entry was at wrong other path %r." % self.id2path(file_id))
 
1164
                    "Entry was at wrong other path %r." % self.id2path(file_id))
1071
1165
            # Remove file_id and the unaltered children. If file_id is not
1072
1166
            # being deleted it will be reinserted back later.
1073
1167
            self.remove_recursive_id(file_id)
1077
1171
        # the resulting inventory were also modified, are inserted after their
1078
1172
        # parents.
1079
1173
        for new_path, f, new_entry in sorted((np, f, e) for op, np, f, e in
1080
 
                                             delta if np is not None):
 
1174
                                          delta if np is not None):
1081
1175
            if new_entry.kind == 'directory':
1082
1176
                # Pop the child which to allow detection of children whose
1083
1177
                # parents were deleted and which were not reattached to a new
1084
1178
                # parent.
1085
1179
                replacement = InventoryDirectory(new_entry.file_id,
1086
 
                                                 new_entry.name, new_entry.parent_id)
 
1180
                    new_entry.name, new_entry.parent_id)
1087
1181
                replacement.revision = new_entry.revision
1088
1182
                replacement.children = children.pop(replacement.file_id, {})
1089
1183
                new_entry = replacement
1091
1185
                self.add(new_entry)
1092
1186
            except errors.DuplicateFileId:
1093
1187
                raise errors.InconsistentDelta(new_path, new_entry.file_id,
1094
 
                                               "New id is already present in target.")
 
1188
                    "New id is already present in target.")
1095
1189
            except AttributeError:
1096
1190
                raise errors.InconsistentDelta(new_path, new_entry.file_id,
1097
 
                                               "Parent is not a directory.")
 
1191
                    "Parent is not a directory.")
1098
1192
            if self.id2path(new_entry.file_id) != new_path:
1099
1193
                raise errors.InconsistentDelta(new_path, new_entry.file_id,
1100
 
                                               "New path is not consistent with parent path.")
 
1194
                    "New path is not consistent with parent path.")
1101
1195
        if len(children):
1102
1196
            # Get the parent id that was deleted
1103
1197
            parent_id, children = children.popitem()
1104
1198
            raise errors.InconsistentDelta("<deleted>", parent_id,
1105
 
                                           "The file id was deleted but its children were not deleted.")
 
1199
                "The file id was deleted but its children were not deleted.")
1106
1200
 
1107
1201
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1108
1202
                              propagate_caches=False):
1121
1215
        entries = self.iter_entries()
1122
1216
        if self.root is None:
1123
1217
            return Inventory(root_id=None)
1124
 
        other = Inventory(next(entries)[1].file_id)
 
1218
        other = Inventory(entries.next()[1].file_id)
1125
1219
        other.root.revision = self.root.revision
1126
1220
        # copy recursively so we know directories will be added before
1127
1221
        # their children.  There are more efficient ways than this...
1129
1223
            other.add(entry.copy())
1130
1224
        return other
1131
1225
 
1132
 
    def iter_all_ids(self):
 
1226
    def _get_mutable_inventory(self):
 
1227
        """See CommonInventory._get_mutable_inventory."""
 
1228
        return copy.deepcopy(self)
 
1229
 
 
1230
    def __iter__(self):
1133
1231
        """Iterate over all file-ids."""
1134
1232
        return iter(self._byid)
1135
1233
 
1136
1234
    def iter_just_entries(self):
1137
1235
        """Iterate over all entries.
1138
 
 
 
1236
        
1139
1237
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1140
1238
        and the order of entries is undefined.
1141
1239
 
1142
1240
        XXX: We may not want to merge this into bzr.dev.
1143
1241
        """
1144
1242
        if self.root is None:
1145
 
            return ()
1146
 
        return iter(viewvalues(self._byid))
 
1243
            return
 
1244
        for _, ie in self._byid.iteritems():
 
1245
            yield ie
1147
1246
 
1148
1247
    def __len__(self):
1149
1248
        """Returns number of entries."""
1150
1249
        return len(self._byid)
1151
1250
 
1152
 
    def get_entry(self, file_id):
 
1251
    def __getitem__(self, file_id):
1153
1252
        """Return the entry for given file_id.
1154
1253
 
1155
1254
        >>> inv = Inventory()
1156
 
        >>> inv.add(InventoryFile(b'123123', 'hello.c', ROOT_ID))
 
1255
        >>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
1157
1256
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1158
 
        >>> inv.get_entry(b'123123').name
 
1257
        >>> inv['123123'].name
1159
1258
        'hello.c'
1160
1259
        """
1161
 
        if not isinstance(file_id, bytes):
1162
 
            raise TypeError(file_id)
1163
1260
        try:
1164
1261
            return self._byid[file_id]
1165
1262
        except KeyError:
1170
1267
        return self._byid[file_id].kind
1171
1268
 
1172
1269
    def get_child(self, parent_id, filename):
1173
 
        return self.get_entry(parent_id).children.get(filename)
 
1270
        return self[parent_id].children.get(filename)
1174
1271
 
1175
1272
    def _add_child(self, entry):
1176
1273
        """Add an entry to the inventory, without adding it to its parent"""
1177
1274
        if entry.file_id in self._byid:
1178
 
            raise errors.BzrError(
1179
 
                "inventory already contains entry with id {%s}" %
1180
 
                entry.file_id)
 
1275
            raise BzrError("inventory already contains entry with id {%s}" %
 
1276
                           entry.file_id)
1181
1277
        self._byid[entry.file_id] = entry
1182
 
        children = getattr(entry, 'children', {})
1183
 
        if children is not None:
1184
 
            for child in viewvalues(children):
1185
 
                self._add_child(child)
 
1278
        for child in getattr(entry, 'children', {}).itervalues():
 
1279
            self._add_child(child)
1186
1280
        return entry
1187
1281
 
1188
1282
    def add(self, entry):
1189
1283
        """Add entry to inventory.
1190
1284
 
 
1285
        To add  a file to a branch ready to be committed, use Branch.add,
 
1286
        which calls this.
 
1287
 
1191
1288
        :return: entry
1192
1289
        """
1193
1290
        if entry.file_id in self._byid:
1200
1297
                parent = self._byid[entry.parent_id]
1201
1298
            except KeyError:
1202
1299
                raise errors.InconsistentDelta("<unknown>", entry.parent_id,
1203
 
                                               "Parent not in inventory.")
 
1300
                    "Parent not in inventory.")
1204
1301
            if entry.name in parent.children:
1205
1302
                raise errors.InconsistentDelta(
1206
1303
                    self.id2path(parent.children[entry.name].file_id),
1232
1329
        ie = make_entry(kind, parts[-1], parent_id, file_id)
1233
1330
        return self.add(ie)
1234
1331
 
1235
 
    def delete(self, file_id):
 
1332
    def __delitem__(self, file_id):
1236
1333
        """Remove entry by id.
1237
1334
 
1238
1335
        >>> inv = Inventory()
1239
 
        >>> inv.add(InventoryFile(b'123', 'foo.c', ROOT_ID))
 
1336
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1240
1337
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1241
 
        >>> inv.has_id(b'123')
 
1338
        >>> '123' in inv
1242
1339
        True
1243
 
        >>> inv.delete(b'123')
1244
 
        >>> inv.has_id(b'123')
 
1340
        >>> del inv['123']
 
1341
        >>> '123' in inv
1245
1342
        False
1246
1343
        """
1247
 
        ie = self.get_entry(file_id)
 
1344
        ie = self[file_id]
1248
1345
        del self._byid[file_id]
1249
1346
        if ie.parent_id is not None:
1250
 
            del self.get_entry(ie.parent_id).children[ie.name]
 
1347
            del self[ie.parent_id].children[ie.name]
1251
1348
 
1252
1349
    def __eq__(self, other):
1253
1350
        """Compare two sets by comparing their contents.
1256
1353
        >>> i2 = Inventory()
1257
1354
        >>> i1 == i2
1258
1355
        True
1259
 
        >>> i1.add(InventoryFile(b'123', 'foo', ROOT_ID))
 
1356
        >>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1260
1357
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1261
1358
        >>> i1 == i2
1262
1359
        False
1263
 
        >>> i2.add(InventoryFile(b'123', 'foo', ROOT_ID))
 
1360
        >>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1264
1361
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1265
1362
        >>> i1 == i2
1266
1363
        True
1291
1388
 
1292
1389
    def _make_delta(self, old):
1293
1390
        """Make an inventory delta from two inventories."""
1294
 
        old_getter = old.get_entry
1295
 
        new_getter = self.get_entry
1296
 
        old_ids = set(old.iter_all_ids())
1297
 
        new_ids = set(self.iter_all_ids())
 
1391
        old_getter = getattr(old, '_byid', old)
 
1392
        new_getter = self._byid
 
1393
        old_ids = set(old_getter)
 
1394
        new_ids = set(new_getter)
1298
1395
        adds = new_ids - old_ids
1299
1396
        deletes = old_ids - new_ids
1300
1397
        if not adds and not deletes:
1305
1402
        for file_id in deletes:
1306
1403
            delta.append((old.id2path(file_id), None, file_id, None))
1307
1404
        for file_id in adds:
1308
 
            delta.append((None, self.id2path(file_id),
1309
 
                          file_id, self.get_entry(file_id)))
 
1405
            delta.append((None, self.id2path(file_id), file_id, self[file_id]))
1310
1406
        for file_id in common:
1311
 
            new_ie = new_getter(file_id)
1312
 
            old_ie = old_getter(file_id)
 
1407
            new_ie = new_getter[file_id]
 
1408
            old_ie = old_getter[file_id]
1313
1409
            # If xml_serializer returns the cached InventoryEntries (rather
1314
1410
            # than always doing .copy()), inlining the 'is' check saves 2.7M
1315
1411
            # calls to __eq__.  Under lsprof this saves 20s => 6s.
1332
1428
            ie = to_find_delete.pop()
1333
1429
            to_delete.append(ie.file_id)
1334
1430
            if ie.kind == 'directory':
1335
 
                to_find_delete.extend(viewvalues(ie.children))
 
1431
                to_find_delete.extend(ie.children.values())
1336
1432
        for file_id in reversed(to_delete):
1337
 
            ie = self.get_entry(file_id)
 
1433
            ie = self[file_id]
1338
1434
            del self._byid[file_id]
1339
1435
        if ie.parent_id is not None:
1340
 
            del self.get_entry(ie.parent_id).children[ie.name]
 
1436
            del self[ie.parent_id].children[ie.name]
1341
1437
        else:
1342
1438
            self.root = None
1343
1439
 
1350
1446
        """
1351
1447
        new_name = ensure_normalized_name(new_name)
1352
1448
        if not is_valid_name(new_name):
1353
 
            raise errors.BzrError("not an acceptable filename: %r" % new_name)
 
1449
            raise BzrError("not an acceptable filename: %r" % new_name)
1354
1450
 
1355
1451
        new_parent = self._byid[new_parent_id]
1356
1452
        if new_name in new_parent.children:
1357
 
            raise errors.BzrError("%r already exists in %r" %
1358
 
                                  (new_name, self.id2path(new_parent_id)))
 
1453
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
1359
1454
 
1360
1455
        new_parent_idpath = self.get_idpath(new_parent_id)
1361
1456
        if file_id in new_parent_idpath:
1362
 
            raise errors.BzrError(
1363
 
                "cannot move directory %r into a subdirectory of itself, %r"
1364
 
                % (self.id2path(file_id), self.id2path(new_parent_id)))
 
1457
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
 
1458
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1365
1459
 
1366
1460
        file_ie = self._byid[file_id]
1367
1461
        old_parent = self._byid[file_ie.parent_id]
1402
1496
    def __init__(self, search_key_name):
1403
1497
        CommonInventory.__init__(self)
1404
1498
        self._fileid_to_entry_cache = {}
1405
 
        self._fully_cached = False
1406
1499
        self._path_to_fileid_cache = {}
1407
1500
        self._search_key_name = search_key_name
1408
1501
        self.root_id = None
1438
1531
        if entry.parent_id is not None:
1439
1532
            parent_str = entry.parent_id
1440
1533
        else:
1441
 
            parent_str = b''
 
1534
            parent_str = ''
1442
1535
        name_str = entry.name.encode("utf8")
1443
1536
        if entry.kind == 'file':
1444
1537
            if entry.executable:
1445
 
                exec_str = b"Y"
 
1538
                exec_str = "Y"
1446
1539
            else:
1447
 
                exec_str = b"N"
1448
 
            return b"file: %s\n%s\n%s\n%s\n%s\n%d\n%s" % (
 
1540
                exec_str = "N"
 
1541
            return "file: %s\n%s\n%s\n%s\n%s\n%d\n%s" % (
1449
1542
                entry.file_id, parent_str, name_str, entry.revision,
1450
1543
                entry.text_sha1, entry.text_size, exec_str)
1451
1544
        elif entry.kind == 'directory':
1452
 
            return b"dir: %s\n%s\n%s\n%s" % (
 
1545
            return "dir: %s\n%s\n%s\n%s" % (
1453
1546
                entry.file_id, parent_str, name_str, entry.revision)
1454
1547
        elif entry.kind == 'symlink':
1455
 
            return b"symlink: %s\n%s\n%s\n%s\n%s" % (
 
1548
            return "symlink: %s\n%s\n%s\n%s\n%s" % (
1456
1549
                entry.file_id, parent_str, name_str, entry.revision,
1457
1550
                entry.symlink_target.encode("utf8"))
1458
1551
        elif entry.kind == 'tree-reference':
1459
 
            return b"tree: %s\n%s\n%s\n%s\n%s" % (
 
1552
            return "tree: %s\n%s\n%s\n%s\n%s" % (
1460
1553
                entry.file_id, parent_str, name_str, entry.revision,
1461
1554
                entry.reference_revision)
1462
1555
        else:
1480
1573
 
1481
1574
        if given [foo-id] we will include
1482
1575
            TREE_ROOT as interesting parents
1483
 
        and
 
1576
        and 
1484
1577
            foo-id, baz-id, frob-id, fringle-id
1485
1578
        As interesting ids.
1486
1579
        """
1495
1588
            if entry.kind == 'directory':
1496
1589
                directories_to_expand.add(entry.file_id)
1497
1590
            interesting.add(entry.parent_id)
1498
 
            children_of_parent_id.setdefault(entry.parent_id, set()
1499
 
                                             ).add(entry.file_id)
 
1591
            children_of_parent_id.setdefault(entry.parent_id, []
 
1592
                                             ).append(entry.file_id)
1500
1593
 
1501
1594
        # Now, interesting has all of the direct parents, but not the
1502
1595
        # parents of those parents. It also may have some duplicates with
1504
1597
        remaining_parents = interesting.difference(file_ids)
1505
1598
        # When we hit the TREE_ROOT, we'll get an interesting parent of None,
1506
1599
        # but we don't actually want to recurse into that
1507
 
        interesting.add(None)  # this will auto-filter it in the loop
1508
 
        remaining_parents.discard(None)
 
1600
        interesting.add(None) # this will auto-filter it in the loop
 
1601
        remaining_parents.discard(None) 
1509
1602
        while remaining_parents:
1510
1603
            next_parents = set()
1511
1604
            for entry in self._getitems(remaining_parents):
1512
1605
                next_parents.add(entry.parent_id)
1513
 
                children_of_parent_id.setdefault(entry.parent_id, set()
1514
 
                                                 ).add(entry.file_id)
 
1606
                children_of_parent_id.setdefault(entry.parent_id, []
 
1607
                                                 ).append(entry.file_id)
1515
1608
            # Remove any search tips we've already processed
1516
1609
            remaining_parents = next_parents.difference(interesting)
1517
1610
            interesting.update(remaining_parents)
1524
1617
            keys = [StaticTuple(f,).intern() for f in directories_to_expand]
1525
1618
            directories_to_expand = set()
1526
1619
            items = self.parent_id_basename_to_file_id.iteritems(keys)
1527
 
            next_file_ids = {item[1] for item in items}
 
1620
            next_file_ids = set([item[1] for item in items])
1528
1621
            next_file_ids = next_file_ids.difference(interesting)
1529
1622
            interesting.update(next_file_ids)
1530
1623
            for entry in self._getitems(next_file_ids):
1531
1624
                if entry.kind == 'directory':
1532
1625
                    directories_to_expand.add(entry.file_id)
1533
 
                children_of_parent_id.setdefault(entry.parent_id, set()
1534
 
                                                 ).add(entry.file_id)
 
1626
                children_of_parent_id.setdefault(entry.parent_id, []
 
1627
                                                 ).append(entry.file_id)
1535
1628
        return interesting, children_of_parent_id
1536
1629
 
1537
1630
    def filter(self, specific_fileids):
1544
1637
        """
1545
1638
        (interesting,
1546
1639
         parent_to_children) = self._expand_fileids_to_parents_and_children(
1547
 
            specific_fileids)
 
1640
                                specific_fileids)
1548
1641
        # There is some overlap here, but we assume that all interesting items
1549
1642
        # are in the _fileid_to_entry_cache because we had to read them to
1550
1643
        # determine if they were a dir we wanted to recurse, or just a file
1559
1652
            # parent_to_children with at least the tree root.)
1560
1653
            return other
1561
1654
        cache = self._fileid_to_entry_cache
1562
 
        remaining_children = collections.deque(
1563
 
            parent_to_children[self.root_id])
 
1655
        try:
 
1656
            remaining_children = collections.deque(parent_to_children[self.root_id])
 
1657
        except:
 
1658
            import pdb; pdb.set_trace()
 
1659
            raise
1564
1660
        while remaining_children:
1565
1661
            file_id = remaining_children.popleft()
1566
1662
            ie = cache[file_id]
1567
1663
            if ie.kind == 'directory':
1568
 
                ie = ie.copy()  # We create a copy to depopulate the .children attribute
 
1664
                ie = ie.copy() # We create a copy to depopulate the .children attribute
1569
1665
            # TODO: depending on the uses of 'other' we should probably alwyas
1570
1666
            #       '.copy()' to prevent someone from mutating other and
1571
1667
            #       invaliding our internal cache
1575
1671
        return other
1576
1672
 
1577
1673
    @staticmethod
1578
 
    def _bytes_to_utf8name_key(data):
1579
 
        """Get the file_id, revision_id key out of data."""
 
1674
    def _bytes_to_utf8name_key(bytes):
 
1675
        """Get the file_id, revision_id key out of bytes."""
1580
1676
        # We don't normally care about name, except for times when we want
1581
1677
        # to filter out empty names because of non rich-root...
1582
 
        sections = data.split(b'\n')
1583
 
        kind, file_id = sections[0].split(b': ')
1584
 
        return (sections[2], bytesintern(file_id), bytesintern(sections[3]))
 
1678
        sections = bytes.split('\n')
 
1679
        kind, file_id = sections[0].split(': ')
 
1680
        return (sections[2], intern(file_id), intern(sections[3]))
1585
1681
 
1586
1682
    def _bytes_to_entry(self, bytes):
1587
1683
        """Deserialise a serialised entry."""
1588
 
        sections = bytes.split(b'\n')
1589
 
        if sections[0].startswith(b"file: "):
 
1684
        sections = bytes.split('\n')
 
1685
        if sections[0].startswith("file: "):
1590
1686
            result = InventoryFile(sections[0][6:],
1591
 
                                   sections[2].decode('utf8'),
1592
 
                                   sections[1])
 
1687
                sections[2].decode('utf8'),
 
1688
                sections[1])
1593
1689
            result.text_sha1 = sections[4]
1594
1690
            result.text_size = int(sections[5])
1595
 
            result.executable = sections[6] == b"Y"
1596
 
        elif sections[0].startswith(b"dir: "):
 
1691
            result.executable = sections[6] == "Y"
 
1692
        elif sections[0].startswith("dir: "):
1597
1693
            result = CHKInventoryDirectory(sections[0][5:],
1598
 
                                           sections[2].decode('utf8'),
1599
 
                                           sections[1], self)
1600
 
        elif sections[0].startswith(b"symlink: "):
 
1694
                sections[2].decode('utf8'),
 
1695
                sections[1], self)
 
1696
        elif sections[0].startswith("symlink: "):
1601
1697
            result = InventoryLink(sections[0][9:],
1602
 
                                   sections[2].decode('utf8'),
1603
 
                                   sections[1])
 
1698
                sections[2].decode('utf8'),
 
1699
                sections[1])
1604
1700
            result.symlink_target = sections[4].decode('utf8')
1605
 
        elif sections[0].startswith(b"tree: "):
 
1701
        elif sections[0].startswith("tree: "):
1606
1702
            result = TreeReference(sections[0][6:],
1607
 
                                   sections[2].decode('utf8'),
1608
 
                                   sections[1])
 
1703
                sections[2].decode('utf8'),
 
1704
                sections[1])
1609
1705
            result.reference_revision = sections[4]
1610
1706
        else:
1611
1707
            raise ValueError("Not a serialised entry %r" % bytes)
1612
 
        result.file_id = bytesintern(result.file_id)
1613
 
        result.revision = bytesintern(sections[3])
1614
 
        if result.parent_id == b'':
 
1708
        result.file_id = intern(result.file_id)
 
1709
        result.revision = intern(sections[3])
 
1710
        if result.parent_id == '':
1615
1711
            result.parent_id = None
1616
1712
        self._fileid_to_entry_cache[result.file_id] = result
1617
1713
        return result
1618
1714
 
 
1715
    def _get_mutable_inventory(self):
 
1716
        """See CommonInventory._get_mutable_inventory."""
 
1717
        entries = self.iter_entries()
 
1718
        inv = Inventory(None, self.revision_id)
 
1719
        for path, inv_entry in entries:
 
1720
            inv.add(inv_entry.copy())
 
1721
        return inv
 
1722
 
1619
1723
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1620
 
                              propagate_caches=False):
 
1724
        propagate_caches=False):
1621
1725
        """Create a new CHKInventory by applying inventory_delta to this one.
1622
1726
 
1623
1727
        See the inventory developers documentation for the theory behind
1634
1738
        result = CHKInventory(self._search_key_name)
1635
1739
        if propagate_caches:
1636
1740
            # Just propagate the path-to-fileid cache for now
1637
 
            result._path_to_fileid_cache = self._path_to_fileid_cache.copy()
1638
 
        search_key_func = chk_map.search_key_registry.get(
1639
 
            self._search_key_name)
 
1741
            result._path_to_fileid_cache = dict(self._path_to_fileid_cache.iteritems())
 
1742
        search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1640
1743
        self.id_to_entry._ensure_root()
1641
1744
        maximum_size = self.id_to_entry._root_node.maximum_size
1642
1745
        result.revision_id = new_revision_id
1719
1822
                old_key = StaticTuple(file_id,)
1720
1823
                if self.id2path(file_id) != old_path:
1721
1824
                    raise errors.InconsistentDelta(old_path, file_id,
1722
 
                                                   "Entry was at wrong other path %r." %
1723
 
                                                   self.id2path(file_id))
 
1825
                        "Entry was at wrong other path %r." %
 
1826
                        self.id2path(file_id))
1724
1827
                altered.add(file_id)
1725
1828
            id_to_entry_delta.append(StaticTuple(old_key, new_key, new_value))
1726
1829
            if result.parent_id_basename_to_file_id is not None:
1728
1831
                if old_path is None:
1729
1832
                    old_key = None
1730
1833
                else:
1731
 
                    old_entry = self.get_entry(file_id)
 
1834
                    old_entry = self[file_id]
1732
1835
                    old_key = self._parent_id_basename_key(old_entry)
1733
1836
                if new_path is None:
1734
1837
                    new_key = None
1749
1852
                            new_key, [None, None])[1] = new_value
1750
1853
        # validate that deletes are complete.
1751
1854
        for file_id in deletes:
1752
 
            entry = self.get_entry(file_id)
 
1855
            entry = self[file_id]
1753
1856
            if entry.kind != 'directory':
1754
1857
                continue
1755
1858
            # This loop could potentially be better by using the id_basename
1756
1859
            # map to just get the child file ids.
1757
 
            for child in viewvalues(entry.children):
 
1860
            for child in entry.children.values():
1758
1861
                if child.file_id not in altered:
1759
1862
                    raise errors.InconsistentDelta(self.id2path(child.file_id),
1760
 
                                                   child.file_id, "Child not deleted or reparented when "
1761
 
                                                   "parent deleted.")
 
1863
                        child.file_id, "Child not deleted or reparented when "
 
1864
                        "parent deleted.")
1762
1865
        result.id_to_entry.apply_delta(id_to_entry_delta)
1763
1866
        if parent_id_basename_delta:
1764
1867
            # Transform the parent_id_basename delta data into a linear delta
1766
1869
            # re-keying, but its simpler to just output that as a delete+add
1767
1870
            # to spend less time calculating the delta.
1768
1871
            delta_list = []
1769
 
            for key, (old_key, value) in viewitems(parent_id_basename_delta):
 
1872
            for key, (old_key, value) in parent_id_basename_delta.iteritems():
1770
1873
                if value is not None:
1771
1874
                    delta_list.append((old_key, key, value))
1772
1875
                else:
1775
1878
        parents.discard(('', None))
1776
1879
        for parent_path, parent in parents:
1777
1880
            try:
1778
 
                if result.get_entry(parent).kind != 'directory':
 
1881
                if result[parent].kind != 'directory':
1779
1882
                    raise errors.InconsistentDelta(result.id2path(parent), parent,
1780
 
                                                   'Not a directory, but given children')
 
1883
                        'Not a directory, but given children')
1781
1884
            except errors.NoSuchId:
1782
1885
                raise errors.InconsistentDelta("<unknown>", parent,
1783
 
                                               "Parent is not present in resulting inventory.")
 
1886
                    "Parent is not present in resulting inventory.")
1784
1887
            if result.path2id(parent_path) != parent:
1785
1888
                raise errors.InconsistentDelta(parent_path, parent,
1786
 
                                               "Parent has wrong path %r." % result.path2id(parent_path))
 
1889
                    "Parent has wrong path %r." % result.path2id(parent_path))
1787
1890
        return result
1788
1891
 
1789
1892
    @classmethod
1796
1899
            for.
1797
1900
        :return: A CHKInventory
1798
1901
        """
1799
 
        lines = bytes.split(b'\n')
1800
 
        if lines[-1] != b'':
 
1902
        lines = bytes.split('\n')
 
1903
        if lines[-1] != '':
1801
1904
            raise AssertionError('bytes to deserialize must end with an eol')
1802
1905
        lines.pop()
1803
 
        if lines[0] != b'chkinventory:':
 
1906
        if lines[0] != 'chkinventory:':
1804
1907
            raise ValueError("not a serialised CHKInventory: %r" % bytes)
1805
1908
        info = {}
1806
 
        allowed_keys = frozenset((b'root_id', b'revision_id',
1807
 
                                  b'parent_id_basename_to_file_id',
1808
 
                                  b'search_key_name', b'id_to_entry'))
 
1909
        allowed_keys = frozenset(['root_id', 'revision_id', 'search_key_name',
 
1910
                                  'parent_id_basename_to_file_id',
 
1911
                                  'id_to_entry'])
1809
1912
        for line in lines[1:]:
1810
 
            key, value = line.split(b': ', 1)
 
1913
            key, value = line.split(': ', 1)
1811
1914
            if key not in allowed_keys:
1812
1915
                raise errors.BzrError('Unknown key in inventory: %r\n%r'
1813
1916
                                      % (key, bytes))
1815
1918
                raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1816
1919
                                      % (key, bytes))
1817
1920
            info[key] = value
1818
 
        revision_id = bytesintern(info[b'revision_id'])
1819
 
        root_id = bytesintern(info[b'root_id'])
1820
 
        search_key_name = bytesintern(info.get(b'search_key_name', b'plain'))
1821
 
        parent_id_basename_to_file_id = bytesintern(info.get(
1822
 
            b'parent_id_basename_to_file_id', None))
1823
 
        if not parent_id_basename_to_file_id.startswith(b'sha1:'):
 
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:'):
1824
1927
            raise ValueError('parent_id_basename_to_file_id should be a sha1'
1825
1928
                             ' key not %r' % (parent_id_basename_to_file_id,))
1826
 
        id_to_entry = info[b'id_to_entry']
1827
 
        if not id_to_entry.startswith(b'sha1:'):
 
1929
        id_to_entry = info['id_to_entry']
 
1930
        if not id_to_entry.startswith('sha1:'):
1828
1931
            raise ValueError('id_to_entry should be a sha1'
1829
1932
                             ' key not %r' % (id_to_entry,))
1830
1933
 
1832
1935
        result.revision_id = revision_id
1833
1936
        result.root_id = root_id
1834
1937
        search_key_func = chk_map.search_key_registry.get(
1835
 
            result._search_key_name)
 
1938
                            result._search_key_name)
1836
1939
        if parent_id_basename_to_file_id is not None:
1837
1940
            result.parent_id_basename_to_file_id = chk_map.CHKMap(
1838
1941
                chk_store, StaticTuple(parent_id_basename_to_file_id,),
1845
1948
                                            search_key_func=search_key_func)
1846
1949
        if (result.revision_id,) != expected_revision_id:
1847
1950
            raise ValueError("Mismatched revision id and expected: %r, %r" %
1848
 
                             (result.revision_id, expected_revision_id))
 
1951
                (result.revision_id, expected_revision_id))
1849
1952
        return result
1850
1953
 
1851
1954
    @classmethod
1852
 
    def from_inventory(klass, chk_store, inventory, maximum_size=0, search_key_name=b'plain'):
 
1955
    def from_inventory(klass, chk_store, inventory, maximum_size=0, search_key_name='plain'):
1853
1956
        """Create a CHKInventory from an existing inventory.
1854
1957
 
1855
1958
        The content of inventory is copied into the chk_store, and a
1875
1978
            parent_id_basename_dict[p_id_key] = entry.file_id
1876
1979
 
1877
1980
        result._populate_from_dicts(chk_store, id_to_entry_dict,
1878
 
                                    parent_id_basename_dict, maximum_size=maximum_size)
 
1981
            parent_id_basename_dict, maximum_size=maximum_size)
1879
1982
        return result
1880
1983
 
1881
1984
    def _populate_from_dicts(self, chk_store, id_to_entry_dict,
1882
1985
                             parent_id_basename_dict, maximum_size):
1883
 
        search_key_func = chk_map.search_key_registry.get(
1884
 
            self._search_key_name)
 
1986
        search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1885
1987
        root_key = chk_map.CHKMap.from_dict(chk_store, id_to_entry_dict,
1886
 
                                            maximum_size=maximum_size, key_width=1,
1887
 
                                            search_key_func=search_key_func)
 
1988
                   maximum_size=maximum_size, key_width=1,
 
1989
                   search_key_func=search_key_func)
1888
1990
        self.id_to_entry = chk_map.CHKMap(chk_store, root_key,
1889
1991
                                          search_key_func)
1890
1992
        root_key = chk_map.CHKMap.from_dict(chk_store,
1891
 
                                            parent_id_basename_dict,
1892
 
                                            maximum_size=maximum_size, key_width=2,
1893
 
                                            search_key_func=search_key_func)
 
1993
                   parent_id_basename_dict,
 
1994
                   maximum_size=maximum_size, key_width=2,
 
1995
                   search_key_func=search_key_func)
1894
1996
        self.parent_id_basename_to_file_id = chk_map.CHKMap(chk_store,
1895
 
                                                            root_key, search_key_func)
 
1997
                                                    root_key, search_key_func)
1896
1998
 
1897
1999
    def _parent_id_basename_key(self, entry):
1898
2000
        """Create a key for a entry in a parent_id_basename_to_file_id index."""
1899
2001
        if entry.parent_id is not None:
1900
2002
            parent_id = entry.parent_id
1901
2003
        else:
1902
 
            parent_id = b''
 
2004
            parent_id = ''
1903
2005
        return StaticTuple(parent_id, entry.name.encode('utf8')).intern()
1904
2006
 
1905
 
    def get_entry(self, file_id):
 
2007
    def __getitem__(self, file_id):
1906
2008
        """map a single file_id -> InventoryEntry."""
1907
2009
        if file_id is None:
1908
2010
            raise errors.NoSuchId(self, file_id)
1911
2013
            return result
1912
2014
        try:
1913
2015
            return self._bytes_to_entry(
1914
 
                next(self.id_to_entry.iteritems([StaticTuple(file_id,)]))[1])
 
2016
                self.id_to_entry.iteritems([StaticTuple(file_id,)]).next()[1])
1915
2017
        except StopIteration:
1916
2018
            # really we're passing an inventory, not a tree...
1917
2019
            raise errors.NoSuchId(self, file_id)
1918
2020
 
1919
2021
    def _getitems(self, file_ids):
1920
 
        """Similar to get_entry, but lets you query for multiple.
1921
 
 
 
2022
        """Similar to __getitem__, but lets you query for multiple.
 
2023
        
1922
2024
        The returned order is undefined. And currently if an item doesn't
1923
2025
        exist, it isn't included in the output.
1924
2026
        """
1951
2053
        """Yield the parents of file_id up to the root."""
1952
2054
        while file_id is not None:
1953
2055
            try:
1954
 
                ie = self.get_entry(file_id)
 
2056
                ie = self[file_id]
1955
2057
            except KeyError:
1956
2058
                raise errors.NoSuchId(tree=self, file_id=file_id)
1957
2059
            yield ie
1958
2060
            file_id = ie.parent_id
1959
2061
 
1960
 
    def iter_all_ids(self):
 
2062
    def __iter__(self):
1961
2063
        """Iterate over all file-ids."""
1962
2064
        for key, _ in self.id_to_entry.iteritems():
1963
2065
            yield key[-1]
1964
2066
 
1965
2067
    def iter_just_entries(self):
1966
2068
        """Iterate over all entries.
1967
 
 
 
2069
        
1968
2070
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1969
2071
        and the order of entries is undefined.
1970
2072
 
1978
2080
                self._fileid_to_entry_cache[file_id] = ie
1979
2081
            yield ie
1980
2082
 
1981
 
    def _preload_cache(self):
1982
 
        """Make sure all file-ids are in _fileid_to_entry_cache"""
1983
 
        if self._fully_cached:
1984
 
            return  # No need to do it again
1985
 
        # The optimal sort order is to use iteritems() directly
1986
 
        cache = self._fileid_to_entry_cache
1987
 
        for key, entry in self.id_to_entry.iteritems():
1988
 
            file_id = key[0]
1989
 
            if file_id not in cache:
1990
 
                ie = self._bytes_to_entry(entry)
1991
 
                cache[file_id] = ie
1992
 
            else:
1993
 
                ie = cache[file_id]
1994
 
        last_parent_id = last_parent_ie = None
1995
 
        pid_items = self.parent_id_basename_to_file_id.iteritems()
1996
 
        for key, child_file_id in pid_items:
1997
 
            if key == (b'', b''):  # This is the root
1998
 
                if child_file_id != self.root_id:
1999
 
                    raise ValueError('Data inconsistency detected.'
2000
 
                                     ' We expected data with key ("","") to match'
2001
 
                                     ' the root id, but %s != %s'
2002
 
                                     % (child_file_id, self.root_id))
2003
 
                continue
2004
 
            parent_id, basename = key
2005
 
            ie = cache[child_file_id]
2006
 
            if parent_id == last_parent_id:
2007
 
                parent_ie = last_parent_ie
2008
 
            else:
2009
 
                parent_ie = cache[parent_id]
2010
 
            if parent_ie.kind != 'directory':
2011
 
                raise ValueError('Data inconsistency detected.'
2012
 
                                 ' An entry in the parent_id_basename_to_file_id map'
2013
 
                                 ' has parent_id {%s} but the kind of that object'
2014
 
                                 ' is %r not "directory"' % (parent_id, parent_ie.kind))
2015
 
            if parent_ie._children is None:
2016
 
                parent_ie._children = {}
2017
 
            basename = basename.decode('utf-8')
2018
 
            if basename in parent_ie._children:
2019
 
                existing_ie = parent_ie._children[basename]
2020
 
                if existing_ie != ie:
2021
 
                    raise ValueError('Data inconsistency detected.'
2022
 
                                     ' Two entries with basename %r were found'
2023
 
                                     ' in the parent entry {%s}'
2024
 
                                     % (basename, parent_id))
2025
 
            if basename != ie.name:
2026
 
                raise ValueError('Data inconsistency detected.'
2027
 
                                 ' In the parent_id_basename_to_file_id map, file_id'
2028
 
                                 ' {%s} is listed as having basename %r, but in the'
2029
 
                                 ' id_to_entry map it is %r'
2030
 
                                 % (child_file_id, basename, ie.name))
2031
 
            parent_ie._children[basename] = ie
2032
 
        self._fully_cached = True
2033
 
 
2034
2083
    def iter_changes(self, basis):
2035
2084
        """Generate a Tree.iter_changes change list between this and basis.
2036
2085
 
2042
2091
        # changed_content, versioned, parent, name, kind,
2043
2092
        # executable)
2044
2093
        for key, basis_value, self_value in \
2045
 
                self.id_to_entry.iter_changes(basis.id_to_entry):
 
2094
            self.id_to_entry.iter_changes(basis.id_to_entry):
2046
2095
            file_id = key[0]
2047
2096
            if basis_value is not None:
2048
2097
                basis_entry = basis._bytes_to_entry(basis_value)
2081
2130
            if kind[0] != kind[1]:
2082
2131
                changed_content = True
2083
2132
            elif kind[0] == 'file':
2084
 
                if (self_entry.text_size != basis_entry.text_size
2085
 
                        or self_entry.text_sha1 != basis_entry.text_sha1):
 
2133
                if (self_entry.text_size != basis_entry.text_size or
 
2134
                    self_entry.text_sha1 != basis_entry.text_sha1):
2086
2135
                    changed_content = True
2087
2136
            elif kind[0] == 'symlink':
2088
2137
                if self_entry.symlink_target != basis_entry.symlink_target:
2089
2138
                    changed_content = True
2090
2139
            elif kind[0] == 'tree-reference':
2091
 
                if (self_entry.reference_revision
2092
 
                        != basis_entry.reference_revision):
 
2140
                if (self_entry.reference_revision !=
 
2141
                    basis_entry.reference_revision):
2093
2142
                    changed_content = True
2094
2143
            parent = (basis_parent, self_parent)
2095
2144
            name = (basis_name, self_name)
2096
2145
            executable = (basis_executable, self_executable)
2097
 
            if (not changed_content and
2098
 
                parent[0] == parent[1] and
2099
 
                name[0] == name[1] and
2100
 
                    executable[0] == executable[1]):
 
2146
            if (not changed_content
 
2147
                and parent[0] == parent[1]
 
2148
                and name[0] == name[1]
 
2149
                and executable[0] == executable[1]):
2101
2150
                # Could happen when only the revision changed for a directory
2102
2151
                # for instance.
2103
2152
                continue
2104
2153
            yield (file_id, (path_in_source, path_in_target), changed_content,
2105
 
                   versioned, parent, name, kind, executable)
 
2154
                versioned, parent, name, kind, executable)
2106
2155
 
2107
2156
    def __len__(self):
2108
2157
        """Return the number of entries in the inventory."""
2110
2159
 
2111
2160
    def _make_delta(self, old):
2112
2161
        """Make an inventory delta from two inventories."""
2113
 
        if not isinstance(old, CHKInventory):
 
2162
        if type(old) != CHKInventory:
2114
2163
            return CommonInventory._make_delta(self, old)
2115
2164
        delta = []
2116
2165
        for key, old_value, self_value in \
2117
 
                self.id_to_entry.iter_changes(old.id_to_entry):
 
2166
            self.id_to_entry.iter_changes(old.id_to_entry):
2118
2167
            file_id = key[0]
2119
2168
            if old_value is not None:
2120
2169
                old_path = old.id2path(file_id)
2133
2182
    def path2id(self, relpath):
2134
2183
        """See CommonInventory.path2id()."""
2135
2184
        # TODO: perhaps support negative hits?
2136
 
        if isinstance(relpath, (str, text_type)):
2137
 
            names = osutils.splitpath(relpath)
2138
 
        else:
2139
 
            names = relpath
2140
 
            if relpath == []:
2141
 
                relpath = [""]
2142
 
            relpath = osutils.pathjoin(*relpath)
2143
2185
        result = self._path_to_fileid_cache.get(relpath, None)
2144
2186
        if result is not None:
2145
2187
            return result
 
2188
        if isinstance(relpath, basestring):
 
2189
            names = osutils.splitpath(relpath)
 
2190
        else:
 
2191
            names = relpath
2146
2192
        current_id = self.root_id
2147
2193
        if current_id is None:
2148
2194
            return None
2161
2207
                for (parent_id, name_utf8), file_id in items:
2162
2208
                    if parent_id != current_id or name_utf8 != basename_utf8:
2163
2209
                        raise errors.BzrError("corrupt inventory lookup! "
2164
 
                                              "%r %r %r %r" % (parent_id, current_id, name_utf8,
2165
 
                                                               basename_utf8))
 
2210
                            "%r %r %r %r" % (parent_id, current_id, name_utf8,
 
2211
                            basename_utf8))
2166
2212
                if file_id is None:
2167
2213
                    return None
2168
2214
                else:
2172
2218
 
2173
2219
    def to_lines(self):
2174
2220
        """Serialise the inventory to lines."""
2175
 
        lines = [b"chkinventory:\n"]
2176
 
        if self._search_key_name != b'plain':
 
2221
        lines = ["chkinventory:\n"]
 
2222
        if self._search_key_name != 'plain':
2177
2223
            # custom ordering grouping things that don't change together
2178
 
            lines.append(b'search_key_name: %s\n' % (
2179
 
                self._search_key_name))
2180
 
            lines.append(b"root_id: %s\n" % self.root_id)
2181
 
            lines.append(b'parent_id_basename_to_file_id: %s\n' %
2182
 
                         (self.parent_id_basename_to_file_id.key()[0],))
2183
 
            lines.append(b"revision_id: %s\n" % self.revision_id)
2184
 
            lines.append(b"id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
 
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' %
 
2227
                (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],))
2185
2230
        else:
2186
 
            lines.append(b"revision_id: %s\n" % self.revision_id)
2187
 
            lines.append(b"root_id: %s\n" % self.root_id)
 
2231
            lines.append("revision_id: %s\n" % self.revision_id)
 
2232
            lines.append("root_id: %s\n" % self.root_id)
2188
2233
            if self.parent_id_basename_to_file_id is not None:
2189
 
                lines.append(b'parent_id_basename_to_file_id: %s\n' %
2190
 
                             (self.parent_id_basename_to_file_id.key()[0],))
2191
 
            lines.append(b"id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
 
2234
                lines.append('parent_id_basename_to_file_id: %s\n' %
 
2235
                    (self.parent_id_basename_to_file_id.key()[0],))
 
2236
            lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2192
2237
        return lines
2193
2238
 
2194
2239
    @property
2195
2240
    def root(self):
2196
2241
        """Get the root entry."""
2197
 
        return self.get_entry(self.root_id)
 
2242
        return self[self.root_id]
2198
2243
 
2199
2244
 
2200
2245
class CHKInventoryDirectory(InventoryDirectory):
2201
2246
    """A directory in an inventory."""
2202
2247
 
2203
 
    __slots__ = ['_children', '_chk_inventory']
 
2248
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
2249
                 'text_id', 'parent_id', '_children', 'executable',
 
2250
                 'revision', 'symlink_target', 'reference_revision',
 
2251
                 '_chk_inventory']
2204
2252
 
2205
2253
    def __init__(self, file_id, name, parent_id, chk_inventory):
2206
2254
        # Don't call InventoryDirectory.__init__ - it isn't right for this
2207
2255
        # class.
2208
2256
        InventoryEntry.__init__(self, file_id, name, parent_id)
2209
2257
        self._children = None
 
2258
        self.kind = 'directory'
2210
2259
        self._chk_inventory = chk_inventory
2211
2260
 
2212
2261
    @property
2224
2273
        # No longer supported
2225
2274
        if self._chk_inventory.parent_id_basename_to_file_id is None:
2226
2275
            raise AssertionError("Inventories without"
2227
 
                                 " parent_id_basename_to_file_id are no longer supported")
 
2276
                " parent_id_basename_to_file_id are no longer supported")
2228
2277
        result = {}
2229
2278
        # XXX: Todo - use proxy objects for the children rather than loading
2230
2279
        # all when the attribute is referenced.
2231
2280
        parent_id_index = self._chk_inventory.parent_id_basename_to_file_id
2232
2281
        child_keys = set()
2233
2282
        for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2234
 
                key_filter=[StaticTuple(self.file_id,)]):
 
2283
            key_filter=[StaticTuple(self.file_id,)]):
2235
2284
            child_keys.add(StaticTuple(file_id,))
2236
2285
        cached = set()
2237
2286
        for file_id_key in child_keys:
2250
2299
        self._children = result
2251
2300
        return result
2252
2301
 
2253
 
 
2254
2302
entry_factory = {
2255
2303
    'directory': InventoryDirectory,
2256
2304
    'file': InventoryFile,
2258
2306
    'tree-reference': TreeReference
2259
2307
}
2260
2308
 
2261
 
 
2262
2309
def make_entry(kind, name, parent_id, file_id=None):
2263
2310
    """Create an inventory entry.
2264
2311
 
2284
2331
        accessed on this platform by the normalized path.
2285
2332
    :return: The NFC normalised version of name.
2286
2333
    """
2287
 
    # ------- This has been copied to breezy.dirstate.DirState.add, please
 
2334
    #------- This has been copied to bzrlib.dirstate.DirState.add, please
2288
2335
    # keep them synchronised.
2289
2336
    # we dont import normalized_filename directly because we want to be
2290
2337
    # able to change the implementation at runtime for tests.
2299
2346
    return name
2300
2347
 
2301
2348
 
2302
 
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
2303
 
 
 
2349
_NAME_RE = None
2304
2350
 
2305
2351
def is_valid_name(name):
 
2352
    global _NAME_RE
 
2353
    if _NAME_RE is None:
 
2354
        _NAME_RE = re.compile(r'^[^/\\]+$')
 
2355
 
2306
2356
    return bool(_NAME_RE.match(name))
2307
2357
 
2308
2358
 
2317
2367
        ids.add(item[2])
2318
2368
        if len(ids) != length:
2319
2369
            raise errors.InconsistentDelta(item[0] or item[1], item[2],
2320
 
                                           "repeated file_id")
 
2370
                "repeated file_id")
2321
2371
        yield item
2322
2372
 
2323
2373
 
2362
2412
        entry = item[3]
2363
2413
        if item[2] is None:
2364
2414
            raise errors.InconsistentDelta(item[0] or item[1], item[2],
2365
 
                                           "entry with file_id None %r" % entry)
2366
 
        if not isinstance(item[2], bytes):
 
2415
                "entry with file_id None %r" % entry)
 
2416
        if type(item[2]) != str:
2367
2417
            raise errors.InconsistentDelta(item[0] or item[1], item[2],
2368
 
                                           "entry with non bytes file_id %r" % entry)
 
2418
                "entry with non bytes file_id %r" % entry)
2369
2419
        yield item
2370
2420
 
2371
2421
 
2379
2429
        if entry is not None:
2380
2430
            if entry.file_id != item[2]:
2381
2431
                raise errors.InconsistentDelta(item[0] or item[1], item[2],
2382
 
                                               "mismatched id with %r" % entry)
 
2432
                    "mismatched id with %r" % entry)
2383
2433
        yield item
2384
2434
 
2385
2435
 
2393
2443
        entry = item[3]
2394
2444
        if new_path is None and entry is not None:
2395
2445
            raise errors.InconsistentDelta(item[0], item[1],
2396
 
                                           "Entry with no new_path")
 
2446
                "Entry with no new_path")
2397
2447
        if new_path is not None and entry is None:
2398
2448
            raise errors.InconsistentDelta(new_path, item[1],
2399
 
                                           "new_path with no entry")
 
2449
                "new_path with no entry")
2400
2450
        yield item
2401
 
 
2402
 
 
2403
 
def mutable_inventory_from_tree(tree):
2404
 
    """Create a new inventory that has the same contents as a specified tree.
2405
 
 
2406
 
    :param tree: Revision tree to create inventory from
2407
 
    """
2408
 
    entries = tree.iter_entries_by_dir()
2409
 
    inv = Inventory(None, tree.get_revision_id())
2410
 
    for path, inv_entry in entries:
2411
 
        inv.add(inv_entry.copy())
2412
 
    return inv