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
158
163
def get_base(self, revision):
159
164
revision_info = self.get_revision_info(revision.revision_id)
160
165
if revision_info.base_id is not None:
161
if revision_info.base_id == NULL_REVISION:
164
return revision_info.base_id
166
return revision_info.base_id
165
167
if len(revision.parent_ids) == 0:
166
168
# There is no base listed, and
167
169
# the lowest revision doesn't have a parent
168
170
# so this is probably against the empty tree
169
# and thus base truly is None
171
# and thus base truly is NULL_REVISION
172
174
return revision.parent_ids[-1]
194
196
raise KeyError(revision_id)
196
198
def revision_tree(self, repository, revision_id, base=None):
197
revision_id = osutils.safe_revision_id(revision_id)
198
199
revision = self.get_revision(revision_id)
199
200
base = self.get_base(revision)
200
assert base != revision_id
201
if base == revision_id:
202
raise AssertionError()
201
203
if not self._validated_revisions_against_repo:
202
204
self._validate_references_from_repository(repository)
203
205
revision_info = self.get_revision_info(revision_id)
204
206
inventory_revision_id = revision_id
205
bundle_tree = BundleTree(repository.revision_tree(base),
207
bundle_tree = BundleTree(repository.revision_tree(base),
206
208
inventory_revision_id)
207
209
self._update_tree(bundle_tree, revision_id)
209
211
inv = bundle_tree.inventory
210
212
self._validate_inventory(inv, revision_id)
211
self._validate_revision(inv, revision_id)
213
self._validate_revision(bundle_tree, revision_id)
213
215
return bundle_tree
241
243
for rev_info in self.revisions:
242
244
checked[rev_info.revision_id] = True
243
245
add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
245
247
for (rev, rev_info) in zip(self.real_revisions, self.revisions):
246
248
add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
250
252
for revision_id, sha1 in rev_to_sha.iteritems():
251
253
if repository.has_revision(revision_id):
252
testament = StrictTestament.from_revision(repository,
254
testament = StrictTestament.from_revision(repository,
254
256
local_sha1 = self._testament_sha1_from_revision(repository,
256
258
if sha1 != local_sha1:
257
raise BzrError('sha1 mismatch. For revision id {%s}'
259
raise BzrError('sha1 mismatch. For revision id {%s}'
258
260
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
261
263
elif revision_id not in checked:
262
264
missing[revision_id] = sha1
264
for inv_id, sha1 in inv_to_sha.iteritems():
265
if repository.has_revision(inv_id):
266
# Note: branch.get_inventory_sha1() just returns the value that
267
# is stored in the revision text, and that value may be out
268
# of date. This is bogus, because that means we aren't
269
# validating the actual text, just that we wrote and read the
270
# string. But for now, what the hell.
271
local_sha1 = repository.get_inventory_sha1(inv_id)
272
if sha1 != local_sha1:
273
raise BzrError('sha1 mismatch. For inventory id {%s}'
274
'local: %s, bundle: %s' %
275
(inv_id, local_sha1, sha1))
279
266
if len(missing) > 0:
280
267
# I don't know if this is an error yet
281
268
warning('Not all revision hashes could be validated.'
287
274
"""At this point we should have generated the BundleTree,
288
275
so build up an inventory, and make sure the hashes match.
291
assert inv is not None
293
277
# Now we should have a complete inventory entry.
294
278
s = serializer_v5.write_inventory_to_string(inv)
295
279
sha1 = sha_string(s)
296
280
# Target revision is the last entry in the real_revisions list
297
281
rev = self.get_revision(revision_id)
298
assert rev.revision_id == revision_id
282
if rev.revision_id != revision_id:
283
raise AssertionError()
299
284
if sha1 != rev.inventory_sha1:
300
open(',,bogus-inv', 'wb').write(s)
285
f = open(',,bogus-inv', 'wb')
301
290
warning('Inventory sha hash mismatch for revision %s. %s'
302
291
' != %s' % (revision_id, sha1, rev.inventory_sha1))
304
def _validate_revision(self, inventory, revision_id):
293
def _validate_revision(self, tree, revision_id):
305
294
"""Make sure all revision entries match their checksum."""
307
# 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
310
299
rev = self.get_revision(revision_id)
311
300
rev_info = self.get_revision_info(revision_id)
312
assert rev.revision_id == rev_info.revision_id
313
assert rev.revision_id == revision_id
314
sha1 = self._testament_sha1(rev, inventory)
301
if not (rev.revision_id == rev_info.revision_id):
302
raise AssertionError()
303
if not (rev.revision_id == revision_id):
304
raise AssertionError()
305
sha1 = self._testament_sha1(rev, tree)
315
306
if sha1 != rev_info.sha1:
316
307
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
317
308
if rev.revision_id in rev_to_sha1:
345
336
name, value = info_item.split(':', 1)
346
337
except ValueError:
347
raise 'Value %r has no colon' % info_item
338
raise ValueError('Value %r has no colon' % info_item)
348
339
if name == 'last-changed':
349
340
last_changed = value
350
341
elif name == 'executable':
351
assert value in ('yes', 'no'), value
352
342
val = (value == 'yes')
353
343
bundle_tree.note_executable(new_path, val)
354
344
elif name == 'target':
358
348
return last_changed, encoding
360
350
def do_patch(path, lines, encoding):
361
if encoding is not None:
362
assert encoding == 'base64'
351
if encoding == 'base64':
363
352
patch = base64.decodestring(''.join(lines))
353
elif encoding is None:
365
354
patch = ''.join(lines)
356
raise ValueError(encoding)
366
357
bundle_tree.note_patch(path, patch)
368
359
def renamed(kind, extra, lines):
497
489
def note_rename(self, old_path, new_path):
498
490
"""A file/directory has been renamed from old_path => new_path"""
499
assert new_path not in self._renamed
500
assert old_path not in self._renamed_r
491
if new_path in self._renamed:
492
raise AssertionError(new_path)
493
if old_path in self._renamed_r:
494
raise AssertionError(old_path)
501
495
self._renamed[new_path] = old_path
502
496
self._renamed_r[old_path] = new_path
554
549
if old_path in self._renamed_r:
558
553
def new_path(self, old_path):
559
554
"""Get the new_path (path in the target_tree) for the file at old_path
560
555
in the base tree.
562
assert old_path[:1] not in ('\\', '/')
557
if old_path[:1] in ('\\', '/'):
558
raise ValueError(old_path)
563
559
new_path = self._renamed_r.get(old_path)
564
560
if new_path is not None:
591
590
if old_path in self.deleted:
593
if getattr(self.base_tree, 'path2id', None) is not None:
594
return self.base_tree.path2id(old_path)
596
return self.base_tree.inventory.path2id(old_path)
592
return self.base_tree.path2id(old_path)
598
594
def id2path(self, file_id):
599
595
"""Return the new path in the target tree of the file with id file_id"""
630
626
base_id = self.old_contents_id(file_id)
631
627
if (base_id is not None and
632
base_id != self.base_tree.inventory.root.file_id):
628
base_id != self.base_tree.get_root_id()):
633
629
patch_original = self.base_tree.get_file(base_id)
635
631
patch_original = None
636
632
file_patch = self.patches.get(self.id2path(file_id))
637
633
if file_patch is None:
638
if (patch_original is None and
639
self.get_kind(file_id) == 'directory'):
634
if (patch_original is None and
635
self.kind(file_id) == 'directory'):
640
636
return StringIO()
641
assert patch_original is not None, "None: %s" % file_id
637
if patch_original is None:
638
raise AssertionError("None: %s" % file_id)
642
639
return patch_original
644
assert not file_patch.startswith('\\'), \
645
'Malformed patch for %s, %r' % (file_id, file_patch)
641
if file_patch.startswith('\\'):
643
'Malformed patch for %s, %r' % (file_id, file_patch))
646
644
return patched_file(file_patch, patch_original)
648
def get_symlink_target(self, file_id):
649
new_path = self.id2path(file_id)
646
def get_symlink_target(self, file_id, path=None):
648
path = self.id2path(file_id)
651
return self._targets[new_path]
650
return self._targets[path]
653
652
return self.base_tree.get_symlink_target(file_id)
655
def get_kind(self, file_id):
654
def kind(self, file_id):
656
655
if file_id in self._kinds:
657
656
return self._kinds[file_id]
658
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)
660
666
def is_executable(self, file_id):
661
667
path = self.id2path(file_id)
662
668
if path in self._executable:
663
669
return self._executable[path]
665
return self.base_tree.inventory[file_id].executable
671
return self.base_tree.is_executable(file_id)
667
673
def get_last_changed(self, file_id):
668
674
path = self.id2path(file_id)
669
675
if path in self._last_changed:
670
676
return self._last_changed[path]
671
return self.base_tree.inventory[file_id].revision
677
return self.base_tree.get_file_revision(file_id)
673
679
def get_size_and_sha1(self, file_id):
674
680
"""Return the size and sha1 hash of the given file id.
681
687
if new_path not in self.patches:
682
688
# If the entry does not have a patch, then the
683
689
# contents must be the same as in the base_tree
684
ie = self.base_tree.inventory[file_id]
685
if ie.text_size is None:
686
return ie.text_size, ie.text_sha1
687
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
688
693
fileobj = self.get_file(file_id)
689
694
content = fileobj.read()
690
695
return len(content), sha_string(content)
721
723
ie.executable = self.is_executable(file_id)
722
724
elif kind == 'symlink':
723
725
ie = InventoryLink(file_id, name, parent_id)
724
ie.symlink_target = self.get_symlink_target(file_id)
726
ie.symlink_target = self.get_symlink_target(file_id, path)
725
727
ie.revision = revision_id
727
if kind in ('directory', 'symlink'):
728
ie.text_size, ie.text_sha1 = None, None
730
730
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
731
if (ie.text_size is None) and (kind == 'file'):
732
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)
735
736
sorted_entries = self.sorted_path_id()
745
746
# at that instant
746
747
inventory = property(_get_inventory)
749
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
752
772
def sorted_path_id(self):
754
774
for result in self._new_id.iteritems():
755
775
paths.append(result)
756
for id in self.base_tree:
776
for id in self.base_tree.all_file_ids():
757
777
path = self.id2path(id)