17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
19
from __future__ import absolute_import
20
from cStringIO import StringIO
22
from io import BytesIO
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,
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 (
36
from ..bzr.inventory import (
42
from ..osutils import sha_string, pathjoin
43
from ..revision import Revision, NULL_REVISION
44
from ..sixish import (
47
from ..testament import StrictTestament
48
from ..trace import mutter, warning
49
from ..tree import Tree
50
from ..bzr.xml5 import serializer_v5
45
53
class RevisionInfo(object):
46
54
"""Gets filled out for each revision object that is read.
48
57
def __init__(self, revision_id):
49
58
self.revision_id = revision_id
66
75
def as_revision(self):
67
76
rev = Revision(revision_id=self.revision_id,
68
committer=self.committer,
69
timestamp=float(self.timestamp),
70
timezone=int(self.timezone),
71
inventory_sha1=self.inventory_sha1,
72
message='\n'.join(self.message))
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))
74
83
if self.parent_ids:
75
84
rev.parent_ids.extend(self.parent_ids)
278
289
if rev.revision_id != revision_id:
279
290
raise AssertionError()
280
291
if sha1 != rev.inventory_sha1:
281
open(',,bogus-inv', 'wb').write(s)
292
with open(',,bogus-inv', 'wb') as f:
282
294
warning('Inventory sha hash mismatch for revision %s. %s'
283
295
' != %s' % (revision_id, sha1, rev.inventory_sha1))
285
def _validate_revision(self, inventory, revision_id):
297
def _validate_revision(self, tree, revision_id):
286
298
"""Make sure all revision entries match their checksum."""
288
# This is a mapping from each revision id to it's sha hash
300
# This is a mapping from each revision id to its sha hash
291
303
rev = self.get_revision(revision_id)
413
424
do_patch(path, lines, encoding)
415
426
valid_actions = {
421
432
for action_line, lines in \
422
self.get_revision_info(revision_id).tree_actions:
433
self.get_revision_info(revision_id).tree_actions:
423
434
first = action_line.find(' ')
425
436
raise BzrError('Bogus action line'
426
' (no opening space): %r' % action_line)
427
second = action_line.find(' ', first+1)
437
' (no opening space): %r' % action_line)
438
second = action_line.find(' ', first + 1)
429
440
raise BzrError('Bogus action line'
430
' (missing second space): %r' % action_line)
441
' (missing second space): %r' % action_line)
431
442
action = action_line[:first]
432
kind = action_line[first+1:second]
443
kind = action_line[first + 1:second]
433
444
if kind not in ('file', 'directory', 'symlink'):
434
445
raise BzrError('Bogus action line'
435
' (invalid object kind %r): %r' % (kind, action_line))
436
extra = action_line[second+1:]
446
' (invalid object kind %r): %r' % (kind, action_line))
447
extra = action_line[second + 1:]
438
449
if action not in valid_actions:
439
450
raise BzrError('Bogus action line'
440
' (unrecognized action): %r' % action_line)
451
' (unrecognized action): %r' % action_line)
441
452
valid_actions[action](kind, extra, lines)
443
454
def install_revisions(self, target_repo, stream_input=True):
460
471
class BundleTree(Tree):
461
473
def __init__(self, base_tree, revision_id):
462
474
self.base_tree = base_tree
463
self._renamed = {} # Mapping from old_path => new_path
464
self._renamed_r = {} # new_path => old_path
465
self._new_id = {} # new_path => new_id
466
self._new_id_r = {} # new_id => new_path
467
self._kinds = {} # new_id => kind
468
self._last_changed = {} # new_id => revision_id
469
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_path => kind
480
self._last_changed = {} # new_id => revision_id
481
self._executable = {} # new_id => executable value
470
482
self.patches = {}
471
self._targets = {} # new path => new symlink target
483
self._targets = {} # new path => new symlink target
472
484
self.deleted = []
473
485
self.contents_by_id = True
474
486
self.revision_id = revision_id
490
502
"""Files that don't exist in base need a new id."""
491
503
self._new_id[new_path] = new_id
492
504
self._new_id_r[new_id] = new_path
493
self._kinds[new_id] = kind
505
self._kinds[new_path] = kind
495
507
def note_last_changed(self, file_id, revision_id):
496
508
if (file_id in self._last_changed
497
509
and self._last_changed[file_id] != revision_id):
498
510
raise BzrError('Mismatched last-changed revision for file_id {%s}'
499
': %s != %s' % (file_id,
500
self._last_changed[file_id],
511
': %s != %s' % (file_id,
512
self._last_changed[file_id],
502
514
self._last_changed[file_id] = revision_id
504
516
def note_patch(self, new_path, patch):
614
626
in the text-store, so that the file contents would
629
file_id = self.path2id(path)
617
630
base_id = self.old_contents_id(file_id)
618
631
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)
632
base_id != self.base_tree.get_root_id()):
633
old_path = self.base_tree.id2path(base_id)
634
patch_original = self.base_tree.get_file(old_path)
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'):
640
self.kind(path) == 'directory'):
628
642
if patch_original is None:
629
643
raise AssertionError("None: %s" % file_id)
630
644
return patch_original
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)
637
def get_symlink_target(self, file_id):
638
new_path = self.id2path(file_id)
640
return self._targets[new_path]
642
return self.base_tree.get_symlink_target(file_id)
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
649
def is_executable(self, file_id):
650
path = self.id2path(file_id)
651
def get_symlink_target(self, path):
653
return self._targets[path]
655
old_path = self.old_path(path)
656
return self.base_tree.get_symlink_target(old_path)
658
def kind(self, path):
660
return self._kinds[path]
662
old_path = self.old_path(path)
663
return self.base_tree.kind(old_path)
665
def get_file_revision(self, path):
666
if path in self._last_changed:
667
return self._last_changed[path]
669
old_path = self.old_path(path)
670
return self.base_tree.get_file_revision(old_path)
672
def is_executable(self, path):
651
673
if path in self._executable:
652
674
return self._executable[path]
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)
656
def get_last_changed(self, file_id):
657
path = self.id2path(file_id)
679
def get_last_changed(self, path):
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)
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.
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)
697
text_sha1 = self.base_tree.get_file_sha1(base_path)
698
return text_size, text_sha1
699
fileobj = self.get_file(new_path)
678
700
content = fileobj.read()
679
701
return len(content), sha_string(content)
684
706
This need to be called before ever accessing self.inventory
686
708
from os.path import dirname, basename
687
base_inv = self.base_tree.inventory
688
709
inv = Inventory(None, self.revision_id)
690
def add_entry(file_id):
691
path = self.id2path(file_id)
711
def add_entry(path, file_id):
697
715
parent_path = dirname(path)
698
716
parent_id = self.path2id(parent_path)
700
kind = self.get_kind(file_id)
701
revision_id = self.get_last_changed(file_id)
718
kind = self.kind(path)
719
revision_id = self.get_last_changed(path)
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)
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)
712
730
ie.revision = revision_id
714
if kind in ('directory', 'symlink'):
715
ie.text_size, ie.text_sha1 = None, None
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)
733
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(path)
734
if ie.text_size is None:
736
'Got a text_size of None for file_id %r' % file_id)
722
739
sorted_entries = self.sorted_path_id()
723
740
for path, file_id in sorted_entries:
741
add_entry(path, file_id)
732
749
# at that instant
733
750
inventory = property(_get_inventory)
736
for path, entry in self.inventory.iter_entries():
752
root_inventory = property(_get_inventory)
754
def all_file_ids(self):
755
return {entry.file_id for path, entry in self.inventory.iter_entries()}
757
def all_versioned_paths(self):
758
return {path for path, entry in self.inventory.iter_entries()}
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
766
from_dir_id = inv.path2id(from_dir)
767
if from_dir_id is None:
768
# Directory not versioned
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.
774
for path, entry in entries:
775
yield path, 'V', entry.kind, entry.file_id, entry
739
777
def sorted_path_id(self):
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)