1
# Copyright (C) 2006 Canonical Ltd
1
# Copyright (C) 2005-2010 Canonical Ltd
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
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
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
30
from . import apply_bundle
31
from ..errors import (
35
from ..inventory import (
41
from ..osutils import sha_string, pathjoin
42
from ..revision import Revision, NULL_REVISION
43
from ..sixish import (
47
from ..testament import StrictTestament
48
from ..trace import mutter, warning
49
from ..tree import Tree
50
from ..xml5 import serializer_v5
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
136
144
split up, based on the assumptions that can be made
137
145
when information is missing.
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)
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)
211
219
return bundle_tree
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,
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
f = open(',,bogus-inv', 'wb')
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)
294
306
raise AssertionError()
295
307
if not (rev.revision_id == revision_id):
296
308
raise AssertionError()
297
sha1 = self._testament_sha1(rev, inventory)
309
sha1 = self._testament_sha1(rev, tree)
298
310
if sha1 != rev_info.sha1:
299
311
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
300
312
if rev.revision_id in rev_to_sha1:
313
325
if last_changed is not None:
314
326
# last_changed will be a Unicode string because of how it was
315
327
# read. Convert it back to utf8.
316
changed_revision_id = osutils.safe_revision_id(last_changed,
328
changed_revision_id = cache_utf8.encode(last_changed)
319
330
changed_revision_id = revision_id
320
331
bundle_tree.note_last_changed(path, changed_revision_id)
328
339
name, value = info_item.split(':', 1)
329
340
except ValueError:
330
raise 'Value %r has no colon' % info_item
341
raise ValueError('Value %r has no colon' % info_item)
331
342
if name == 'last-changed':
332
343
last_changed = value
333
344
elif name == 'executable':
390
401
# This will be Unicode because of how the stream is read. Turn it
391
402
# back into a utf8 file_id
392
file_id = osutils.safe_file_id(info[1][8:], warn=False)
403
file_id = cache_utf8.encode(info[1][8:])
394
405
bundle_tree.note_id(file_id, path, kind)
395
406
# this will be overridden in extra_info if executable is specified.
578
593
if old_path in self.deleted:
580
if getattr(self.base_tree, 'path2id', None) is not None:
581
return self.base_tree.path2id(old_path)
583
return self.base_tree.inventory.path2id(old_path)
595
return self.base_tree.path2id(old_path)
585
597
def id2path(self, file_id):
586
598
"""Return the new path in the target tree of the file with id file_id"""
617
629
base_id = self.old_contents_id(file_id)
618
630
if (base_id is not None and
619
base_id != self.base_tree.inventory.root.file_id):
631
base_id != self.base_tree.get_root_id()):
620
632
patch_original = self.base_tree.get_file(base_id)
622
634
patch_original = None
623
635
file_patch = self.patches.get(self.id2path(file_id))
624
636
if file_patch is None:
625
637
if (patch_original is None and
626
self.get_kind(file_id) == 'directory'):
638
self.kind(file_id) == 'directory'):
628
640
if patch_original is None:
629
641
raise AssertionError("None: %s" % file_id)
630
642
return patch_original
634
646
'Malformed patch for %s, %r' % (file_id, file_patch))
635
647
return patched_file(file_patch, patch_original)
637
def get_symlink_target(self, file_id):
638
new_path = self.id2path(file_id)
649
def get_symlink_target(self, file_id, path=None):
651
path = self.id2path(file_id)
640
return self._targets[new_path]
653
return self._targets[path]
642
655
return self.base_tree.get_symlink_target(file_id)
644
def get_kind(self, file_id):
657
def kind(self, file_id):
645
658
if file_id in self._kinds:
646
659
return self._kinds[file_id]
647
return self.base_tree.inventory[file_id].kind
660
return self.base_tree.kind(file_id)
662
def get_file_revision(self, file_id):
663
path = self.id2path(file_id)
664
if path in self._last_changed:
665
return self._last_changed[path]
667
return self.base_tree.get_file_revision(file_id)
649
669
def is_executable(self, file_id):
650
670
path = self.id2path(file_id)
651
671
if path in self._executable:
652
672
return self._executable[path]
654
return self.base_tree.inventory[file_id].executable
674
return self.base_tree.is_executable(file_id)
656
676
def get_last_changed(self, file_id):
657
677
path = self.id2path(file_id)
658
678
if path in self._last_changed:
659
679
return self._last_changed[path]
660
return self.base_tree.inventory[file_id].revision
680
return self.base_tree.get_file_revision(file_id)
662
682
def get_size_and_sha1(self, file_id):
663
683
"""Return the size and sha1 hash of the given file id.
670
690
if new_path not in self.patches:
671
691
# If the entry does not have a patch, then the
672
692
# 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
693
text_size = self.base_tree.get_file_size(file_id)
694
text_sha1 = self.base_tree.get_file_sha1(file_id)
695
return text_size, text_sha1
677
696
fileobj = self.get_file(file_id)
678
697
content = fileobj.read()
679
698
return len(content), sha_string(content)
684
703
This need to be called before ever accessing self.inventory
686
705
from os.path import dirname, basename
687
base_inv = self.base_tree.inventory
688
706
inv = Inventory(None, self.revision_id)
690
708
def add_entry(file_id):
697
715
parent_path = dirname(path)
698
716
parent_id = self.path2id(parent_path)
700
kind = self.get_kind(file_id)
718
kind = self.kind(file_id)
701
719
revision_id = self.get_last_changed(file_id)
703
721
name = basename(path)
708
726
ie.executable = self.is_executable(file_id)
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(file_id, path)
712
730
ie.revision = revision_id
714
if kind in ('directory', 'symlink'):
715
ie.text_size, ie.text_sha1 = None, None
717
733
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)
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()
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 list_files(self, include_root=False, from_dir=None, recursive=True):
758
# The only files returned by this are those from the version
763
from_dir_id = inv.path2id(from_dir)
764
if from_dir_id is None:
765
# Directory not versioned
767
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
768
if inv.root is not None and not include_root and from_dir is None:
769
# skip the root for compatability with the current apis.
771
for path, entry in entries:
772
yield path, 'V', entry.kind, entry.file_id, entry
739
774
def sorted_path_id(self):
741
for result in self._new_id.iteritems():
776
for result in viewitems(self._new_id):
742
777
paths.append(result)
743
for id in self.base_tree:
778
for id in self.base_tree.all_file_ids():
744
779
path = self.id2path(id)
752
787
def patched_file(file_patch, original):
753
788
"""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
789
from breezy.patches import iter_patched
790
from breezy.iterablefile import IterableFile
756
791
if file_patch == "":
757
792
return IterableFile(())
758
793
# string.splitlines(True) also splits on '\r', but the iter_patched code
759
794
# only expects to iterate over '\n' style lines
760
795
return IterableFile(iter_patched(original,
761
StringIO(file_patch).readlines()))
796
BytesIO(file_patch).readlines()))