/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: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2018-07-04 12:50:55 UTC
  • mfrom: (7027.2.8 git-fixes)
  • Revision ID: breezy.the.bot@gmail.com-20180704125055-8nni25pn2439p48v
Fix eol handling in knits on Python 3, port fastimport plugin to Python 3.

Merged from https://code.launchpad.net/~jelmer/brz/fastimport-fixes/+merge/348924

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