/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/bundle/bundle_data.py

  • Committer: Jelmer Vernooij
  • Date: 2017-07-23 22:06:41 UTC
  • mfrom: (6738 trunk)
  • mto: This revision was merged to the branch mainline in revision 6739.
  • Revision ID: jelmer@jelmer.uk-20170723220641-69eczax9bmv8d6kk
Merge trunk, address review comments.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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 io import BytesIO
21
22
import os
22
23
import pprint
23
24
 
24
 
from ... import (
 
25
from .. import (
25
26
    cache_utf8,
26
27
    osutils,
27
28
    timestamp,
28
29
    )
29
30
from . import apply_bundle
30
 
from ...errors import (
 
31
from ..errors import (
31
32
    TestamentMismatch,
32
33
    BzrError,
33
 
    NoSuchId,
34
34
    )
35
 
from ..inventory import (
 
35
from ..bzr.inventory import (
36
36
    Inventory,
37
37
    InventoryDirectory,
38
38
    InventoryFile,
39
39
    InventoryLink,
40
40
    )
41
 
from ..inventorytree import InventoryTree
42
 
from ...osutils import sha_string, sha_strings, pathjoin
43
 
from ...revision import Revision, NULL_REVISION
 
41
from ..osutils import sha_string, pathjoin
 
42
from ..revision import Revision, NULL_REVISION
 
43
from ..sixish import (
 
44
    BytesIO,
 
45
    viewitems,
 
46
    )
44
47
from ..testament import StrictTestament
45
 
from ...trace import mutter, warning
46
 
from ...tree import (
47
 
    InterTree,
48
 
    Tree,
49
 
    )
50
 
from ..xml5 import serializer_v5
 
48
from ..trace import mutter, warning
 
49
from ..tree import Tree
 
50
from ..bzr.xml5 import serializer_v5
51
51
 
52
52
 
53
53
class RevisionInfo(object):
54
54
    """Gets filled out for each revision object that is read.
55
55
    """
56
 
 
57
56
    def __init__(self, revision_id):
58
57
        self.revision_id = revision_id
59
58
        self.sha1 = None
74
73
 
75
74
    def as_revision(self):
76
75
        rev = Revision(revision_id=self.revision_id,
77
 
                       committer=self.committer,
78
 
                       timestamp=float(self.timestamp),
79
 
                       timezone=int(self.timezone),
80
 
                       inventory_sha1=self.inventory_sha1,
81
 
                       message='\n'.join(self.message))
 
76
            committer=self.committer,
 
77
            timestamp=float(self.timestamp),
 
78
            timezone=int(self.timezone),
 
79
            inventory_sha1=self.inventory_sha1,
 
80
            message='\n'.join(self.message))
82
81
 
83
82
        if self.parent_ids:
84
83
            rev.parent_ids.extend(self.parent_ids)
93
92
                    value = ''
94
93
                else:
95
94
                    key = str(property[:key_end])
96
 
                    value = property[key_end + 2:]
 
95
                    value = property[key_end+2:]
97
96
                rev.properties[key] = value
98
97
 
99
98
        return rev
108
107
        revision_info.timestamp = revision.timestamp
109
108
        revision_info.message = revision.message.split('\n')
110
109
        revision_info.properties = [': '.join(p) for p in
111
 
                                    revision.properties.items()]
 
110
                                    viewitems(revision.properties)]
112
111
        return revision_info
113
112
 
114
113
 
116
115
    """This contains the meta information. Stuff that allows you to
117
116
    recreate the revision or inventory XML.
118
117
    """
119
 
 
120
118
    def __init__(self, bundle_format=None):
121
119
        self.bundle_format = None
122
120
        self.committer = None
156
154
            if rev.timestamp is None:
157
155
                if rev.date is not None:
158
156
                    rev.timestamp, rev.timezone = \
159
 
                        unpack_highres_date(rev.date)
 
157
                            unpack_highres_date(rev.date)
160
158
                else:
161
159
                    rev.timestamp = self.timestamp
162
160
                    rev.timezone = self.timezone
211
209
        revision_info = self.get_revision_info(revision_id)
212
210
        inventory_revision_id = revision_id
213
211
        bundle_tree = BundleTree(repository.revision_tree(base),
214
 
                                 inventory_revision_id)
 
212
                                  inventory_revision_id)
215
213
        self._update_tree(bundle_tree, revision_id)
216
214
 
217
215
        inv = bundle_tree.inventory
227
225
        """
228
226
        rev_to_sha = {}
229
227
        inv_to_sha = {}
230
 
 
231
228
        def add_sha(d, revision_id, sha1):
232
229
            if revision_id is None:
233
230
                if sha1 is not None:
234
231
                    raise BzrError('A Null revision should always'
235
 
                                   'have a null sha1 hash')
 
232
                        'have a null sha1 hash')
236
233
                return
237
234
            if revision_id in d:
238
235
                # This really should have been validated as part
239
236
                # of _validate_revisions but lets do it again
240
237
                if sha1 != d[revision_id]:
241
238
                    raise BzrError('** Revision %r referenced with 2 different'
242
 
                                   ' sha hashes %s != %s' % (revision_id,
243
 
                                                             sha1, d[revision_id]))
 
239
                            ' sha hashes %s != %s' % (revision_id,
 
240
                                sha1, d[revision_id]))
244
241
            else:
245
242
                d[revision_id] = sha1
246
243
 
256
253
 
257
254
        count = 0
258
255
        missing = {}
259
 
        for revision_id, sha1 in rev_to_sha.items():
 
256
        for revision_id, sha1 in viewitems(rev_to_sha):
260
257
            if repository.has_revision(revision_id):
261
258
                testament = StrictTestament.from_revision(repository,
262
259
                                                          revision_id)
264
261
                                                                revision_id)
265
262
                if sha1 != local_sha1:
266
263
                    raise BzrError('sha1 mismatch. For revision id {%s}'
267
 
                                   'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
 
264
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
268
265
                else:
269
266
                    count += 1
270
267
            elif revision_id not in checked:
282
279
        so build up an inventory, and make sure the hashes match.
283
280
        """
284
281
        # Now we should have a complete inventory entry.
285
 
        cs = serializer_v5.write_inventory_to_chunks(inv)
286
 
        sha1 = sha_strings(cs)
 
282
        s = serializer_v5.write_inventory_to_string(inv)
 
283
        sha1 = sha_string(s)
287
284
        # Target revision is the last entry in the real_revisions list
288
285
        rev = self.get_revision(revision_id)
289
286
        if rev.revision_id != revision_id:
290
287
            raise AssertionError()
291
288
        if sha1 != rev.inventory_sha1:
292
 
            with open(',,bogus-inv', 'wb') as f:
293
 
                f.writelines(cs)
 
289
            f = open(',,bogus-inv', 'wb')
 
290
            try:
 
291
                f.write(s)
 
292
            finally:
 
293
                f.close()
294
294
            warning('Inventory sha hash mismatch for revision %s. %s'
295
295
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
296
296
 
297
 
    def _testament(self, revision, tree):
298
 
        raise NotImplementedError(self._testament)
299
 
 
300
297
    def _validate_revision(self, tree, revision_id):
301
298
        """Make sure all revision entries match their checksum."""
302
299
 
309
306
            raise AssertionError()
310
307
        if not (rev.revision_id == revision_id):
311
308
            raise AssertionError()
312
 
        testament = self._testament(rev, tree)
313
 
        sha1 = testament.as_sha1()
 
309
        sha1 = self._testament_sha1(rev, tree)
314
310
        if sha1 != rev_info.sha1:
315
311
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
316
312
        if rev.revision_id in rev_to_sha1:
317
313
            raise BzrError('Revision {%s} given twice in the list'
318
 
                           % (rev.revision_id))
 
314
                    % (rev.revision_id))
319
315
        rev_to_sha1[rev.revision_id] = sha1
320
316
 
321
317
    def _update_tree(self, bundle_tree, revision_id):
356
352
 
357
353
        def do_patch(path, lines, encoding):
358
354
            if encoding == 'base64':
359
 
                patch = base64.b64decode(b''.join(lines))
 
355
                patch = base64.decodestring(''.join(lines))
360
356
            elif encoding is None:
361
 
                patch = b''.join(lines)
 
357
                patch =  ''.join(lines)
362
358
            else:
363
359
                raise ValueError(encoding)
364
360
            bundle_tree.note_patch(path, patch)
367
363
            info = extra.split(' // ')
368
364
            if len(info) < 2:
369
365
                raise BzrError('renamed action lines need both a from and to'
370
 
                               ': %r' % extra)
 
366
                        ': %r' % extra)
371
367
            old_path = info[0]
372
368
            if info[1].startswith('=> '):
373
369
                new_path = info[1][3:]
386
382
                # TODO: in the future we might allow file ids to be
387
383
                # given for removed entries
388
384
                raise BzrError('removed action lines should only have the path'
389
 
                               ': %r' % extra)
 
385
                        ': %r' % extra)
390
386
            path = info[0]
391
387
            bundle_tree.note_deletion(path)
392
388
 
394
390
            info = extra.split(' // ')
395
391
            if len(info) <= 1:
396
392
                raise BzrError('add action lines require the path and file id'
397
 
                               ': %r' % extra)
 
393
                        ': %r' % extra)
398
394
            elif len(info) > 5:
399
395
                raise BzrError('add action lines have fewer than 5 entries.'
400
 
                               ': %r' % extra)
 
396
                        ': %r' % extra)
401
397
            path = info[0]
402
398
            if not info[1].startswith('file-id:'):
403
399
                raise BzrError('The file-id should follow the path for an add'
404
 
                               ': %r' % extra)
 
400
                        ': %r' % extra)
405
401
            # This will be Unicode because of how the stream is read. Turn it
406
402
            # back into a utf8 file_id
407
403
            file_id = cache_utf8.encode(info[1][8:])
419
415
            info = extra.split(' // ')
420
416
            if len(info) < 1:
421
417
                raise BzrError('modified action lines have at least'
422
 
                               'the path in them: %r' % extra)
 
418
                        'the path in them: %r' % extra)
423
419
            path = info[0]
424
420
 
425
421
            last_modified, encoding = extra_info(info[1:], path)
428
424
                do_patch(path, lines, encoding)
429
425
 
430
426
        valid_actions = {
431
 
            'renamed': renamed,
432
 
            'removed': removed,
433
 
            'added': added,
434
 
            'modified': modified
 
427
            'renamed':renamed,
 
428
            'removed':removed,
 
429
            'added':added,
 
430
            'modified':modified
435
431
        }
436
432
        for action_line, lines in \
437
 
                self.get_revision_info(revision_id).tree_actions:
 
433
            self.get_revision_info(revision_id).tree_actions:
438
434
            first = action_line.find(' ')
439
435
            if first == -1:
440
436
                raise BzrError('Bogus action line'
441
 
                               ' (no opening space): %r' % action_line)
442
 
            second = action_line.find(' ', first + 1)
 
437
                        ' (no opening space): %r' % action_line)
 
438
            second = action_line.find(' ', first+1)
443
439
            if second == -1:
444
440
                raise BzrError('Bogus action line'
445
 
                               ' (missing second space): %r' % action_line)
 
441
                        ' (missing second space): %r' % action_line)
446
442
            action = action_line[:first]
447
 
            kind = action_line[first + 1:second]
 
443
            kind = action_line[first+1:second]
448
444
            if kind not in ('file', 'directory', 'symlink'):
449
445
                raise BzrError('Bogus action line'
450
 
                               ' (invalid object kind %r): %r' % (kind, action_line))
451
 
            extra = action_line[second + 1:]
 
446
                        ' (invalid object kind %r): %r' % (kind, action_line))
 
447
            extra = action_line[second+1:]
452
448
 
453
449
            if action not in valid_actions:
454
450
                raise BzrError('Bogus action line'
455
 
                               ' (unrecognized action): %r' % action_line)
 
451
                        ' (unrecognized action): %r' % action_line)
456
452
            valid_actions[action](kind, extra, lines)
457
453
 
458
454
    def install_revisions(self, target_repo, stream_input=True):
472
468
        return None, self.target, 'inapplicable'
473
469
 
474
470
 
475
 
class BundleTree(InventoryTree):
 
471
class BundleTree(Tree):
476
472
 
477
473
    def __init__(self, base_tree, revision_id):
478
474
        self.base_tree = base_tree
479
 
        self._renamed = {}  # Mapping from old_path => new_path
480
 
        self._renamed_r = {}  # new_path => old_path
481
 
        self._new_id = {}  # new_path => new_id
482
 
        self._new_id_r = {}  # new_id => new_path
483
 
        self._kinds = {}  # new_path => kind
484
 
        self._last_changed = {}  # new_id => revision_id
485
 
        self._executable = {}  # new_id => executable value
 
475
        self._renamed = {} # Mapping from old_path => new_path
 
476
        self._renamed_r = {} # new_path => old_path
 
477
        self._new_id = {} # new_path => new_id
 
478
        self._new_id_r = {} # new_id => new_path
 
479
        self._kinds = {} # new_id => kind
 
480
        self._last_changed = {} # new_id => revision_id
 
481
        self._executable = {} # new_id => executable value
486
482
        self.patches = {}
487
 
        self._targets = {}  # new path => new symlink target
 
483
        self._targets = {} # new path => new symlink target
488
484
        self.deleted = []
 
485
        self.contents_by_id = True
489
486
        self.revision_id = revision_id
490
487
        self._inventory = None
491
 
        self._base_inter = InterTree.get(self.base_tree, self)
492
488
 
493
489
    def __str__(self):
494
490
        return pprint.pformat(self.__dict__)
506
502
        """Files that don't exist in base need a new id."""
507
503
        self._new_id[new_path] = new_id
508
504
        self._new_id_r[new_id] = new_path
509
 
        self._kinds[new_path] = kind
 
505
        self._kinds[new_id] = kind
510
506
 
511
507
    def note_last_changed(self, file_id, revision_id):
512
508
        if (file_id in self._last_changed
513
509
                and self._last_changed[file_id] != revision_id):
514
510
            raise BzrError('Mismatched last-changed revision for file_id {%s}'
515
 
                           ': %s != %s' % (file_id,
516
 
                                           self._last_changed[file_id],
517
 
                                           revision_id))
 
511
                    ': %s != %s' % (file_id,
 
512
                                    self._last_changed[file_id],
 
513
                                    revision_id))
518
514
        self._last_changed[file_id] = revision_id
519
515
 
520
516
    def note_patch(self, new_path, patch):
539
535
        old_path = self._renamed.get(new_path)
540
536
        if old_path is not None:
541
537
            return old_path
542
 
        dirname, basename = os.path.split(new_path)
 
538
        dirname,basename = os.path.split(new_path)
543
539
        # dirname is not '' doesn't work, because
544
540
        # dirname may be a unicode entry, and is
545
541
        # requires the objects to be identical
551
547
                old_path = pathjoin(old_dir, basename)
552
548
        else:
553
549
            old_path = new_path
554
 
        # If the new path wasn't in renamed, the old one shouldn't be in
555
 
        # renamed_r
 
550
        #If the new path wasn't in renamed, the old one shouldn't be in
 
551
        #renamed_r
556
552
        if old_path in self._renamed_r:
557
553
            return None
558
554
        return old_path
568
564
            return new_path
569
565
        if new_path in self._renamed:
570
566
            return None
571
 
        dirname, basename = os.path.split(old_path)
 
567
        dirname,basename = os.path.split(old_path)
572
568
        if dirname != '':
573
569
            new_dir = self.new_path(dirname)
574
570
            if new_dir is None:
577
573
                new_path = pathjoin(new_dir, basename)
578
574
        else:
579
575
            new_path = old_path
580
 
        # If the old path wasn't in renamed, the new one shouldn't be in
581
 
        # renamed_r
 
576
        #If the old path wasn't in renamed, the new one shouldn't be in
 
577
        #renamed_r
582
578
        if new_path in self._renamed:
583
579
            return None
584
580
        return new_path
585
581
 
 
582
    def get_root_id(self):
 
583
        return self.path2id('')
 
584
 
586
585
    def path2id(self, path):
587
586
        """Return the id of the file present at path in the target tree."""
588
587
        file_id = self._new_id.get(path)
595
594
            return None
596
595
        return self.base_tree.path2id(old_path)
597
596
 
598
 
    def id2path(self, file_id, recurse='down'):
 
597
    def id2path(self, file_id):
599
598
        """Return the new path in the target tree of the file with id file_id"""
600
599
        path = self._new_id_r.get(file_id)
601
600
        if path is not None:
602
601
            return path
603
 
        old_path = self.base_tree.id2path(file_id, recurse)
 
602
        old_path = self.base_tree.id2path(file_id)
604
603
        if old_path is None:
605
 
            raise NoSuchId(file_id, self)
 
604
            return None
606
605
        if old_path in self.deleted:
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):
 
606
            return None
 
607
        return self.new_path(old_path)
 
608
 
 
609
    def old_contents_id(self, file_id):
 
610
        """Return the id in the base_tree for the given file_id.
 
611
        Return None if the file did not exist in base.
 
612
        """
 
613
        if self.contents_by_id:
 
614
            if self.base_tree.has_id(file_id):
 
615
                return file_id
 
616
            else:
 
617
                return None
 
618
        new_path = self.id2path(file_id)
 
619
        return self.base_tree.path2id(new_path)
 
620
 
 
621
    def get_file(self, file_id):
614
622
        """Return a file-like object containing the new contents of the
615
623
        file given by file_id.
616
624
 
618
626
                in the text-store, so that the file contents would
619
627
                then be cached.
620
628
        """
621
 
        old_path = self._base_inter.find_source_path(path)
622
 
        if old_path is None:
 
629
        base_id = self.old_contents_id(file_id)
 
630
        if (base_id is not None and
 
631
            base_id != self.base_tree.get_root_id()):
 
632
            patch_original = self.base_tree.get_file(base_id)
 
633
        else:
623
634
            patch_original = None
624
 
        else:
625
 
            patch_original = self.base_tree.get_file(old_path)
626
 
        file_patch = self.patches.get(path)
 
635
        file_patch = self.patches.get(self.id2path(file_id))
627
636
        if file_patch is None:
628
637
            if (patch_original is None and
629
 
                    self.kind(path) == 'directory'):
 
638
                self.kind(file_id) == 'directory'):
630
639
                return BytesIO()
631
640
            if patch_original is None:
632
641
                raise AssertionError("None: %s" % file_id)
633
642
            return patch_original
634
643
 
635
 
        if file_patch.startswith(b'\\'):
 
644
        if file_patch.startswith('\\'):
636
645
            raise ValueError(
637
646
                'Malformed patch for %s, %r' % (file_id, file_patch))
638
647
        return patched_file(file_patch, patch_original)
639
648
 
640
 
    def get_symlink_target(self, path):
 
649
    def get_symlink_target(self, file_id, path=None):
 
650
        if path is None:
 
651
            path = self.id2path(file_id)
641
652
        try:
642
653
            return self._targets[path]
643
654
        except KeyError:
644
 
            old_path = self.old_path(path)
645
 
            return self.base_tree.get_symlink_target(old_path)
646
 
 
647
 
    def kind(self, path):
648
 
        try:
649
 
            return self._kinds[path]
650
 
        except KeyError:
651
 
            old_path = self.old_path(path)
652
 
            return self.base_tree.kind(old_path)
653
 
 
654
 
    def get_file_revision(self, path):
 
655
            return self.base_tree.get_symlink_target(file_id)
 
656
 
 
657
    def kind(self, file_id):
 
658
        if file_id in self._kinds:
 
659
            return self._kinds[file_id]
 
660
        return self.base_tree.kind(file_id)
 
661
 
 
662
    def get_file_revision(self, file_id):
 
663
        path = self.id2path(file_id)
655
664
        if path in self._last_changed:
656
665
            return self._last_changed[path]
657
666
        else:
658
 
            old_path = self.old_path(path)
659
 
            return self.base_tree.get_file_revision(old_path)
 
667
            return self.base_tree.get_file_revision(file_id)
660
668
 
661
 
    def is_executable(self, path):
 
669
    def is_executable(self, file_id):
 
670
        path = self.id2path(file_id)
662
671
        if path in self._executable:
663
672
            return self._executable[path]
664
673
        else:
665
 
            old_path = self.old_path(path)
666
 
            return self.base_tree.is_executable(old_path)
 
674
            return self.base_tree.is_executable(file_id)
667
675
 
668
 
    def get_last_changed(self, path):
 
676
    def get_last_changed(self, file_id):
 
677
        path = self.id2path(file_id)
669
678
        if path in self._last_changed:
670
679
            return self._last_changed[path]
671
 
        old_path = self.old_path(path)
672
 
        return self.base_tree.get_file_revision(old_path)
 
680
        return self.base_tree.get_file_revision(file_id)
673
681
 
674
 
    def get_size_and_sha1(self, new_path):
 
682
    def get_size_and_sha1(self, file_id):
675
683
        """Return the size and sha1 hash of the given file id.
676
684
        If the file was not locally modified, this is extracted
677
685
        from the base_tree. Rather than re-reading the file.
678
686
        """
 
687
        new_path = self.id2path(file_id)
679
688
        if new_path is None:
680
689
            return None, None
681
690
        if new_path not in self.patches:
682
691
            # If the entry does not have a patch, then the
683
692
            # contents must be the same as in the base_tree
684
 
            base_path = self.old_path(new_path)
685
 
            text_size = self.base_tree.get_file_size(base_path)
686
 
            text_sha1 = self.base_tree.get_file_sha1(base_path)
 
693
            text_size = self.base_tree.get_file_size(file_id)
 
694
            text_sha1 = self.base_tree.get_file_sha1(file_id)
687
695
            return text_size, text_sha1
688
 
        fileobj = self.get_file(new_path)
 
696
        fileobj = self.get_file(file_id)
689
697
        content = fileobj.read()
690
698
        return len(content), sha_string(content)
691
699
 
697
705
        from os.path import dirname, basename
698
706
        inv = Inventory(None, self.revision_id)
699
707
 
700
 
        def add_entry(path, file_id):
 
708
        def add_entry(file_id):
 
709
            path = self.id2path(file_id)
 
710
            if path is None:
 
711
                return
701
712
            if path == '':
702
713
                parent_id = None
703
714
            else:
704
715
                parent_path = dirname(path)
705
716
                parent_id = self.path2id(parent_path)
706
717
 
707
 
            kind = self.kind(path)
708
 
            revision_id = self.get_last_changed(path)
 
718
            kind = self.kind(file_id)
 
719
            revision_id = self.get_last_changed(file_id)
709
720
 
710
721
            name = basename(path)
711
722
            if kind == 'directory':
712
723
                ie = InventoryDirectory(file_id, name, parent_id)
713
724
            elif kind == 'file':
714
725
                ie = InventoryFile(file_id, name, parent_id)
715
 
                ie.executable = self.is_executable(path)
 
726
                ie.executable = self.is_executable(file_id)
716
727
            elif kind == 'symlink':
717
728
                ie = InventoryLink(file_id, name, parent_id)
718
 
                ie.symlink_target = self.get_symlink_target(path)
 
729
                ie.symlink_target = self.get_symlink_target(file_id, path)
719
730
            ie.revision = revision_id
720
731
 
721
732
            if kind == 'file':
722
 
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(path)
 
733
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
723
734
                if ie.text_size is None:
724
735
                    raise BzrError(
725
736
                        'Got a text_size of None for file_id %r' % file_id)
727
738
 
728
739
        sorted_entries = self.sorted_path_id()
729
740
        for path, file_id in sorted_entries:
730
 
            add_entry(path, file_id)
 
741
            add_entry(file_id)
731
742
 
732
743
        return inv
733
744
 
743
754
    def all_file_ids(self):
744
755
        return {entry.file_id for path, entry in self.inventory.iter_entries()}
745
756
 
746
 
    def all_versioned_paths(self):
747
 
        return {path for path, entry in self.inventory.iter_entries()}
748
 
 
749
757
    def list_files(self, include_root=False, from_dir=None, recursive=True):
750
758
        # The only files returned by this are those from the version
751
759
        inv = self.inventory
758
766
                return
759
767
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
760
768
        if inv.root is not None and not include_root and from_dir is None:
761
 
            # skip the root for compatibility with the current apis.
 
769
            # skip the root for compatability with the current apis.
762
770
            next(entries)
763
771
        for path, entry in entries:
764
 
            yield path, 'V', entry.kind, entry
 
772
            yield path, 'V', entry.kind, entry.file_id, entry
765
773
 
766
774
    def sorted_path_id(self):
767
775
        paths = []
768
 
        for result in self._new_id.items():
 
776
        for result in viewitems(self._new_id):
769
777
            paths.append(result)
770
778
        for id in self.base_tree.all_file_ids():
771
 
            try:
772
 
                path = self.id2path(id, recurse='none')
773
 
            except NoSuchId:
 
779
            path = self.id2path(id)
 
780
            if path is None:
774
781
                continue
775
782
            paths.append((path, id))
776
783
        paths.sort()
781
788
    """Produce a file-like object with the patched version of a text"""
782
789
    from breezy.patches import iter_patched
783
790
    from breezy.iterablefile import IterableFile
784
 
    if file_patch == b"":
 
791
    if file_patch == "":
785
792
        return IterableFile(())
786
793
    # string.splitlines(True) also splits on '\r', but the iter_patched code
787
794
    # only expects to iterate over '\n' style lines
788
795
    return IterableFile(iter_patched(original,
789
 
                                     BytesIO(file_patch).readlines()))
 
796
                BytesIO(file_patch).readlines()))