19
19
from __future__ import absolute_import
22
from io import BytesIO
31
30
from . import apply_bundle
32
from ...errors import (
31
from ..errors import (
37
from ..inventory import (
35
from ..bzr.inventory import (
39
37
InventoryDirectory,
43
from ...osutils import sha_string, sha_strings, pathjoin
44
from ...revision import Revision, NULL_REVISION
45
from ...sixish import (
41
from ..osutils import sha_string, pathjoin
42
from ..revision import Revision, NULL_REVISION
43
from ..sixish import (
48
47
from ..testament import StrictTestament
49
from ...trace import mutter, warning
54
from ..xml5 import serializer_v5
48
from ..trace import mutter, warning
49
from ..tree import Tree
50
from ..bzr.xml5 import serializer_v5
57
53
class RevisionInfo(object):
58
54
"""Gets filled out for each revision object that is read.
61
56
def __init__(self, revision_id):
62
57
self.revision_id = revision_id
79
74
def as_revision(self):
80
75
rev = Revision(revision_id=self.revision_id,
81
committer=self.committer,
82
timestamp=float(self.timestamp),
83
timezone=int(self.timezone),
84
inventory_sha1=self.inventory_sha1,
85
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))
87
82
if self.parent_ids:
88
83
rev.parent_ids.extend(self.parent_ids)
286
279
so build up an inventory, and make sure the hashes match.
288
281
# Now we should have a complete inventory entry.
289
cs = serializer_v5.write_inventory_to_chunks(inv)
290
sha1 = sha_strings(cs)
282
s = serializer_v5.write_inventory_to_string(inv)
291
284
# Target revision is the last entry in the real_revisions list
292
285
rev = self.get_revision(revision_id)
293
286
if rev.revision_id != revision_id:
294
287
raise AssertionError()
295
288
if sha1 != rev.inventory_sha1:
296
289
with open(',,bogus-inv', 'wb') as f:
298
291
warning('Inventory sha hash mismatch for revision %s. %s'
299
292
' != %s' % (revision_id, sha1, rev.inventory_sha1))
301
def _testament(self, revision, tree):
302
raise NotImplementedError(self._testament)
304
294
def _validate_revision(self, tree, revision_id):
305
295
"""Make sure all revision entries match their checksum."""
313
303
raise AssertionError()
314
304
if not (rev.revision_id == revision_id):
315
305
raise AssertionError()
316
testament = self._testament(rev, tree)
317
sha1 = testament.as_sha1()
306
sha1 = self._testament_sha1(rev, tree)
318
307
if sha1 != rev_info.sha1:
319
308
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
320
309
if rev.revision_id in rev_to_sha1:
321
310
raise BzrError('Revision {%s} given twice in the list'
323
312
rev_to_sha1[rev.revision_id] = sha1
325
314
def _update_tree(self, bundle_tree, revision_id):
432
421
do_patch(path, lines, encoding)
434
423
valid_actions = {
440
429
for action_line, lines in \
441
self.get_revision_info(revision_id).tree_actions:
430
self.get_revision_info(revision_id).tree_actions:
442
431
first = action_line.find(' ')
444
433
raise BzrError('Bogus action line'
445
' (no opening space): %r' % action_line)
446
second = action_line.find(' ', first + 1)
434
' (no opening space): %r' % action_line)
435
second = action_line.find(' ', first+1)
448
437
raise BzrError('Bogus action line'
449
' (missing second space): %r' % action_line)
438
' (missing second space): %r' % action_line)
450
439
action = action_line[:first]
451
kind = action_line[first + 1:second]
440
kind = action_line[first+1:second]
452
441
if kind not in ('file', 'directory', 'symlink'):
453
442
raise BzrError('Bogus action line'
454
' (invalid object kind %r): %r' % (kind, action_line))
455
extra = action_line[second + 1:]
443
' (invalid object kind %r): %r' % (kind, action_line))
444
extra = action_line[second+1:]
457
446
if action not in valid_actions:
458
447
raise BzrError('Bogus action line'
459
' (unrecognized action): %r' % action_line)
448
' (unrecognized action): %r' % action_line)
460
449
valid_actions[action](kind, extra, lines)
462
451
def install_revisions(self, target_repo, stream_input=True):
481
470
def __init__(self, base_tree, revision_id):
482
471
self.base_tree = base_tree
483
self._renamed = {} # Mapping from old_path => new_path
484
self._renamed_r = {} # new_path => old_path
485
self._new_id = {} # new_path => new_id
486
self._new_id_r = {} # new_id => new_path
487
self._kinds = {} # new_path => kind
488
self._last_changed = {} # new_id => revision_id
489
self._executable = {} # new_id => executable value
472
self._renamed = {} # Mapping from old_path => new_path
473
self._renamed_r = {} # new_path => old_path
474
self._new_id = {} # new_path => new_id
475
self._new_id_r = {} # new_id => new_path
476
self._kinds = {} # new_path => kind
477
self._last_changed = {} # new_id => revision_id
478
self._executable = {} # new_id => executable value
490
479
self.patches = {}
491
self._targets = {} # new path => new symlink target
480
self._targets = {} # new path => new symlink target
492
481
self.deleted = []
482
self.contents_by_id = True
493
483
self.revision_id = revision_id
494
484
self._inventory = None
495
self._base_inter = InterTree.get(self.base_tree, self)
497
486
def __str__(self):
498
487
return pprint.pformat(self.__dict__)
600
592
return self.base_tree.path2id(old_path)
602
def id2path(self, file_id, recurse='down'):
594
def id2path(self, file_id):
603
595
"""Return the new path in the target tree of the file with id file_id"""
604
596
path = self._new_id_r.get(file_id)
605
597
if path is not None:
607
old_path = self.base_tree.id2path(file_id, recurse)
599
old_path = self.base_tree.id2path(file_id)
608
600
if old_path is None:
609
raise NoSuchId(file_id, self)
610
602
if old_path in self.deleted:
611
raise NoSuchId(file_id, self)
612
new_path = self.new_path(old_path)
614
raise NoSuchId(file_id, self)
617
def get_file(self, path):
604
return self.new_path(old_path)
606
def old_contents_id(self, file_id):
607
"""Return the id in the base_tree for the given file_id.
608
Return None if the file did not exist in base.
610
if self.contents_by_id:
611
if self.base_tree.has_id(file_id):
615
new_path = self.id2path(file_id)
616
return self.base_tree.path2id(new_path)
618
def get_file(self, path, file_id=None):
618
619
"""Return a file-like object containing the new contents of the
619
620
file given by file_id.
622
623
in the text-store, so that the file contents would
625
old_path = self._base_inter.find_source_path(path)
627
file_id = self.path2id(path)
628
base_id = self.old_contents_id(file_id)
629
if (base_id is not None and
630
base_id != self.base_tree.get_root_id()):
631
old_path = self.old_path(path)
632
patch_original = self.base_tree.get_file(
627
635
patch_original = None
629
patch_original = self.base_tree.get_file(old_path)
630
636
file_patch = self.patches.get(path)
631
637
if file_patch is None:
632
638
if (patch_original is None and
633
self.kind(path) == 'directory'):
639
self.kind(path, file_id) == 'directory'):
635
641
if patch_original is None:
636
642
raise AssertionError("None: %s" % file_id)
637
643
return patch_original
639
if file_patch.startswith(b'\\'):
645
if file_patch.startswith('\\'):
640
646
raise ValueError(
641
647
'Malformed patch for %s, %r' % (file_id, file_patch))
642
648
return patched_file(file_patch, patch_original)
644
def get_symlink_target(self, path):
650
def get_symlink_target(self, path, file_id=None):
646
652
return self._targets[path]
648
654
old_path = self.old_path(path)
649
return self.base_tree.get_symlink_target(old_path)
655
return self.base_tree.get_symlink_target(old_path, file_id)
651
def kind(self, path):
657
def kind(self, path, file_id=None):
653
659
return self._kinds[path]
655
661
old_path = self.old_path(path)
656
return self.base_tree.kind(old_path)
662
return self.base_tree.kind(old_path, file_id)
658
def get_file_revision(self, path):
664
def get_file_revision(self, path, file_id=None):
659
665
if path in self._last_changed:
660
666
return self._last_changed[path]
662
668
old_path = self.old_path(path)
663
return self.base_tree.get_file_revision(old_path)
669
return self.base_tree.get_file_revision(old_path, file_id)
665
def is_executable(self, path):
671
def is_executable(self, path, file_id=None):
666
672
if path in self._executable:
667
673
return self._executable[path]
669
675
old_path = self.old_path(path)
670
return self.base_tree.is_executable(old_path)
676
return self.base_tree.is_executable(old_path, file_id)
672
def get_last_changed(self, path):
678
def get_last_changed(self, path, file_id=None):
673
679
if path in self._last_changed:
674
680
return self._last_changed[path]
675
681
old_path = self.old_path(path)
676
return self.base_tree.get_file_revision(old_path)
682
return self.base_tree.get_file_revision(old_path, file_id)
678
def get_size_and_sha1(self, new_path):
684
def get_size_and_sha1(self, new_path, file_id=None):
679
685
"""Return the size and sha1 hash of the given file id.
680
686
If the file was not locally modified, this is extracted
681
687
from the base_tree. Rather than re-reading the file.
686
692
# If the entry does not have a patch, then the
687
693
# contents must be the same as in the base_tree
688
694
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)
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)
691
697
return text_size, text_sha1
692
fileobj = self.get_file(new_path)
698
fileobj = self.get_file(new_path, file_id)
693
699
content = fileobj.read()
694
700
return len(content), sha_string(content)
708
714
parent_path = dirname(path)
709
715
parent_id = self.path2id(parent_path)
711
kind = self.kind(path)
712
revision_id = self.get_last_changed(path)
717
kind = self.kind(path, file_id)
718
revision_id = self.get_last_changed(path, file_id)
714
720
name = basename(path)
715
721
if kind == 'directory':
716
722
ie = InventoryDirectory(file_id, name, parent_id)
717
723
elif kind == 'file':
718
724
ie = InventoryFile(file_id, name, parent_id)
719
ie.executable = self.is_executable(path)
725
ie.executable = self.is_executable(path, file_id)
720
726
elif kind == 'symlink':
721
727
ie = InventoryLink(file_id, name, parent_id)
722
ie.symlink_target = self.get_symlink_target(path)
728
ie.symlink_target = self.get_symlink_target(path, file_id)
723
729
ie.revision = revision_id
725
731
if kind == 'file':
726
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(path)
732
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(
727
734
if ie.text_size is None:
729
736
'Got a text_size of None for file_id %r' % file_id)
763
770
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
764
771
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.
772
# skip the root for compatability with the current apis.
767
774
for path, entry in entries:
768
yield path, 'V', entry.kind, entry
775
yield path, 'V', entry.kind, entry.file_id, entry
770
777
def sorted_path_id(self):
772
779
for result in viewitems(self._new_id):
773
780
paths.append(result)
774
781
for id in self.base_tree.all_file_ids():
776
path = self.id2path(id, recurse='none')
782
path = self.id2path(id)
779
785
paths.append((path, id))