/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-11-12 17:53:47 UTC
  • mfrom: (6813 trunk)
  • mto: This revision was merged to the branch mainline in revision 6819.
  • Revision ID: jelmer@jelmer.uk-20171112175347-wovgaqmkbyo8fo7f
Merge trunk.

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
21
22
import os
22
23
import pprint
23
24
 
24
 
from bzrlib import (
 
25
from .. import (
 
26
    cache_utf8,
25
27
    osutils,
26
28
    timestamp,
27
29
    )
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
 
30
from . import apply_bundle
 
31
from ..errors import (
 
32
    NoSuchId,
 
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
    BytesIO,
 
46
    viewitems,
 
47
    )
 
48
from ..testament import StrictTestament
 
49
from ..trace import mutter, warning
 
50
from ..tree import Tree
 
51
from ..bzr.xml5 import serializer_v5
43
52
 
44
53
 
45
54
class RevisionInfo(object):
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
            f = open(',,bogus-inv', 'wb')
 
291
            try:
 
292
                f.write(s)
 
293
            finally:
 
294
                f.close()
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 _validate_revision(self, tree, revision_id):
286
299
        """Make sure all revision entries match their checksum."""
287
300
 
288
 
        # This is a mapping from each revision id to it's sha hash
 
301
        # This is a mapping from each revision id to its sha hash
289
302
        rev_to_sha1 = {}
290
303
 
291
304
        rev = self.get_revision(revision_id)
294
307
            raise AssertionError()
295
308
        if not (rev.revision_id == revision_id):
296
309
            raise AssertionError()
297
 
        sha1 = self._testament_sha1(rev, inventory)
 
310
        sha1 = self._testament_sha1(rev, tree)
298
311
        if sha1 != rev_info.sha1:
299
312
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
300
313
        if rev.revision_id in rev_to_sha1:
313
326
            if last_changed is not None:
314
327
                # last_changed will be a Unicode string because of how it was
315
328
                # read. Convert it back to utf8.
316
 
                changed_revision_id = osutils.safe_revision_id(last_changed,
317
 
                                                               warn=False)
 
329
                changed_revision_id = cache_utf8.encode(last_changed)
318
330
            else:
319
331
                changed_revision_id = revision_id
320
332
            bundle_tree.note_last_changed(path, changed_revision_id)
327
339
                try:
328
340
                    name, value = info_item.split(':', 1)
329
341
                except ValueError:
330
 
                    raise 'Value %r has no colon' % info_item
 
342
                    raise ValueError('Value %r has no colon' % info_item)
331
343
                if name == 'last-changed':
332
344
                    last_changed = value
333
345
                elif name == 'executable':
389
401
                        ': %r' % extra)
390
402
            # This will be Unicode because of how the stream is read. Turn it
391
403
            # back into a utf8 file_id
392
 
            file_id = osutils.safe_file_id(info[1][8:], warn=False)
 
404
            file_id = cache_utf8.encode(info[1][8:])
393
405
 
394
406
            bundle_tree.note_id(file_id, path, kind)
395
407
            # this will be overridden in extra_info if executable is specified.
458
470
 
459
471
 
460
472
class BundleTree(Tree):
 
473
 
461
474
    def __init__(self, base_tree, revision_id):
462
475
        self.base_tree = base_tree
463
476
        self._renamed = {} # Mapping from old_path => new_path
523
536
        old_path = self._renamed.get(new_path)
524
537
        if old_path is not None:
525
538
            return old_path
526
 
        dirname,basename = os.path.split(new_path)
 
539
        dirname, basename = os.path.split(new_path)
527
540
        # dirname is not '' doesn't work, because
528
541
        # dirname may be a unicode entry, and is
529
542
        # requires the objects to be identical
552
565
            return new_path
553
566
        if new_path in self._renamed:
554
567
            return None
555
 
        dirname,basename = os.path.split(old_path)
 
568
        dirname, basename = os.path.split(old_path)
556
569
        if dirname != '':
557
570
            new_dir = self.new_path(dirname)
558
571
            if new_dir is None:
567
580
            return None
568
581
        return new_path
569
582
 
 
583
    def get_root_id(self):
 
584
        return self.path2id('')
 
585
 
570
586
    def path2id(self, path):
571
587
        """Return the id of the file present at path in the target tree."""
572
588
        file_id = self._new_id.get(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"""
606
619
        new_path = self.id2path(file_id)
607
620
        return self.base_tree.path2id(new_path)
608
621
 
609
 
    def get_file(self, file_id):
 
622
    def get_file(self, path, file_id=None):
610
623
        """Return a file-like object containing the new contents of the
611
624
        file given by file_id.
612
625
 
614
627
                in the text-store, so that the file contents would
615
628
                then be cached.
616
629
        """
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:
 
630
        try:
 
631
            patch_original = self.base_tree.get_file(path)
 
632
        except errors.NoSuchId:
622
633
            patch_original = None
623
 
        file_patch = self.patches.get(self.id2path(file_id))
 
634
        file_patch = self.patches.get(path)
624
635
        if file_patch is None:
625
636
            if (patch_original is None and
626
 
                self.get_kind(file_id) == 'directory'):
627
 
                return StringIO()
 
637
                self.kind(self.path2id(path)) == 'directory'):
 
638
                return BytesIO()
628
639
            if patch_original is None:
629
 
                raise AssertionError("None: %s" % file_id)
 
640
                raise AssertionError("None: %s" % path)
630
641
            return patch_original
631
642
 
632
643
        if file_patch.startswith('\\'):
633
644
            raise ValueError(
634
 
                'Malformed patch for %s, %r' % (file_id, file_patch))
 
645
                'Malformed patch for %s, %r' % (path, file_patch))
635
646
        return patched_file(file_patch, patch_original)
636
647
 
637
 
    def get_symlink_target(self, file_id):
638
 
        new_path = self.id2path(file_id)
 
648
    def get_symlink_target(self, file_id, path=None):
 
649
        if path is None:
 
650
            path = self.id2path(file_id)
639
651
        try:
640
 
            return self._targets[new_path]
 
652
            return self._targets[path]
641
653
        except KeyError:
642
654
            return self.base_tree.get_symlink_target(file_id)
643
655
 
644
 
    def get_kind(self, file_id):
 
656
    def kind(self, file_id):
645
657
        if file_id in self._kinds:
646
658
            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)
 
659
        return self.base_tree.kind(file_id)
 
660
 
 
661
    def get_file_revision(self, path, file_id=None):
 
662
        if path in self._last_changed:
 
663
            return self._last_changed[path]
 
664
        else:
 
665
            return self.base_tree.get_file_revision(path, file_id)
 
666
 
 
667
    def is_executable(self, path, file_id=None):
651
668
        if path in self._executable:
652
669
            return self._executable[path]
653
670
        else:
654
 
            return self.base_tree.inventory[file_id].executable
 
671
            return self.base_tree.is_executable(path, file_id)
655
672
 
656
673
    def get_last_changed(self, file_id):
657
674
        path = self.id2path(file_id)
658
675
        if path in self._last_changed:
659
676
            return self._last_changed[path]
660
 
        return self.base_tree.inventory[file_id].revision
 
677
        base_path = self.base_tree.id2path(file_id)
 
678
        return self.base_tree.get_file_revision(base_path, file_id)
661
679
 
662
680
    def get_size_and_sha1(self, file_id):
663
681
        """Return the size and sha1 hash of the given file id.
668
686
        if new_path is None:
669
687
            return None, None
670
688
        if new_path not in self.patches:
 
689
            base_path = self.base_tree.id2path(file_id)
671
690
            # If the entry does not have a patch, then the
672
691
            # 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
 
692
            text_size = self.base_tree.get_file_size(base_path, file_id)
 
693
            text_sha1 = self.base_tree.get_file_sha1(base_path, file_id)
 
694
            return text_size, text_sha1
677
695
        fileobj = self.get_file(file_id)
678
696
        content = fileobj.read()
679
697
        return len(content), sha_string(content)
684
702
        This need to be called before ever accessing self.inventory
685
703
        """
686
704
        from os.path import dirname, basename
687
 
        base_inv = self.base_tree.inventory
688
705
        inv = Inventory(None, self.revision_id)
689
706
 
690
707
        def add_entry(file_id):
697
714
                parent_path = dirname(path)
698
715
                parent_id = self.path2id(parent_path)
699
716
 
700
 
            kind = self.get_kind(file_id)
 
717
            kind = self.kind(file_id)
701
718
            revision_id = self.get_last_changed(file_id)
702
719
 
703
720
            name = basename(path)
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(file_id, path)
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:
 
731
            if kind == 'file':
717
732
                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)
 
733
                if ie.text_size is None:
 
734
                    raise BzrError(
 
735
                        'Got a text_size of None for file_id %r' % file_id)
720
736
            inv.add(ie)
721
737
 
722
738
        sorted_entries = self.sorted_path_id()
732
748
    # at that instant
733
749
    inventory = property(_get_inventory)
734
750
 
735
 
    def __iter__(self):
736
 
        for path, entry in self.inventory.iter_entries():
737
 
            yield entry.file_id
 
751
    root_inventory = property(_get_inventory)
 
752
 
 
753
    def all_file_ids(self):
 
754
        return {entry.file_id for path, entry in self.inventory.iter_entries()}
 
755
 
 
756
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
757
        # The only files returned by this are those from the version
 
758
        inv = self.inventory
 
759
        if from_dir is None:
 
760
            from_dir_id = None
 
761
        else:
 
762
            from_dir_id = inv.path2id(from_dir)
 
763
            if from_dir_id is None:
 
764
                # Directory not versioned
 
765
                return
 
766
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
 
767
        if inv.root is not None and not include_root and from_dir is None:
 
768
            # skip the root for compatability with the current apis.
 
769
            next(entries)
 
770
        for path, entry in entries:
 
771
            yield path, 'V', entry.kind, entry.file_id, entry
738
772
 
739
773
    def sorted_path_id(self):
740
774
        paths = []
741
 
        for result in self._new_id.iteritems():
 
775
        for result in viewitems(self._new_id):
742
776
            paths.append(result)
743
 
        for id in self.base_tree:
 
777
        for id in self.base_tree.all_file_ids():
744
778
            path = self.id2path(id)
745
779
            if path is None:
746
780
                continue
751
785
 
752
786
def patched_file(file_patch, original):
753
787
    """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
 
788
    from breezy.patches import iter_patched
 
789
    from breezy.iterablefile import IterableFile
756
790
    if file_patch == "":
757
791
        return IterableFile(())
758
792
    # string.splitlines(True) also splits on '\r', but the iter_patched code
759
793
    # only expects to iterate over '\n' style lines
760
794
    return IterableFile(iter_patched(original,
761
 
                StringIO(file_patch).readlines()))
 
795
                BytesIO(file_patch).readlines()))