/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: 2018-08-26 02:01:46 UTC
  • mto: This revision was merged to the branch mainline in revision 7087.
  • Revision ID: jelmer@jelmer.uk-20180826020146-owq7fxsr6ermorlh
Fix remaining warnings on Python 3.

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
    )
 
36
from ..bzr.inventory import (
 
37
    Inventory,
 
38
    InventoryDirectory,
 
39
    InventoryFile,
 
40
    InventoryLink,
 
41
    )
 
42
from ..osutils import sha_string, pathjoin
 
43
from ..revision import Revision, NULL_REVISION
 
44
from ..sixish import (
 
45
    viewitems,
 
46
    )
 
47
from ..testament import StrictTestament
 
48
from ..trace import mutter, warning
 
49
from ..tree import Tree
 
50
from ..bzr.xml5 import serializer_v5
43
51
 
44
52
 
45
53
class RevisionInfo(object):
46
54
    """Gets filled out for each revision object that is read.
47
55
    """
 
56
 
48
57
    def __init__(self, revision_id):
49
58
        self.revision_id = revision_id
50
59
        self.sha1 = None
99
108
        revision_info.timestamp = revision.timestamp
100
109
        revision_info.message = revision.message.split('\n')
101
110
        revision_info.properties = [': '.join(p) for p in
102
 
                                    revision.properties.iteritems()]
 
111
                                    viewitems(revision.properties)]
103
112
        return revision_info
104
113
 
105
114
 
136
145
        split up, based on the assumptions that can be made
137
146
        when information is missing.
138
147
        """
139
 
        from bzrlib.timestamp import unpack_highres_date
 
148
        from breezy.timestamp import unpack_highres_date
140
149
        # Put in all of the guessable information.
141
150
        if not self.timestamp and self.date:
142
151
            self.timestamp, self.timezone = unpack_highres_date(self.date)
206
215
 
207
216
        inv = bundle_tree.inventory
208
217
        self._validate_inventory(inv, revision_id)
209
 
        self._validate_revision(inv, revision_id)
 
218
        self._validate_revision(bundle_tree, revision_id)
210
219
 
211
220
        return bundle_tree
212
221
 
245
254
 
246
255
        count = 0
247
256
        missing = {}
248
 
        for revision_id, sha1 in rev_to_sha.iteritems():
 
257
        for revision_id, sha1 in viewitems(rev_to_sha):
249
258
            if repository.has_revision(revision_id):
250
259
                testament = StrictTestament.from_revision(repository,
251
260
                                                          revision_id)
278
287
        if rev.revision_id != revision_id:
279
288
            raise AssertionError()
280
289
        if sha1 != rev.inventory_sha1:
281
 
            open(',,bogus-inv', 'wb').write(s)
 
290
            with open(',,bogus-inv', 'wb') as f:
 
291
                f.write(s)
282
292
            warning('Inventory sha hash mismatch for revision %s. %s'
283
293
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
284
294
 
285
 
    def _validate_revision(self, inventory, revision_id):
 
295
    def _validate_revision(self, tree, revision_id):
286
296
        """Make sure all revision entries match their checksum."""
287
297
 
288
 
        # This is a mapping from each revision id to it's sha hash
 
298
        # This is a mapping from each revision id to its sha hash
289
299
        rev_to_sha1 = {}
290
300
 
291
301
        rev = self.get_revision(revision_id)
294
304
            raise AssertionError()
295
305
        if not (rev.revision_id == revision_id):
296
306
            raise AssertionError()
297
 
        sha1 = self._testament_sha1(rev, inventory)
 
307
        sha1 = self._testament_sha1(rev, tree)
298
308
        if sha1 != rev_info.sha1:
299
309
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
300
310
        if rev.revision_id in rev_to_sha1:
313
323
            if last_changed is not None:
314
324
                # last_changed will be a Unicode string because of how it was
315
325
                # read. Convert it back to utf8.
316
 
                changed_revision_id = osutils.safe_revision_id(last_changed,
317
 
                                                               warn=False)
 
326
                changed_revision_id = cache_utf8.encode(last_changed)
318
327
            else:
319
328
                changed_revision_id = revision_id
320
329
            bundle_tree.note_last_changed(path, changed_revision_id)
327
336
                try:
328
337
                    name, value = info_item.split(':', 1)
329
338
                except ValueError:
330
 
                    raise 'Value %r has no colon' % info_item
 
339
                    raise ValueError('Value %r has no colon' % info_item)
331
340
                if name == 'last-changed':
332
341
                    last_changed = value
333
342
                elif name == 'executable':
341
350
 
342
351
        def do_patch(path, lines, encoding):
343
352
            if encoding == 'base64':
344
 
                patch = base64.decodestring(''.join(lines))
 
353
                patch = base64.b64decode(b''.join(lines))
345
354
            elif encoding is None:
346
 
                patch =  ''.join(lines)
 
355
                patch =  b''.join(lines)
347
356
            else:
348
357
                raise ValueError(encoding)
349
358
            bundle_tree.note_patch(path, patch)
389
398
                        ': %r' % extra)
390
399
            # This will be Unicode because of how the stream is read. Turn it
391
400
            # back into a utf8 file_id
392
 
            file_id = osutils.safe_file_id(info[1][8:], warn=False)
 
401
            file_id = cache_utf8.encode(info[1][8:])
393
402
 
394
403
            bundle_tree.note_id(file_id, path, kind)
395
404
            # this will be overridden in extra_info if executable is specified.
458
467
 
459
468
 
460
469
class BundleTree(Tree):
 
470
 
461
471
    def __init__(self, base_tree, revision_id):
462
472
        self.base_tree = base_tree
463
473
        self._renamed = {} # Mapping from old_path => new_path
464
474
        self._renamed_r = {} # new_path => old_path
465
475
        self._new_id = {} # new_path => new_id
466
476
        self._new_id_r = {} # new_id => new_path
467
 
        self._kinds = {} # new_id => kind
 
477
        self._kinds = {} # new_path => kind
468
478
        self._last_changed = {} # new_id => revision_id
469
479
        self._executable = {} # new_id => executable value
470
480
        self.patches = {}
490
500
        """Files that don't exist in base need a new id."""
491
501
        self._new_id[new_path] = new_id
492
502
        self._new_id_r[new_id] = new_path
493
 
        self._kinds[new_id] = kind
 
503
        self._kinds[new_path] = kind
494
504
 
495
505
    def note_last_changed(self, file_id, revision_id):
496
506
        if (file_id in self._last_changed
523
533
        old_path = self._renamed.get(new_path)
524
534
        if old_path is not None:
525
535
            return old_path
526
 
        dirname,basename = os.path.split(new_path)
 
536
        dirname, basename = os.path.split(new_path)
527
537
        # dirname is not '' doesn't work, because
528
538
        # dirname may be a unicode entry, and is
529
539
        # requires the objects to be identical
552
562
            return new_path
553
563
        if new_path in self._renamed:
554
564
            return None
555
 
        dirname,basename = os.path.split(old_path)
 
565
        dirname, basename = os.path.split(old_path)
556
566
        if dirname != '':
557
567
            new_dir = self.new_path(dirname)
558
568
            if new_dir is None:
567
577
            return None
568
578
        return new_path
569
579
 
 
580
    def get_root_id(self):
 
581
        return self.path2id('')
 
582
 
570
583
    def path2id(self, path):
571
584
        """Return the id of the file present at path in the target tree."""
572
585
        file_id = self._new_id.get(path)
577
590
            return None
578
591
        if old_path in self.deleted:
579
592
            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)
 
593
        return self.base_tree.path2id(old_path)
584
594
 
585
595
    def id2path(self, file_id):
586
596
        """Return the new path in the target tree of the file with id file_id"""
606
616
        new_path = self.id2path(file_id)
607
617
        return self.base_tree.path2id(new_path)
608
618
 
609
 
    def get_file(self, file_id):
 
619
    def get_file(self, path, file_id=None):
610
620
        """Return a file-like object containing the new contents of the
611
621
        file given by file_id.
612
622
 
614
624
                in the text-store, so that the file contents would
615
625
                then be cached.
616
626
        """
 
627
        if file_id is None:
 
628
            file_id = self.path2id(path)
617
629
        base_id = self.old_contents_id(file_id)
618
630
        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)
 
631
            base_id != self.base_tree.get_root_id()):
 
632
            old_path = self.old_path(path)
 
633
            patch_original = self.base_tree.get_file(
 
634
                    old_path, base_id)
621
635
        else:
622
636
            patch_original = None
623
 
        file_patch = self.patches.get(self.id2path(file_id))
 
637
        file_patch = self.patches.get(path)
624
638
        if file_patch is None:
625
639
            if (patch_original is None and
626
 
                self.get_kind(file_id) == 'directory'):
627
 
                return StringIO()
 
640
                self.kind(path, file_id) == 'directory'):
 
641
                return BytesIO()
628
642
            if patch_original is None:
629
643
                raise AssertionError("None: %s" % file_id)
630
644
            return patch_original
631
645
 
632
 
        if file_patch.startswith('\\'):
 
646
        if file_patch.startswith(b'\\'):
633
647
            raise ValueError(
634
648
                'Malformed patch for %s, %r' % (file_id, file_patch))
635
649
        return patched_file(file_patch, patch_original)
636
650
 
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)
 
651
    def get_symlink_target(self, path, file_id=None):
 
652
        try:
 
653
            return self._targets[path]
 
654
        except KeyError:
 
655
            old_path = self.old_path(path)
 
656
            return self.base_tree.get_symlink_target(old_path, file_id)
 
657
 
 
658
    def kind(self, path, file_id=None):
 
659
        try:
 
660
            return self._kinds[path]
 
661
        except KeyError:
 
662
            old_path = self.old_path(path)
 
663
            return self.base_tree.kind(old_path, file_id)
 
664
 
 
665
    def get_file_revision(self, path, file_id=None):
 
666
        if path in self._last_changed:
 
667
            return self._last_changed[path]
 
668
        else:
 
669
            old_path = self.old_path(path)
 
670
            return self.base_tree.get_file_revision(old_path, file_id)
 
671
 
 
672
    def is_executable(self, path, file_id=None):
651
673
        if path in self._executable:
652
674
            return self._executable[path]
653
675
        else:
654
 
            return self.base_tree.inventory[file_id].executable
 
676
            old_path = self.old_path(path)
 
677
            return self.base_tree.is_executable(old_path, file_id)
655
678
 
656
 
    def get_last_changed(self, file_id):
657
 
        path = self.id2path(file_id)
 
679
    def get_last_changed(self, path, file_id=None):
658
680
        if path in self._last_changed:
659
681
            return self._last_changed[path]
660
 
        return self.base_tree.inventory[file_id].revision
 
682
        old_path = self.old_path(path)
 
683
        return self.base_tree.get_file_revision(old_path, file_id)
661
684
 
662
 
    def get_size_and_sha1(self, file_id):
 
685
    def get_size_and_sha1(self, new_path, file_id=None):
663
686
        """Return the size and sha1 hash of the given file id.
664
687
        If the file was not locally modified, this is extracted
665
688
        from the base_tree. Rather than re-reading the file.
666
689
        """
667
 
        new_path = self.id2path(file_id)
668
690
        if new_path is None:
669
691
            return None, None
670
692
        if new_path not in self.patches:
671
693
            # If the entry does not have a patch, then the
672
694
            # 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)
 
695
            base_path = self.old_path(new_path)
 
696
            text_size = self.base_tree.get_file_size(base_path, file_id)
 
697
            text_sha1 = self.base_tree.get_file_sha1(base_path, file_id)
 
698
            return text_size, text_sha1
 
699
        fileobj = self.get_file(new_path, file_id)
678
700
        content = fileobj.read()
679
701
        return len(content), sha_string(content)
680
702
 
684
706
        This need to be called before ever accessing self.inventory
685
707
        """
686
708
        from os.path import dirname, basename
687
 
        base_inv = self.base_tree.inventory
688
709
        inv = Inventory(None, self.revision_id)
689
710
 
690
 
        def add_entry(file_id):
691
 
            path = self.id2path(file_id)
692
 
            if path is None:
693
 
                return
 
711
        def add_entry(path, file_id):
694
712
            if path == '':
695
713
                parent_id = None
696
714
            else:
697
715
                parent_path = dirname(path)
698
716
                parent_id = self.path2id(parent_path)
699
717
 
700
 
            kind = self.get_kind(file_id)
701
 
            revision_id = self.get_last_changed(file_id)
 
718
            kind = self.kind(path, file_id)
 
719
            revision_id = self.get_last_changed(path, file_id)
702
720
 
703
721
            name = basename(path)
704
722
            if kind == 'directory':
705
723
                ie = InventoryDirectory(file_id, name, parent_id)
706
724
            elif kind == 'file':
707
725
                ie = InventoryFile(file_id, name, parent_id)
708
 
                ie.executable = self.is_executable(file_id)
 
726
                ie.executable = self.is_executable(path, file_id)
709
727
            elif kind == 'symlink':
710
728
                ie = InventoryLink(file_id, name, parent_id)
711
 
                ie.symlink_target = self.get_symlink_target(file_id)
 
729
                ie.symlink_target = self.get_symlink_target(path, file_id)
712
730
            ie.revision = revision_id
713
731
 
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)
 
732
            if kind == 'file':
 
733
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(
 
734
                        path, file_id)
 
735
                if ie.text_size is None:
 
736
                    raise BzrError(
 
737
                        'Got a text_size of None for file_id %r' % file_id)
720
738
            inv.add(ie)
721
739
 
722
740
        sorted_entries = self.sorted_path_id()
723
741
        for path, file_id in sorted_entries:
724
 
            add_entry(file_id)
 
742
            add_entry(path, file_id)
725
743
 
726
744
        return inv
727
745
 
732
750
    # at that instant
733
751
    inventory = property(_get_inventory)
734
752
 
735
 
    def __iter__(self):
736
 
        for path, entry in self.inventory.iter_entries():
737
 
            yield entry.file_id
 
753
    root_inventory = property(_get_inventory)
 
754
 
 
755
    def all_file_ids(self):
 
756
        return {entry.file_id for path, entry in self.inventory.iter_entries()}
 
757
 
 
758
    def all_versioned_paths(self):
 
759
        return {path for path, entry in self.inventory.iter_entries()}
 
760
 
 
761
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
762
        # The only files returned by this are those from the version
 
763
        inv = self.inventory
 
764
        if from_dir is None:
 
765
            from_dir_id = None
 
766
        else:
 
767
            from_dir_id = inv.path2id(from_dir)
 
768
            if from_dir_id is None:
 
769
                # Directory not versioned
 
770
                return
 
771
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
 
772
        if inv.root is not None and not include_root and from_dir is None:
 
773
            # skip the root for compatability with the current apis.
 
774
            next(entries)
 
775
        for path, entry in entries:
 
776
            yield path, 'V', entry.kind, entry.file_id, entry
738
777
 
739
778
    def sorted_path_id(self):
740
779
        paths = []
741
 
        for result in self._new_id.iteritems():
 
780
        for result in viewitems(self._new_id):
742
781
            paths.append(result)
743
 
        for id in self.base_tree:
 
782
        for id in self.base_tree.all_file_ids():
744
783
            path = self.id2path(id)
745
784
            if path is None:
746
785
                continue
751
790
 
752
791
def patched_file(file_patch, original):
753
792
    """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 == "":
 
793
    from breezy.patches import iter_patched
 
794
    from breezy.iterablefile import IterableFile
 
795
    if file_patch == b"":
757
796
        return IterableFile(())
758
797
    # string.splitlines(True) also splits on '\r', but the iter_patched code
759
798
    # only expects to iterate over '\n' style lines
760
799
    return IterableFile(iter_patched(original,
761
 
                StringIO(file_patch).readlines()))
 
800
                BytesIO(file_patch).readlines()))