1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
28
29
from bzrlib.bundle import apply_bundle
29
from bzrlib.errors import (
33
from bzrlib.inventory import (
39
from bzrlib.osutils import sha_string, pathjoin
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
40
36
from bzrlib.revision import Revision, NULL_REVISION
41
37
from bzrlib.testament import StrictTestament
42
38
from bzrlib.trace import mutter, warning
39
import bzrlib.transport
43
40
from bzrlib.tree import Tree
41
import bzrlib.urlutils
44
42
from bzrlib.xml5 import serializer_v5
80
78
for property in self.properties:
81
79
key_end = property.find(': ')
83
if not property.endswith(':'):
84
raise ValueError(property)
81
assert property.endswith(':')
85
82
key = str(property[:-1])
161
158
def get_base(self, revision):
162
159
revision_info = self.get_revision_info(revision.revision_id)
163
160
if revision_info.base_id is not None:
164
return revision_info.base_id
161
if revision_info.base_id == NULL_REVISION:
164
return revision_info.base_id
165
165
if len(revision.parent_ids) == 0:
166
166
# There is no base listed, and
167
167
# the lowest revision doesn't have a parent
168
168
# so this is probably against the empty tree
169
# and thus base truly is NULL_REVISION
169
# and thus base truly is None
172
172
return revision.parent_ids[-1]
194
194
raise KeyError(revision_id)
196
196
def revision_tree(self, repository, revision_id, base=None):
197
revision_id = osutils.safe_revision_id(revision_id)
197
198
revision = self.get_revision(revision_id)
198
199
base = self.get_base(revision)
199
if base == revision_id:
200
raise AssertionError()
200
assert base != revision_id
201
201
if not self._validated_revisions_against_repo:
202
202
self._validate_references_from_repository(repository)
203
203
revision_info = self.get_revision_info(revision_id)
204
204
inventory_revision_id = revision_id
205
bundle_tree = BundleTree(repository.revision_tree(base),
205
bundle_tree = BundleTree(repository.revision_tree(base),
206
206
inventory_revision_id)
207
207
self._update_tree(bundle_tree, revision_id)
209
209
inv = bundle_tree.inventory
210
210
self._validate_inventory(inv, revision_id)
211
self._validate_revision(bundle_tree, revision_id)
211
self._validate_revision(inv, revision_id)
213
213
return bundle_tree
241
241
for rev_info in self.revisions:
242
242
checked[rev_info.revision_id] = True
243
243
add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
245
245
for (rev, rev_info) in zip(self.real_revisions, self.revisions):
246
246
add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
250
250
for revision_id, sha1 in rev_to_sha.iteritems():
251
251
if repository.has_revision(revision_id):
252
testament = StrictTestament.from_revision(repository,
252
testament = StrictTestament.from_revision(repository,
254
254
local_sha1 = self._testament_sha1_from_revision(repository,
256
256
if sha1 != local_sha1:
257
raise BzrError('sha1 mismatch. For revision id {%s}'
257
raise BzrError('sha1 mismatch. For revision id {%s}'
258
258
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
261
261
elif revision_id not in checked:
262
262
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))
264
279
if len(missing) > 0:
265
280
# I don't know if this is an error yet
266
281
warning('Not all revision hashes could be validated.'
272
287
"""At this point we should have generated the BundleTree,
273
288
so build up an inventory, and make sure the hashes match.
291
assert inv is not None
275
293
# Now we should have a complete inventory entry.
276
294
s = serializer_v5.write_inventory_to_string(inv)
277
295
sha1 = sha_string(s)
278
296
# Target revision is the last entry in the real_revisions list
279
297
rev = self.get_revision(revision_id)
280
if rev.revision_id != revision_id:
281
raise AssertionError()
298
assert rev.revision_id == revision_id
282
299
if sha1 != rev.inventory_sha1:
283
f = open(',,bogus-inv', 'wb')
300
open(',,bogus-inv', 'wb').write(s)
288
301
warning('Inventory sha hash mismatch for revision %s. %s'
289
302
' != %s' % (revision_id, sha1, rev.inventory_sha1))
291
def _validate_revision(self, tree, revision_id):
304
def _validate_revision(self, inventory, revision_id):
292
305
"""Make sure all revision entries match their checksum."""
294
# This is a mapping from each revision id to its sha hash
307
# This is a mapping from each revision id to it's sha hash
297
310
rev = self.get_revision(revision_id)
298
311
rev_info = self.get_revision_info(revision_id)
299
if not (rev.revision_id == rev_info.revision_id):
300
raise AssertionError()
301
if not (rev.revision_id == revision_id):
302
raise AssertionError()
303
sha1 = self._testament_sha1(rev, tree)
312
assert rev.revision_id == rev_info.revision_id
313
assert rev.revision_id == revision_id
314
sha1 = self._testament_sha1(rev, inventory)
304
315
if sha1 != rev_info.sha1:
305
316
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
306
317
if rev.revision_id in rev_to_sha1:
334
345
name, value = info_item.split(':', 1)
335
346
except ValueError:
336
raise ValueError('Value %r has no colon' % info_item)
347
raise 'Value %r has no colon' % info_item
337
348
if name == 'last-changed':
338
349
last_changed = value
339
350
elif name == 'executable':
351
assert value in ('yes', 'no'), value
340
352
val = (value == 'yes')
341
353
bundle_tree.note_executable(new_path, val)
342
354
elif name == 'target':
346
358
return last_changed, encoding
348
360
def do_patch(path, lines, encoding):
349
if encoding == 'base64':
361
if encoding is not None:
362
assert encoding == 'base64'
350
363
patch = base64.decodestring(''.join(lines))
351
elif encoding is None:
352
365
patch = ''.join(lines)
354
raise ValueError(encoding)
355
366
bundle_tree.note_patch(path, patch)
357
368
def renamed(kind, extra, lines):
446
457
' (unrecognized action): %r' % action_line)
447
458
valid_actions[action](kind, extra, lines)
449
def install_revisions(self, target_repo, stream_input=True):
450
"""Install revisions and return the target revision
452
:param target_repo: The repository to install into
453
:param stream_input: Ignored by this implementation.
460
def install_revisions(self, target_repo):
461
"""Install revisions and return the target revision"""
455
462
apply_bundle.install_bundle(target_repo, self)
456
463
return self.target
487
493
def note_rename(self, old_path, new_path):
488
494
"""A file/directory has been renamed from old_path => new_path"""
489
if new_path in self._renamed:
490
raise AssertionError(new_path)
491
if old_path in self._renamed_r:
492
raise AssertionError(old_path)
495
assert new_path not in self._renamed
496
assert old_path not in self._renamed_r
493
497
self._renamed[new_path] = old_path
494
498
self._renamed_r[old_path] = new_path
526
530
def old_path(self, new_path):
527
531
"""Get the old_path (path in the base_tree) for the file at new_path"""
528
if new_path[:1] in ('\\', '/'):
529
raise ValueError(new_path)
532
assert new_path[:1] not in ('\\', '/')
530
533
old_path = self._renamed.get(new_path)
531
534
if old_path is not None:
547
550
if old_path in self._renamed_r:
551
554
def new_path(self, old_path):
552
555
"""Get the new_path (path in the target_tree) for the file at old_path
553
556
in the base tree.
555
if old_path[:1] in ('\\', '/'):
556
raise ValueError(old_path)
558
assert old_path[:1] not in ('\\', '/')
557
559
new_path = self._renamed_r.get(old_path)
558
560
if new_path is not None:
613
615
new_path = self.id2path(file_id)
614
616
return self.base_tree.path2id(new_path)
616
618
def get_file(self, file_id):
617
619
"""Return a file-like object containing the new contents of the
618
620
file given by file_id.
629
631
patch_original = None
630
632
file_patch = self.patches.get(self.id2path(file_id))
631
633
if file_patch is None:
632
if (patch_original is None and
634
if (patch_original is None and
633
635
self.get_kind(file_id) == 'directory'):
634
636
return StringIO()
635
if patch_original is None:
636
raise AssertionError("None: %s" % file_id)
637
assert patch_original is not None, "None: %s" % file_id
637
638
return patch_original
639
if file_patch.startswith('\\'):
641
'Malformed patch for %s, %r' % (file_id, file_patch))
640
assert not file_patch.startswith('\\'), \
641
'Malformed patch for %s, %r' % (file_id, file_patch)
642
642
return patched_file(file_patch, patch_original)
644
def get_symlink_target(self, file_id, path=None):
646
path = self.id2path(file_id)
644
def get_symlink_target(self, file_id):
645
new_path = self.id2path(file_id)
648
return self._targets[path]
647
return self._targets[new_path]
650
649
return self.base_tree.get_symlink_target(file_id)
665
664
path = self.id2path(file_id)
666
665
if path in self._last_changed:
667
666
return self._last_changed[path]
668
return self.base_tree.get_file_revision(file_id)
667
return self.base_tree.inventory[file_id].revision
670
669
def get_size_and_sha1(self, file_id):
671
670
"""Return the size and sha1 hash of the given file id.
692
691
This need to be called before ever accessing self.inventory
694
693
from os.path import dirname, basename
695
assert self.base_tree is not None
695
696
base_inv = self.base_tree.inventory
696
697
inv = Inventory(None, self.revision_id)
716
717
ie.executable = self.is_executable(file_id)
717
718
elif kind == 'symlink':
718
719
ie = InventoryLink(file_id, name, parent_id)
719
ie.symlink_target = self.get_symlink_target(file_id, path)
720
ie.symlink_target = self.get_symlink_target(file_id)
720
721
ie.revision = revision_id
723
if kind in ('directory', 'symlink'):
724
ie.text_size, ie.text_sha1 = None, None
723
726
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
724
if ie.text_size is None:
726
'Got a text_size of None for file_id %r' % file_id)
727
if (ie.text_size is None) and (kind == 'file'):
728
raise BzrError('Got a text_size of None for file_id %r' % file_id)
729
731
sorted_entries = self.sorted_path_id()
743
745
for path, entry in self.inventory.iter_entries():
744
746
yield entry.file_id
746
def list_files(self, include_root=False, from_dir=None, recursive=True):
747
# The only files returned by this are those from the version
752
from_dir_id = inv.path2id(from_dir)
753
if from_dir_id is None:
754
# Directory not versioned
756
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
757
if inv.root is not None and not include_root and from_dir is None:
758
# skip the root for compatability with the current apis.
760
for path, entry in entries:
761
yield path, 'V', entry.kind, entry.file_id, entry
763
748
def sorted_path_id(self):
765
750
for result in self._new_id.iteritems():
766
751
paths.append(result)
767
for id in self.base_tree.all_file_ids():
752
for id in self.base_tree:
768
753
path = self.id2path(id)