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
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
19
from __future__ import absolute_import
20
22
from cStringIO import StringIO
29
30
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
31
from bzrlib.errors import (
35
from bzrlib.inventory import (
41
from bzrlib.osutils import sha_string, pathjoin
36
42
from bzrlib.revision import Revision, NULL_REVISION
37
43
from bzrlib.testament import StrictTestament
38
44
from bzrlib.trace import mutter, warning
39
import bzrlib.transport
40
45
from bzrlib.tree import Tree
41
import bzrlib.urlutils
42
46
from bzrlib.xml5 import serializer_v5
159
163
def get_base(self, revision):
160
164
revision_info = self.get_revision_info(revision.revision_id)
161
165
if revision_info.base_id is not None:
162
if revision_info.base_id == NULL_REVISION:
165
return revision_info.base_id
166
return revision_info.base_id
166
167
if len(revision.parent_ids) == 0:
167
168
# There is no base listed, and
168
169
# the lowest revision doesn't have a parent
169
170
# so this is probably against the empty tree
170
# and thus base truly is None
171
# and thus base truly is NULL_REVISION
173
174
return revision.parent_ids[-1]
203
204
self._validate_references_from_repository(repository)
204
205
revision_info = self.get_revision_info(revision_id)
205
206
inventory_revision_id = revision_id
206
bundle_tree = BundleTree(repository.revision_tree(base),
207
bundle_tree = BundleTree(repository.revision_tree(base),
207
208
inventory_revision_id)
208
209
self._update_tree(bundle_tree, revision_id)
210
211
inv = bundle_tree.inventory
211
212
self._validate_inventory(inv, revision_id)
212
self._validate_revision(inv, revision_id)
213
self._validate_revision(bundle_tree, revision_id)
214
215
return bundle_tree
242
243
for rev_info in self.revisions:
243
244
checked[rev_info.revision_id] = True
244
245
add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
246
247
for (rev, rev_info) in zip(self.real_revisions, self.revisions):
247
248
add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
251
252
for revision_id, sha1 in rev_to_sha.iteritems():
252
253
if repository.has_revision(revision_id):
253
testament = StrictTestament.from_revision(repository,
254
testament = StrictTestament.from_revision(repository,
255
256
local_sha1 = self._testament_sha1_from_revision(repository,
257
258
if sha1 != local_sha1:
258
raise BzrError('sha1 mismatch. For revision id {%s}'
259
raise BzrError('sha1 mismatch. For revision id {%s}'
259
260
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
281
282
if rev.revision_id != revision_id:
282
283
raise AssertionError()
283
284
if sha1 != rev.inventory_sha1:
284
open(',,bogus-inv', 'wb').write(s)
285
f = open(',,bogus-inv', 'wb')
285
290
warning('Inventory sha hash mismatch for revision %s. %s'
286
291
' != %s' % (revision_id, sha1, rev.inventory_sha1))
288
def _validate_revision(self, inventory, revision_id):
293
def _validate_revision(self, tree, revision_id):
289
294
"""Make sure all revision entries match their checksum."""
291
# This is a mapping from each revision id to it's sha hash
296
# This is a mapping from each revision id to its sha hash
294
299
rev = self.get_revision(revision_id)
295
300
rev_info = self.get_revision_info(revision_id)
296
301
if not (rev.revision_id == rev_info.revision_id):
297
302
raise AssertionError()
298
303
if not (rev.revision_id == revision_id):
299
304
raise AssertionError()
300
sha1 = self._testament_sha1(rev, inventory)
305
sha1 = self._testament_sha1(rev, tree)
301
306
if sha1 != rev_info.sha1:
302
307
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
303
308
if rev.revision_id in rev_to_sha1:
331
336
name, value = info_item.split(':', 1)
332
337
except ValueError:
333
raise 'Value %r has no colon' % info_item
338
raise ValueError('Value %r has no colon' % info_item)
334
339
if name == 'last-changed':
335
340
last_changed = value
336
341
elif name == 'executable':
581
590
if old_path in self.deleted:
583
if getattr(self.base_tree, 'path2id', None) is not None:
584
return self.base_tree.path2id(old_path)
586
return self.base_tree.inventory.path2id(old_path)
592
return self.base_tree.path2id(old_path)
588
594
def id2path(self, file_id):
589
595
"""Return the new path in the target tree of the file with id file_id"""
609
615
new_path = self.id2path(file_id)
610
616
return self.base_tree.path2id(new_path)
612
618
def get_file(self, file_id):
613
619
"""Return a file-like object containing the new contents of the
614
620
file given by file_id.
620
626
base_id = self.old_contents_id(file_id)
621
627
if (base_id is not None and
622
base_id != self.base_tree.inventory.root.file_id):
628
base_id != self.base_tree.get_root_id()):
623
629
patch_original = self.base_tree.get_file(base_id)
625
631
patch_original = None
626
632
file_patch = self.patches.get(self.id2path(file_id))
627
633
if file_patch is None:
628
if (patch_original is None and
629
self.get_kind(file_id) == 'directory'):
634
if (patch_original is None and
635
self.kind(file_id) == 'directory'):
630
636
return StringIO()
631
637
if patch_original is None:
632
638
raise AssertionError("None: %s" % file_id)
637
643
'Malformed patch for %s, %r' % (file_id, file_patch))
638
644
return patched_file(file_patch, patch_original)
640
def get_symlink_target(self, file_id):
641
new_path = self.id2path(file_id)
646
def get_symlink_target(self, file_id, path=None):
648
path = self.id2path(file_id)
643
return self._targets[new_path]
650
return self._targets[path]
645
652
return self.base_tree.get_symlink_target(file_id)
647
def get_kind(self, file_id):
654
def kind(self, file_id):
648
655
if file_id in self._kinds:
649
656
return self._kinds[file_id]
650
return self.base_tree.inventory[file_id].kind
657
return self.base_tree.kind(file_id)
659
def get_file_revision(self, file_id):
660
path = self.id2path(file_id)
661
if path in self._last_changed:
662
return self._last_changed[path]
664
return self.base_tree.get_file_revision(file_id)
652
666
def is_executable(self, file_id):
653
667
path = self.id2path(file_id)
654
668
if path in self._executable:
655
669
return self._executable[path]
657
return self.base_tree.inventory[file_id].executable
671
return self.base_tree.is_executable(file_id)
659
673
def get_last_changed(self, file_id):
660
674
path = self.id2path(file_id)
661
675
if path in self._last_changed:
662
676
return self._last_changed[path]
663
return self.base_tree.inventory[file_id].revision
677
return self.base_tree.get_file_revision(file_id)
665
679
def get_size_and_sha1(self, file_id):
666
680
"""Return the size and sha1 hash of the given file id.
673
687
if new_path not in self.patches:
674
688
# If the entry does not have a patch, then the
675
689
# contents must be the same as in the base_tree
676
ie = self.base_tree.inventory[file_id]
677
if ie.text_size is None:
678
return ie.text_size, ie.text_sha1
679
return int(ie.text_size), ie.text_sha1
690
text_size = self.base_tree.get_file_size(file_id)
691
text_sha1 = self.base_tree.get_file_sha1(file_id)
692
return text_size, text_sha1
680
693
fileobj = self.get_file(file_id)
681
694
content = fileobj.read()
682
695
return len(content), sha_string(content)
687
700
This need to be called before ever accessing self.inventory
689
702
from os.path import dirname, basename
690
base_inv = self.base_tree.inventory
691
703
inv = Inventory(None, self.revision_id)
693
705
def add_entry(file_id):
700
712
parent_path = dirname(path)
701
713
parent_id = self.path2id(parent_path)
703
kind = self.get_kind(file_id)
715
kind = self.kind(file_id)
704
716
revision_id = self.get_last_changed(file_id)
706
718
name = basename(path)
711
723
ie.executable = self.is_executable(file_id)
712
724
elif kind == 'symlink':
713
725
ie = InventoryLink(file_id, name, parent_id)
714
ie.symlink_target = self.get_symlink_target(file_id)
726
ie.symlink_target = self.get_symlink_target(file_id, path)
715
727
ie.revision = revision_id
717
if kind in ('directory', 'symlink'):
718
ie.text_size, ie.text_sha1 = None, None
720
730
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
721
if (ie.text_size is None) and (kind == 'file'):
722
raise BzrError('Got a text_size of None for file_id %r' % file_id)
731
if ie.text_size is None:
733
'Got a text_size of None for file_id %r' % file_id)
725
736
sorted_entries = self.sorted_path_id()
735
746
# at that instant
736
747
inventory = property(_get_inventory)
739
for path, entry in self.inventory.iter_entries():
749
root_inventory = property(_get_inventory)
751
def all_file_ids(self):
753
[entry.file_id for path, entry in self.inventory.iter_entries()])
755
def list_files(self, include_root=False, from_dir=None, recursive=True):
756
# The only files returned by this are those from the version
761
from_dir_id = inv.path2id(from_dir)
762
if from_dir_id is None:
763
# Directory not versioned
765
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
766
if inv.root is not None and not include_root and from_dir is None:
767
# skip the root for compatability with the current apis.
769
for path, entry in entries:
770
yield path, 'V', entry.kind, entry.file_id, entry
742
772
def sorted_path_id(self):
744
774
for result in self._new_id.iteritems():
745
775
paths.append(result)
746
for id in self.base_tree:
776
for id in self.base_tree.all_file_ids():
747
777
path = self.id2path(id)