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."""
28
28
import bzrlib.errors
29
29
from bzrlib.bundle import apply_bundle
30
from bzrlib.errors import (TestamentMismatch, BzrError,
30
from bzrlib.errors import (TestamentMismatch, BzrError,
31
31
MalformedHeader, MalformedPatches, NotABundle)
32
32
from bzrlib.inventory import (Inventory, InventoryEntry,
33
33
InventoryDirectory, InventoryFile,
78
78
for property in self.properties:
79
79
key_end = property.find(': ')
81
assert property.endswith(':')
81
if not property.endswith(':'):
82
raise ValueError(property)
82
83
key = str(property[:-1])
158
159
def get_base(self, revision):
159
160
revision_info = self.get_revision_info(revision.revision_id)
160
161
if revision_info.base_id is not None:
161
if revision_info.base_id == NULL_REVISION:
164
return revision_info.base_id
162
return revision_info.base_id
165
163
if len(revision.parent_ids) == 0:
166
164
# There is no base listed, and
167
165
# the lowest revision doesn't have a parent
168
166
# so this is probably against the empty tree
169
# and thus base truly is None
167
# and thus base truly is NULL_REVISION
172
170
return revision.parent_ids[-1]
196
194
def revision_tree(self, repository, revision_id, base=None):
197
195
revision = self.get_revision(revision_id)
198
196
base = self.get_base(revision)
199
assert base != revision_id
197
if base == revision_id:
198
raise AssertionError()
200
199
if not self._validated_revisions_against_repo:
201
200
self._validate_references_from_repository(repository)
202
201
revision_info = self.get_revision_info(revision_id)
203
202
inventory_revision_id = revision_id
204
bundle_tree = BundleTree(repository.revision_tree(base),
203
bundle_tree = BundleTree(repository.revision_tree(base),
205
204
inventory_revision_id)
206
205
self._update_tree(bundle_tree, revision_id)
240
239
for rev_info in self.revisions:
241
240
checked[rev_info.revision_id] = True
242
241
add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
244
243
for (rev, rev_info) in zip(self.real_revisions, self.revisions):
245
244
add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
249
248
for revision_id, sha1 in rev_to_sha.iteritems():
250
249
if repository.has_revision(revision_id):
251
testament = StrictTestament.from_revision(repository,
250
testament = StrictTestament.from_revision(repository,
253
252
local_sha1 = self._testament_sha1_from_revision(repository,
255
254
if sha1 != local_sha1:
256
raise BzrError('sha1 mismatch. For revision id {%s}'
255
raise BzrError('sha1 mismatch. For revision id {%s}'
257
256
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
260
259
elif revision_id not in checked:
261
260
missing[revision_id] = sha1
263
for inv_id, sha1 in inv_to_sha.iteritems():
264
if repository.has_revision(inv_id):
265
# Note: branch.get_inventory_sha1() just returns the value that
266
# is stored in the revision text, and that value may be out
267
# of date. This is bogus, because that means we aren't
268
# validating the actual text, just that we wrote and read the
269
# string. But for now, what the hell.
270
local_sha1 = repository.get_inventory_sha1(inv_id)
271
if sha1 != local_sha1:
272
raise BzrError('sha1 mismatch. For inventory id {%s}'
273
'local: %s, bundle: %s' %
274
(inv_id, local_sha1, sha1))
278
262
if len(missing) > 0:
279
263
# I don't know if this is an error yet
280
264
warning('Not all revision hashes could be validated.'
286
270
"""At this point we should have generated the BundleTree,
287
271
so build up an inventory, and make sure the hashes match.
290
assert inv is not None
292
273
# Now we should have a complete inventory entry.
293
274
s = serializer_v5.write_inventory_to_string(inv)
294
275
sha1 = sha_string(s)
295
276
# Target revision is the last entry in the real_revisions list
296
277
rev = self.get_revision(revision_id)
297
assert rev.revision_id == revision_id
278
if rev.revision_id != revision_id:
279
raise AssertionError()
298
280
if sha1 != rev.inventory_sha1:
299
open(',,bogus-inv', 'wb').write(s)
281
f = open(',,bogus-inv', 'wb')
300
286
warning('Inventory sha hash mismatch for revision %s. %s'
301
287
' != %s' % (revision_id, sha1, rev.inventory_sha1))
303
289
def _validate_revision(self, inventory, revision_id):
304
290
"""Make sure all revision entries match their checksum."""
306
# This is a mapping from each revision id to it's sha hash
292
# This is a mapping from each revision id to its sha hash
309
295
rev = self.get_revision(revision_id)
310
296
rev_info = self.get_revision_info(revision_id)
311
assert rev.revision_id == rev_info.revision_id
312
assert rev.revision_id == revision_id
297
if not (rev.revision_id == rev_info.revision_id):
298
raise AssertionError()
299
if not (rev.revision_id == revision_id):
300
raise AssertionError()
313
301
sha1 = self._testament_sha1(rev, inventory)
314
302
if sha1 != rev_info.sha1:
315
303
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
344
332
name, value = info_item.split(':', 1)
345
333
except ValueError:
346
raise 'Value %r has no colon' % info_item
334
raise ValueError('Value %r has no colon' % info_item)
347
335
if name == 'last-changed':
348
336
last_changed = value
349
337
elif name == 'executable':
350
assert value in ('yes', 'no'), value
351
338
val = (value == 'yes')
352
339
bundle_tree.note_executable(new_path, val)
353
340
elif name == 'target':
357
344
return last_changed, encoding
359
346
def do_patch(path, lines, encoding):
360
if encoding is not None:
361
assert encoding == 'base64'
347
if encoding == 'base64':
362
348
patch = base64.decodestring(''.join(lines))
349
elif encoding is None:
364
350
patch = ''.join(lines)
352
raise ValueError(encoding)
365
353
bundle_tree.note_patch(path, patch)
367
355
def renamed(kind, extra, lines):
496
484
def note_rename(self, old_path, new_path):
497
485
"""A file/directory has been renamed from old_path => new_path"""
498
assert new_path not in self._renamed
499
assert old_path not in self._renamed_r
486
if new_path in self._renamed:
487
raise AssertionError(new_path)
488
if old_path in self._renamed_r:
489
raise AssertionError(old_path)
500
490
self._renamed[new_path] = old_path
501
491
self._renamed_r[old_path] = new_path
533
523
def old_path(self, new_path):
534
524
"""Get the old_path (path in the base_tree) for the file at new_path"""
535
assert new_path[:1] not in ('\\', '/')
525
if new_path[:1] in ('\\', '/'):
526
raise ValueError(new_path)
536
527
old_path = self._renamed.get(new_path)
537
528
if old_path is not None:
553
544
if old_path in self._renamed_r:
557
548
def new_path(self, old_path):
558
549
"""Get the new_path (path in the target_tree) for the file at old_path
559
550
in the base tree.
561
assert old_path[:1] not in ('\\', '/')
552
if old_path[:1] in ('\\', '/'):
553
raise ValueError(old_path)
562
554
new_path = self._renamed_r.get(old_path)
563
555
if new_path is not None:
618
610
new_path = self.id2path(file_id)
619
611
return self.base_tree.path2id(new_path)
621
613
def get_file(self, file_id):
622
614
"""Return a file-like object containing the new contents of the
623
615
file given by file_id.
634
626
patch_original = None
635
627
file_patch = self.patches.get(self.id2path(file_id))
636
628
if file_patch is None:
637
if (patch_original is None and
629
if (patch_original is None and
638
630
self.get_kind(file_id) == 'directory'):
639
631
return StringIO()
640
assert patch_original is not None, "None: %s" % file_id
632
if patch_original is None:
633
raise AssertionError("None: %s" % file_id)
641
634
return patch_original
643
assert not file_patch.startswith('\\'), \
644
'Malformed patch for %s, %r' % (file_id, file_patch)
636
if file_patch.startswith('\\'):
638
'Malformed patch for %s, %r' % (file_id, file_patch))
645
639
return patched_file(file_patch, patch_original)
647
641
def get_symlink_target(self, file_id):
694
688
This need to be called before ever accessing self.inventory
696
690
from os.path import dirname, basename
698
assert self.base_tree is not None
699
691
base_inv = self.base_tree.inventory
700
692
inv = Inventory(None, self.revision_id)
723
715
ie.symlink_target = self.get_symlink_target(file_id)
724
716
ie.revision = revision_id
726
if kind in ('directory', 'symlink'):
727
ie.text_size, ie.text_sha1 = None, None
729
719
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
730
if (ie.text_size is None) and (kind == 'file'):
731
raise BzrError('Got a text_size of None for file_id %r' % file_id)
720
if ie.text_size is None:
722
'Got a text_size of None for file_id %r' % file_id)
734
725
sorted_entries = self.sorted_path_id()