/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/bzr/bundle/bundle_data.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2019-10-13 17:31:55 UTC
  • mfrom: (7397.4.9 remove-unused)
  • Revision ID: breezy.the.bot@gmail.com-20191013173155-yoiokny4mknxb3um
Remove Tree.has_id.

Merged from https://code.launchpad.net/~jelmer/brz/remove-unused/+merge/373320

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 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
16
16
 
17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
18
18
 
 
19
from __future__ import absolute_import
 
20
 
19
21
import base64
20
 
from cStringIO import StringIO
 
22
from io import BytesIO
21
23
import os
22
24
import pprint
23
25
 
24
 
from bzrlib import (
 
26
from ... import (
 
27
    cache_utf8,
25
28
    osutils,
26
29
    timestamp,
27
30
    )
28
 
import bzrlib.errors
29
 
from bzrlib.bundle import apply_bundle
30
 
from bzrlib.errors import (TestamentMismatch, BzrError,
31
 
                           MalformedHeader, MalformedPatches, NotABundle)
32
 
from bzrlib.inventory import (Inventory, InventoryEntry,
33
 
                              InventoryDirectory, InventoryFile,
34
 
                              InventoryLink)
35
 
from bzrlib.osutils import sha_file, sha_string, pathjoin
36
 
from bzrlib.revision import Revision, NULL_REVISION
37
 
from bzrlib.testament import StrictTestament
38
 
from bzrlib.trace import mutter, warning
39
 
import bzrlib.transport
40
 
from bzrlib.tree import Tree
41
 
import bzrlib.urlutils
42
 
from bzrlib.xml5 import serializer_v5
 
31
from . import apply_bundle
 
32
from ...errors import (
 
33
    TestamentMismatch,
 
34
    BzrError,
 
35
    NoSuchId,
 
36
    )
 
37
from ..inventory import (
 
38
    Inventory,
 
39
    InventoryDirectory,
 
40
    InventoryFile,
 
41
    InventoryLink,
 
42
    )
 
43
from ...osutils import sha_string, pathjoin
 
44
from ...revision import Revision, NULL_REVISION
 
45
from ...sixish import (
 
46
    viewitems,
 
47
    )
 
48
from ..testament import StrictTestament
 
49
from ...trace import mutter, warning
 
50
from ...tree import Tree
 
51
from ..xml5 import serializer_v5
43
52
 
44
53
 
45
54
class RevisionInfo(object):
46
55
    """Gets filled out for each revision object that is read.
47
56
    """
 
57
 
48
58
    def __init__(self, revision_id):
49
59
        self.revision_id = revision_id
50
60
        self.sha1 = None
65
75
 
66
76
    def as_revision(self):
67
77
        rev = Revision(revision_id=self.revision_id,
68
 
            committer=self.committer,
69
 
            timestamp=float(self.timestamp),
70
 
            timezone=int(self.timezone),
71
 
            inventory_sha1=self.inventory_sha1,
72
 
            message='\n'.join(self.message))
 
78
                       committer=self.committer,
 
79
                       timestamp=float(self.timestamp),
 
80
                       timezone=int(self.timezone),
 
81
                       inventory_sha1=self.inventory_sha1,
 
82
                       message='\n'.join(self.message))
73
83
 
74
84
        if self.parent_ids:
75
85
            rev.parent_ids.extend(self.parent_ids)
84
94
                    value = ''
85
95
                else:
86
96
                    key = str(property[:key_end])
87
 
                    value = property[key_end+2:]
 
97
                    value = property[key_end + 2:]
88
98
                rev.properties[key] = value
89
99
 
90
100
        return rev
99
109
        revision_info.timestamp = revision.timestamp
100
110
        revision_info.message = revision.message.split('\n')
101
111
        revision_info.properties = [': '.join(p) for p in
102
 
                                    revision.properties.iteritems()]
 
112
                                    viewitems(revision.properties)]
103
113
        return revision_info
104
114
 
105
115
 
107
117
    """This contains the meta information. Stuff that allows you to
108
118
    recreate the revision or inventory XML.
109
119
    """
 
120
 
110
121
    def __init__(self, bundle_format=None):
111
122
        self.bundle_format = None
112
123
        self.committer = None
136
147
        split up, based on the assumptions that can be made
137
148
        when information is missing.
138
149
        """
139
 
        from bzrlib.timestamp import unpack_highres_date
 
150
        from breezy.timestamp import unpack_highres_date
140
151
        # Put in all of the guessable information.
141
152
        if not self.timestamp and self.date:
142
153
            self.timestamp, self.timezone = unpack_highres_date(self.date)
146
157
            if rev.timestamp is None:
147
158
                if rev.date is not None:
148
159
                    rev.timestamp, rev.timezone = \
149
 
                            unpack_highres_date(rev.date)
 
160
                        unpack_highres_date(rev.date)
150
161
                else:
151
162
                    rev.timestamp = self.timestamp
152
163
                    rev.timezone = self.timezone
201
212
        revision_info = self.get_revision_info(revision_id)
202
213
        inventory_revision_id = revision_id
203
214
        bundle_tree = BundleTree(repository.revision_tree(base),
204
 
                                  inventory_revision_id)
 
215
                                 inventory_revision_id)
205
216
        self._update_tree(bundle_tree, revision_id)
206
217
 
207
218
        inv = bundle_tree.inventory
208
219
        self._validate_inventory(inv, revision_id)
209
 
        self._validate_revision(inv, revision_id)
 
220
        self._validate_revision(bundle_tree, revision_id)
210
221
 
211
222
        return bundle_tree
212
223
 
217
228
        """
218
229
        rev_to_sha = {}
219
230
        inv_to_sha = {}
 
231
 
220
232
        def add_sha(d, revision_id, sha1):
221
233
            if revision_id is None:
222
234
                if sha1 is not None:
223
235
                    raise BzrError('A Null revision should always'
224
 
                        'have a null sha1 hash')
 
236
                                   'have a null sha1 hash')
225
237
                return
226
238
            if revision_id in d:
227
239
                # This really should have been validated as part
228
240
                # of _validate_revisions but lets do it again
229
241
                if sha1 != d[revision_id]:
230
242
                    raise BzrError('** Revision %r referenced with 2 different'
231
 
                            ' sha hashes %s != %s' % (revision_id,
232
 
                                sha1, d[revision_id]))
 
243
                                   ' sha hashes %s != %s' % (revision_id,
 
244
                                                             sha1, d[revision_id]))
233
245
            else:
234
246
                d[revision_id] = sha1
235
247
 
245
257
 
246
258
        count = 0
247
259
        missing = {}
248
 
        for revision_id, sha1 in rev_to_sha.iteritems():
 
260
        for revision_id, sha1 in viewitems(rev_to_sha):
249
261
            if repository.has_revision(revision_id):
250
262
                testament = StrictTestament.from_revision(repository,
251
263
                                                          revision_id)
253
265
                                                                revision_id)
254
266
                if sha1 != local_sha1:
255
267
                    raise BzrError('sha1 mismatch. For revision id {%s}'
256
 
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
 
268
                                   'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
257
269
                else:
258
270
                    count += 1
259
271
            elif revision_id not in checked:
278
290
        if rev.revision_id != revision_id:
279
291
            raise AssertionError()
280
292
        if sha1 != rev.inventory_sha1:
281
 
            open(',,bogus-inv', 'wb').write(s)
 
293
            with open(',,bogus-inv', 'wb') as f:
 
294
                f.write(s)
282
295
            warning('Inventory sha hash mismatch for revision %s. %s'
283
296
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
284
297
 
285
 
    def _validate_revision(self, inventory, revision_id):
 
298
    def _testament(self, revision, tree):
 
299
        raise NotImplementedError(self._testament)
 
300
 
 
301
    def _validate_revision(self, tree, revision_id):
286
302
        """Make sure all revision entries match their checksum."""
287
303
 
288
 
        # This is a mapping from each revision id to it's sha hash
 
304
        # This is a mapping from each revision id to its sha hash
289
305
        rev_to_sha1 = {}
290
306
 
291
307
        rev = self.get_revision(revision_id)
294
310
            raise AssertionError()
295
311
        if not (rev.revision_id == revision_id):
296
312
            raise AssertionError()
297
 
        sha1 = self._testament_sha1(rev, inventory)
 
313
        testament = self._testament(rev, tree)
 
314
        sha1 = testament.as_sha1()
298
315
        if sha1 != rev_info.sha1:
299
316
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
300
317
        if rev.revision_id in rev_to_sha1:
301
318
            raise BzrError('Revision {%s} given twice in the list'
302
 
                    % (rev.revision_id))
 
319
                           % (rev.revision_id))
303
320
        rev_to_sha1[rev.revision_id] = sha1
304
321
 
305
322
    def _update_tree(self, bundle_tree, revision_id):
313
330
            if last_changed is not None:
314
331
                # last_changed will be a Unicode string because of how it was
315
332
                # read. Convert it back to utf8.
316
 
                changed_revision_id = osutils.safe_revision_id(last_changed,
317
 
                                                               warn=False)
 
333
                changed_revision_id = cache_utf8.encode(last_changed)
318
334
            else:
319
335
                changed_revision_id = revision_id
320
336
            bundle_tree.note_last_changed(path, changed_revision_id)
327
343
                try:
328
344
                    name, value = info_item.split(':', 1)
329
345
                except ValueError:
330
 
                    raise 'Value %r has no colon' % info_item
 
346
                    raise ValueError('Value %r has no colon' % info_item)
331
347
                if name == 'last-changed':
332
348
                    last_changed = value
333
349
                elif name == 'executable':
341
357
 
342
358
        def do_patch(path, lines, encoding):
343
359
            if encoding == 'base64':
344
 
                patch = base64.decodestring(''.join(lines))
 
360
                patch = base64.b64decode(b''.join(lines))
345
361
            elif encoding is None:
346
 
                patch =  ''.join(lines)
 
362
                patch = b''.join(lines)
347
363
            else:
348
364
                raise ValueError(encoding)
349
365
            bundle_tree.note_patch(path, patch)
352
368
            info = extra.split(' // ')
353
369
            if len(info) < 2:
354
370
                raise BzrError('renamed action lines need both a from and to'
355
 
                        ': %r' % extra)
 
371
                               ': %r' % extra)
356
372
            old_path = info[0]
357
373
            if info[1].startswith('=> '):
358
374
                new_path = info[1][3:]
371
387
                # TODO: in the future we might allow file ids to be
372
388
                # given for removed entries
373
389
                raise BzrError('removed action lines should only have the path'
374
 
                        ': %r' % extra)
 
390
                               ': %r' % extra)
375
391
            path = info[0]
376
392
            bundle_tree.note_deletion(path)
377
393
 
379
395
            info = extra.split(' // ')
380
396
            if len(info) <= 1:
381
397
                raise BzrError('add action lines require the path and file id'
382
 
                        ': %r' % extra)
 
398
                               ': %r' % extra)
383
399
            elif len(info) > 5:
384
400
                raise BzrError('add action lines have fewer than 5 entries.'
385
 
                        ': %r' % extra)
 
401
                               ': %r' % extra)
386
402
            path = info[0]
387
403
            if not info[1].startswith('file-id:'):
388
404
                raise BzrError('The file-id should follow the path for an add'
389
 
                        ': %r' % extra)
 
405
                               ': %r' % extra)
390
406
            # This will be Unicode because of how the stream is read. Turn it
391
407
            # back into a utf8 file_id
392
 
            file_id = osutils.safe_file_id(info[1][8:], warn=False)
 
408
            file_id = cache_utf8.encode(info[1][8:])
393
409
 
394
410
            bundle_tree.note_id(file_id, path, kind)
395
411
            # this will be overridden in extra_info if executable is specified.
404
420
            info = extra.split(' // ')
405
421
            if len(info) < 1:
406
422
                raise BzrError('modified action lines have at least'
407
 
                        'the path in them: %r' % extra)
 
423
                               'the path in them: %r' % extra)
408
424
            path = info[0]
409
425
 
410
426
            last_modified, encoding = extra_info(info[1:], path)
413
429
                do_patch(path, lines, encoding)
414
430
 
415
431
        valid_actions = {
416
 
            'renamed':renamed,
417
 
            'removed':removed,
418
 
            'added':added,
419
 
            'modified':modified
 
432
            'renamed': renamed,
 
433
            'removed': removed,
 
434
            'added': added,
 
435
            'modified': modified
420
436
        }
421
437
        for action_line, lines in \
422
 
            self.get_revision_info(revision_id).tree_actions:
 
438
                self.get_revision_info(revision_id).tree_actions:
423
439
            first = action_line.find(' ')
424
440
            if first == -1:
425
441
                raise BzrError('Bogus action line'
426
 
                        ' (no opening space): %r' % action_line)
427
 
            second = action_line.find(' ', first+1)
 
442
                               ' (no opening space): %r' % action_line)
 
443
            second = action_line.find(' ', first + 1)
428
444
            if second == -1:
429
445
                raise BzrError('Bogus action line'
430
 
                        ' (missing second space): %r' % action_line)
 
446
                               ' (missing second space): %r' % action_line)
431
447
            action = action_line[:first]
432
 
            kind = action_line[first+1:second]
 
448
            kind = action_line[first + 1:second]
433
449
            if kind not in ('file', 'directory', 'symlink'):
434
450
                raise BzrError('Bogus action line'
435
 
                        ' (invalid object kind %r): %r' % (kind, action_line))
436
 
            extra = action_line[second+1:]
 
451
                               ' (invalid object kind %r): %r' % (kind, action_line))
 
452
            extra = action_line[second + 1:]
437
453
 
438
454
            if action not in valid_actions:
439
455
                raise BzrError('Bogus action line'
440
 
                        ' (unrecognized action): %r' % action_line)
 
456
                               ' (unrecognized action): %r' % action_line)
441
457
            valid_actions[action](kind, extra, lines)
442
458
 
443
459
    def install_revisions(self, target_repo, stream_input=True):
458
474
 
459
475
 
460
476
class BundleTree(Tree):
 
477
 
461
478
    def __init__(self, base_tree, revision_id):
462
479
        self.base_tree = base_tree
463
 
        self._renamed = {} # Mapping from old_path => new_path
464
 
        self._renamed_r = {} # new_path => old_path
465
 
        self._new_id = {} # new_path => new_id
466
 
        self._new_id_r = {} # new_id => new_path
467
 
        self._kinds = {} # new_id => kind
468
 
        self._last_changed = {} # new_id => revision_id
469
 
        self._executable = {} # new_id => executable value
 
480
        self._renamed = {}  # Mapping from old_path => new_path
 
481
        self._renamed_r = {}  # new_path => old_path
 
482
        self._new_id = {}  # new_path => new_id
 
483
        self._new_id_r = {}  # new_id => new_path
 
484
        self._kinds = {}  # new_path => kind
 
485
        self._last_changed = {}  # new_id => revision_id
 
486
        self._executable = {}  # new_id => executable value
470
487
        self.patches = {}
471
 
        self._targets = {} # new path => new symlink target
 
488
        self._targets = {}  # new path => new symlink target
472
489
        self.deleted = []
473
 
        self.contents_by_id = True
474
490
        self.revision_id = revision_id
475
491
        self._inventory = None
476
492
 
490
506
        """Files that don't exist in base need a new id."""
491
507
        self._new_id[new_path] = new_id
492
508
        self._new_id_r[new_id] = new_path
493
 
        self._kinds[new_id] = kind
 
509
        self._kinds[new_path] = kind
494
510
 
495
511
    def note_last_changed(self, file_id, revision_id):
496
512
        if (file_id in self._last_changed
497
513
                and self._last_changed[file_id] != revision_id):
498
514
            raise BzrError('Mismatched last-changed revision for file_id {%s}'
499
 
                    ': %s != %s' % (file_id,
500
 
                                    self._last_changed[file_id],
501
 
                                    revision_id))
 
515
                           ': %s != %s' % (file_id,
 
516
                                           self._last_changed[file_id],
 
517
                                           revision_id))
502
518
        self._last_changed[file_id] = revision_id
503
519
 
504
520
    def note_patch(self, new_path, patch):
523
539
        old_path = self._renamed.get(new_path)
524
540
        if old_path is not None:
525
541
            return old_path
526
 
        dirname,basename = os.path.split(new_path)
 
542
        dirname, basename = os.path.split(new_path)
527
543
        # dirname is not '' doesn't work, because
528
544
        # dirname may be a unicode entry, and is
529
545
        # requires the objects to be identical
535
551
                old_path = pathjoin(old_dir, basename)
536
552
        else:
537
553
            old_path = new_path
538
 
        #If the new path wasn't in renamed, the old one shouldn't be in
539
 
        #renamed_r
 
554
        # If the new path wasn't in renamed, the old one shouldn't be in
 
555
        # renamed_r
540
556
        if old_path in self._renamed_r:
541
557
            return None
542
558
        return old_path
552
568
            return new_path
553
569
        if new_path in self._renamed:
554
570
            return None
555
 
        dirname,basename = os.path.split(old_path)
 
571
        dirname, basename = os.path.split(old_path)
556
572
        if dirname != '':
557
573
            new_dir = self.new_path(dirname)
558
574
            if new_dir is None:
561
577
                new_path = pathjoin(new_dir, basename)
562
578
        else:
563
579
            new_path = old_path
564
 
        #If the old path wasn't in renamed, the new one shouldn't be in
565
 
        #renamed_r
 
580
        # If the old path wasn't in renamed, the new one shouldn't be in
 
581
        # renamed_r
566
582
        if new_path in self._renamed:
567
583
            return None
568
584
        return new_path
577
593
            return None
578
594
        if old_path in self.deleted:
579
595
            return None
580
 
        if getattr(self.base_tree, 'path2id', None) is not None:
581
 
            return self.base_tree.path2id(old_path)
582
 
        else:
583
 
            return self.base_tree.inventory.path2id(old_path)
 
596
        return self.base_tree.path2id(old_path)
584
597
 
585
598
    def id2path(self, file_id):
586
599
        """Return the new path in the target tree of the file with id file_id"""
589
602
            return path
590
603
        old_path = self.base_tree.id2path(file_id)
591
604
        if old_path is None:
592
 
            return None
 
605
            raise NoSuchId(file_id, self)
593
606
        if old_path in self.deleted:
594
 
            return None
595
 
        return self.new_path(old_path)
596
 
 
597
 
    def old_contents_id(self, file_id):
598
 
        """Return the id in the base_tree for the given file_id.
599
 
        Return None if the file did not exist in base.
600
 
        """
601
 
        if self.contents_by_id:
602
 
            if self.base_tree.has_id(file_id):
603
 
                return file_id
604
 
            else:
605
 
                return None
606
 
        new_path = self.id2path(file_id)
607
 
        return self.base_tree.path2id(new_path)
608
 
 
609
 
    def get_file(self, file_id):
 
607
            raise NoSuchId(file_id, self)
 
608
        new_path = self.new_path(old_path)
 
609
        if new_path is None:
 
610
            raise NoSuchId(file_id, self)
 
611
        return new_path
 
612
 
 
613
    def get_file(self, path):
610
614
        """Return a file-like object containing the new contents of the
611
615
        file given by file_id.
612
616
 
614
618
                in the text-store, so that the file contents would
615
619
                then be cached.
616
620
        """
617
 
        base_id = self.old_contents_id(file_id)
618
 
        if (base_id is not None and
619
 
            base_id != self.base_tree.inventory.root.file_id):
620
 
            patch_original = self.base_tree.get_file(base_id)
621
 
        else:
 
621
        file_id = self.path2id(path)
 
622
        try:
 
623
            old_path = self.base_tree.id2path(file_id)
 
624
        except NoSuchId:
622
625
            patch_original = None
623
 
        file_patch = self.patches.get(self.id2path(file_id))
 
626
        else:
 
627
            if old_path is None:
 
628
                import pdb; pdb.set_trace()
 
629
            patch_original = self.base_tree.get_file(old_path)
 
630
        file_patch = self.patches.get(path)
624
631
        if file_patch is None:
625
632
            if (patch_original is None and
626
 
                self.get_kind(file_id) == 'directory'):
627
 
                return StringIO()
 
633
                    self.kind(path) == 'directory'):
 
634
                return BytesIO()
628
635
            if patch_original is None:
629
636
                raise AssertionError("None: %s" % file_id)
630
637
            return patch_original
631
638
 
632
 
        if file_patch.startswith('\\'):
 
639
        if file_patch.startswith(b'\\'):
633
640
            raise ValueError(
634
641
                'Malformed patch for %s, %r' % (file_id, file_patch))
635
642
        return patched_file(file_patch, patch_original)
636
643
 
637
 
    def get_symlink_target(self, file_id):
638
 
        new_path = self.id2path(file_id)
639
 
        try:
640
 
            return self._targets[new_path]
641
 
        except KeyError:
642
 
            return self.base_tree.get_symlink_target(file_id)
643
 
 
644
 
    def get_kind(self, file_id):
645
 
        if file_id in self._kinds:
646
 
            return self._kinds[file_id]
647
 
        return self.base_tree.inventory[file_id].kind
648
 
 
649
 
    def is_executable(self, file_id):
650
 
        path = self.id2path(file_id)
 
644
    def get_symlink_target(self, path):
 
645
        try:
 
646
            return self._targets[path]
 
647
        except KeyError:
 
648
            old_path = self.old_path(path)
 
649
            return self.base_tree.get_symlink_target(old_path)
 
650
 
 
651
    def kind(self, path):
 
652
        try:
 
653
            return self._kinds[path]
 
654
        except KeyError:
 
655
            old_path = self.old_path(path)
 
656
            return self.base_tree.kind(old_path)
 
657
 
 
658
    def get_file_revision(self, path):
 
659
        if path in self._last_changed:
 
660
            return self._last_changed[path]
 
661
        else:
 
662
            old_path = self.old_path(path)
 
663
            return self.base_tree.get_file_revision(old_path)
 
664
 
 
665
    def is_executable(self, path):
651
666
        if path in self._executable:
652
667
            return self._executable[path]
653
668
        else:
654
 
            return self.base_tree.inventory[file_id].executable
 
669
            old_path = self.old_path(path)
 
670
            return self.base_tree.is_executable(old_path)
655
671
 
656
 
    def get_last_changed(self, file_id):
657
 
        path = self.id2path(file_id)
 
672
    def get_last_changed(self, path):
658
673
        if path in self._last_changed:
659
674
            return self._last_changed[path]
660
 
        return self.base_tree.inventory[file_id].revision
 
675
        old_path = self.old_path(path)
 
676
        return self.base_tree.get_file_revision(old_path)
661
677
 
662
 
    def get_size_and_sha1(self, file_id):
 
678
    def get_size_and_sha1(self, new_path):
663
679
        """Return the size and sha1 hash of the given file id.
664
680
        If the file was not locally modified, this is extracted
665
681
        from the base_tree. Rather than re-reading the file.
666
682
        """
667
 
        new_path = self.id2path(file_id)
668
683
        if new_path is None:
669
684
            return None, None
670
685
        if new_path not in self.patches:
671
686
            # If the entry does not have a patch, then the
672
687
            # contents must be the same as in the base_tree
673
 
            ie = self.base_tree.inventory[file_id]
674
 
            if ie.text_size is None:
675
 
                return ie.text_size, ie.text_sha1
676
 
            return int(ie.text_size), ie.text_sha1
677
 
        fileobj = self.get_file(file_id)
 
688
            base_path = self.old_path(new_path)
 
689
            text_size = self.base_tree.get_file_size(base_path)
 
690
            text_sha1 = self.base_tree.get_file_sha1(base_path)
 
691
            return text_size, text_sha1
 
692
        fileobj = self.get_file(new_path)
678
693
        content = fileobj.read()
679
694
        return len(content), sha_string(content)
680
695
 
684
699
        This need to be called before ever accessing self.inventory
685
700
        """
686
701
        from os.path import dirname, basename
687
 
        base_inv = self.base_tree.inventory
688
702
        inv = Inventory(None, self.revision_id)
689
703
 
690
 
        def add_entry(file_id):
691
 
            path = self.id2path(file_id)
692
 
            if path is None:
693
 
                return
 
704
        def add_entry(path, file_id):
694
705
            if path == '':
695
706
                parent_id = None
696
707
            else:
697
708
                parent_path = dirname(path)
698
709
                parent_id = self.path2id(parent_path)
699
710
 
700
 
            kind = self.get_kind(file_id)
701
 
            revision_id = self.get_last_changed(file_id)
 
711
            kind = self.kind(path)
 
712
            revision_id = self.get_last_changed(path)
702
713
 
703
714
            name = basename(path)
704
715
            if kind == 'directory':
705
716
                ie = InventoryDirectory(file_id, name, parent_id)
706
717
            elif kind == 'file':
707
718
                ie = InventoryFile(file_id, name, parent_id)
708
 
                ie.executable = self.is_executable(file_id)
 
719
                ie.executable = self.is_executable(path)
709
720
            elif kind == 'symlink':
710
721
                ie = InventoryLink(file_id, name, parent_id)
711
 
                ie.symlink_target = self.get_symlink_target(file_id)
 
722
                ie.symlink_target = self.get_symlink_target(path)
712
723
            ie.revision = revision_id
713
724
 
714
 
            if kind in ('directory', 'symlink'):
715
 
                ie.text_size, ie.text_sha1 = None, None
716
 
            else:
717
 
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
718
 
            if (ie.text_size is None) and (kind == 'file'):
719
 
                raise BzrError('Got a text_size of None for file_id %r' % file_id)
 
725
            if kind == 'file':
 
726
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(path)
 
727
                if ie.text_size is None:
 
728
                    raise BzrError(
 
729
                        'Got a text_size of None for file_id %r' % file_id)
720
730
            inv.add(ie)
721
731
 
722
732
        sorted_entries = self.sorted_path_id()
723
733
        for path, file_id in sorted_entries:
724
 
            add_entry(file_id)
 
734
            add_entry(path, file_id)
725
735
 
726
736
        return inv
727
737
 
732
742
    # at that instant
733
743
    inventory = property(_get_inventory)
734
744
 
735
 
    def __iter__(self):
736
 
        for path, entry in self.inventory.iter_entries():
737
 
            yield entry.file_id
 
745
    root_inventory = property(_get_inventory)
 
746
 
 
747
    def all_file_ids(self):
 
748
        return {entry.file_id for path, entry in self.inventory.iter_entries()}
 
749
 
 
750
    def all_versioned_paths(self):
 
751
        return {path for path, entry in self.inventory.iter_entries()}
 
752
 
 
753
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
754
        # The only files returned by this are those from the version
 
755
        inv = self.inventory
 
756
        if from_dir is None:
 
757
            from_dir_id = None
 
758
        else:
 
759
            from_dir_id = inv.path2id(from_dir)
 
760
            if from_dir_id is None:
 
761
                # Directory not versioned
 
762
                return
 
763
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
 
764
        if inv.root is not None and not include_root and from_dir is None:
 
765
            # skip the root for compatibility with the current apis.
 
766
            next(entries)
 
767
        for path, entry in entries:
 
768
            yield path, 'V', entry.kind, entry
738
769
 
739
770
    def sorted_path_id(self):
740
771
        paths = []
741
 
        for result in self._new_id.iteritems():
 
772
        for result in viewitems(self._new_id):
742
773
            paths.append(result)
743
 
        for id in self.base_tree:
744
 
            path = self.id2path(id)
745
 
            if path is None:
 
774
        for id in self.base_tree.all_file_ids():
 
775
            try:
 
776
                path = self.id2path(id)
 
777
            except NoSuchId:
746
778
                continue
747
779
            paths.append((path, id))
748
780
        paths.sort()
751
783
 
752
784
def patched_file(file_patch, original):
753
785
    """Produce a file-like object with the patched version of a text"""
754
 
    from bzrlib.patches import iter_patched
755
 
    from bzrlib.iterablefile import IterableFile
756
 
    if file_patch == "":
 
786
    from breezy.patches import iter_patched
 
787
    from breezy.iterablefile import IterableFile
 
788
    if file_patch == b"":
757
789
        return IterableFile(())
758
790
    # string.splitlines(True) also splits on '\r', but the iter_patched code
759
791
    # only expects to iterate over '\n' style lines
760
792
    return IterableFile(iter_patched(original,
761
 
                StringIO(file_patch).readlines()))
 
793
                                     BytesIO(file_patch).readlines()))