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 (
37
from ..inventory import (
43
from ...osutils import sha_string, pathjoin
44
from ...revision import Revision, NULL_REVISION
45
from ...sixish import (
48
from ..testament import StrictTestament
49
from ...trace import mutter, warning
50
from ...tree import Tree
51
from ..xml5 import serializer_v5
45
54
class RevisionInfo(object):
46
55
"""Gets filled out for each revision object that is read.
48
58
def __init__(self, revision_id):
49
59
self.revision_id = revision_id
66
76
def as_revision(self):
67
77
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))
78
committer=self.committer,
79
timestamp=float(self.timestamp),
80
timezone=int(self.timezone),
81
inventory_sha1=self.inventory_sha1,
82
message='\n'.join(self.message))
74
84
if self.parent_ids:
75
85
rev.parent_ids.extend(self.parent_ids)
278
290
if rev.revision_id != revision_id:
279
291
raise AssertionError()
280
292
if sha1 != rev.inventory_sha1:
281
open(',,bogus-inv', 'wb').write(s)
293
with open(',,bogus-inv', 'wb') as f:
282
295
warning('Inventory sha hash mismatch for revision %s. %s'
283
296
' != %s' % (revision_id, sha1, rev.inventory_sha1))
285
def _validate_revision(self, inventory, revision_id):
298
def _testament(self, revision, tree):
299
raise NotImplementedError(self._testament)
301
def _validate_revision(self, tree, revision_id):
286
302
"""Make sure all revision entries match their checksum."""
288
# This is a mapping from each revision id to it's sha hash
304
# This is a mapping from each revision id to its sha hash
291
307
rev = self.get_revision(revision_id)
413
429
do_patch(path, lines, encoding)
415
431
valid_actions = {
421
437
for action_line, lines in \
422
self.get_revision_info(revision_id).tree_actions:
438
self.get_revision_info(revision_id).tree_actions:
423
439
first = action_line.find(' ')
425
441
raise BzrError('Bogus action line'
426
' (no opening space): %r' % action_line)
427
second = action_line.find(' ', first+1)
442
' (no opening space): %r' % action_line)
443
second = action_line.find(' ', first + 1)
429
445
raise BzrError('Bogus action line'
430
' (missing second space): %r' % action_line)
446
' (missing second space): %r' % action_line)
431
447
action = action_line[:first]
432
kind = action_line[first+1:second]
448
kind = action_line[first + 1:second]
433
449
if kind not in ('file', 'directory', 'symlink'):
434
450
raise BzrError('Bogus action line'
435
' (invalid object kind %r): %r' % (kind, action_line))
436
extra = action_line[second+1:]
451
' (invalid object kind %r): %r' % (kind, action_line))
452
extra = action_line[second + 1:]
438
454
if action not in valid_actions:
439
455
raise BzrError('Bogus action line'
440
' (unrecognized action): %r' % action_line)
456
' (unrecognized action): %r' % action_line)
441
457
valid_actions[action](kind, extra, lines)
443
459
def install_revisions(self, target_repo, stream_input=True):
460
476
class BundleTree(Tree):
461
478
def __init__(self, base_tree, revision_id):
462
479
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
480
self._renamed = {} # Mapping from old_path => new_path
481
self._renamed_r = {} # new_path => old_path
482
self._new_id = {} # new_path => new_id
483
self._new_id_r = {} # new_id => new_path
484
self._kinds = {} # new_path => kind
485
self._last_changed = {} # new_id => revision_id
486
self._executable = {} # new_id => executable value
470
487
self.patches = {}
471
self._targets = {} # new path => new symlink target
488
self._targets = {} # new path => new symlink target
472
489
self.deleted = []
473
self.contents_by_id = True
474
490
self.revision_id = revision_id
475
491
self._inventory = None
490
506
"""Files that don't exist in base need a new id."""
491
507
self._new_id[new_path] = new_id
492
508
self._new_id_r[new_id] = new_path
493
self._kinds[new_id] = kind
509
self._kinds[new_path] = kind
495
511
def note_last_changed(self, file_id, revision_id):
496
512
if (file_id in self._last_changed
497
513
and self._last_changed[file_id] != revision_id):
498
514
raise BzrError('Mismatched last-changed revision for file_id {%s}'
499
': %s != %s' % (file_id,
500
self._last_changed[file_id],
515
': %s != %s' % (file_id,
516
self._last_changed[file_id],
502
518
self._last_changed[file_id] = revision_id
504
520
def note_patch(self, new_path, patch):
590
603
old_path = self.base_tree.id2path(file_id)
591
604
if old_path is None:
605
raise NoSuchId(file_id, self)
593
606
if old_path in self.deleted:
595
return self.new_path(old_path)
597
def old_contents_id(self, file_id):
598
"""Return the id in the base_tree for the given file_id.
599
Return None if the file did not exist in base.
601
if self.contents_by_id:
602
if self.base_tree.has_id(file_id):
606
new_path = self.id2path(file_id)
607
return self.base_tree.path2id(new_path)
609
def get_file(self, file_id):
607
raise NoSuchId(file_id, self)
608
new_path = self.new_path(old_path)
610
raise NoSuchId(file_id, self)
613
def get_file(self, path):
610
614
"""Return a file-like object containing the new contents of the
611
615
file given by file_id.
614
618
in the text-store, so that the file contents would
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
file_id = self.path2id(path)
623
old_path = self.base_tree.id2path(file_id)
622
625
patch_original = None
623
file_patch = self.patches.get(self.id2path(file_id))
628
import pdb; pdb.set_trace()
629
patch_original = self.base_tree.get_file(old_path)
630
file_patch = self.patches.get(path)
624
631
if file_patch is None:
625
632
if (patch_original is None and
626
self.get_kind(file_id) == 'directory'):
633
self.kind(path) == 'directory'):
628
635
if patch_original is None:
629
636
raise AssertionError("None: %s" % file_id)
630
637
return patch_original
632
if file_patch.startswith('\\'):
639
if file_patch.startswith(b'\\'):
633
640
raise ValueError(
634
641
'Malformed patch for %s, %r' % (file_id, file_patch))
635
642
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)
644
def get_symlink_target(self, path):
646
return self._targets[path]
648
old_path = self.old_path(path)
649
return self.base_tree.get_symlink_target(old_path)
651
def kind(self, path):
653
return self._kinds[path]
655
old_path = self.old_path(path)
656
return self.base_tree.kind(old_path)
658
def get_file_revision(self, path):
659
if path in self._last_changed:
660
return self._last_changed[path]
662
old_path = self.old_path(path)
663
return self.base_tree.get_file_revision(old_path)
665
def is_executable(self, path):
651
666
if path in self._executable:
652
667
return self._executable[path]
654
return self.base_tree.inventory[file_id].executable
669
old_path = self.old_path(path)
670
return self.base_tree.is_executable(old_path)
656
def get_last_changed(self, file_id):
657
path = self.id2path(file_id)
672
def get_last_changed(self, path):
658
673
if path in self._last_changed:
659
674
return self._last_changed[path]
660
return self.base_tree.inventory[file_id].revision
675
old_path = self.old_path(path)
676
return self.base_tree.get_file_revision(old_path)
662
def get_size_and_sha1(self, file_id):
678
def get_size_and_sha1(self, new_path):
663
679
"""Return the size and sha1 hash of the given file id.
664
680
If the file was not locally modified, this is extracted
665
681
from the base_tree. Rather than re-reading the file.
667
new_path = self.id2path(file_id)
668
683
if new_path is None:
669
684
return None, None
670
685
if new_path not in self.patches:
671
686
# If the entry does not have a patch, then the
672
687
# 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)
688
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)
691
return text_size, text_sha1
692
fileobj = self.get_file(new_path)
678
693
content = fileobj.read()
679
694
return len(content), sha_string(content)
684
699
This need to be called before ever accessing self.inventory
686
701
from os.path import dirname, basename
687
base_inv = self.base_tree.inventory
688
702
inv = Inventory(None, self.revision_id)
690
def add_entry(file_id):
691
path = self.id2path(file_id)
704
def add_entry(path, file_id):
697
708
parent_path = dirname(path)
698
709
parent_id = self.path2id(parent_path)
700
kind = self.get_kind(file_id)
701
revision_id = self.get_last_changed(file_id)
711
kind = self.kind(path)
712
revision_id = self.get_last_changed(path)
703
714
name = basename(path)
704
715
if kind == 'directory':
705
716
ie = InventoryDirectory(file_id, name, parent_id)
706
717
elif kind == 'file':
707
718
ie = InventoryFile(file_id, name, parent_id)
708
ie.executable = self.is_executable(file_id)
719
ie.executable = self.is_executable(path)
709
720
elif kind == 'symlink':
710
721
ie = InventoryLink(file_id, name, parent_id)
711
ie.symlink_target = self.get_symlink_target(file_id)
722
ie.symlink_target = self.get_symlink_target(path)
712
723
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)
726
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(path)
727
if ie.text_size is None:
729
'Got a text_size of None for file_id %r' % file_id)
722
732
sorted_entries = self.sorted_path_id()
723
733
for path, file_id in sorted_entries:
734
add_entry(path, file_id)
732
742
# at that instant
733
743
inventory = property(_get_inventory)
736
for path, entry in self.inventory.iter_entries():
745
root_inventory = property(_get_inventory)
747
def all_file_ids(self):
748
return {entry.file_id for path, entry in self.inventory.iter_entries()}
750
def all_versioned_paths(self):
751
return {path for path, entry in self.inventory.iter_entries()}
753
def list_files(self, include_root=False, from_dir=None, recursive=True):
754
# The only files returned by this are those from the version
759
from_dir_id = inv.path2id(from_dir)
760
if from_dir_id is None:
761
# Directory not versioned
763
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
764
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.
767
for path, entry in entries:
768
yield path, 'V', entry.kind, entry
739
770
def sorted_path_id(self):
741
for result in self._new_id.iteritems():
772
for result in viewitems(self._new_id):
742
773
paths.append(result)
743
for id in self.base_tree:
744
path = self.id2path(id)
774
for id in self.base_tree.all_file_ids():
776
path = self.id2path(id)
747
779
paths.append((path, id))