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."""
29
28
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
29
from bzrlib.errors import (
33
from bzrlib.inventory import (
39
from bzrlib.osutils import sha_string, pathjoin
36
40
from bzrlib.revision import Revision, NULL_REVISION
37
41
from bzrlib.testament import StrictTestament
38
42
from bzrlib.trace import mutter, warning
39
import bzrlib.transport
40
43
from bzrlib.tree import Tree
41
import bzrlib.urlutils
42
44
from bzrlib.xml5 import serializer_v5
78
80
for property in self.properties:
79
81
key_end = property.find(': ')
81
assert property.endswith(':')
83
if not property.endswith(':'):
84
raise ValueError(property)
82
85
key = str(property[:-1])
158
161
def get_base(self, revision):
159
162
revision_info = self.get_revision_info(revision.revision_id)
160
163
if revision_info.base_id is not None:
161
if revision_info.base_id == NULL_REVISION:
164
return revision_info.base_id
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 None
169
# and thus base truly is NULL_REVISION
172
172
return revision.parent_ids[-1]
196
196
def revision_tree(self, repository, revision_id, base=None):
197
197
revision = self.get_revision(revision_id)
198
198
base = self.get_base(revision)
199
assert base != revision_id
199
if base == revision_id:
200
raise AssertionError()
200
201
if not self._validated_revisions_against_repo:
201
202
self._validate_references_from_repository(repository)
202
203
revision_info = self.get_revision_info(revision_id)
203
204
inventory_revision_id = revision_id
204
bundle_tree = BundleTree(repository.revision_tree(base),
205
bundle_tree = BundleTree(repository.revision_tree(base),
205
206
inventory_revision_id)
206
207
self._update_tree(bundle_tree, revision_id)
208
209
inv = bundle_tree.inventory
209
210
self._validate_inventory(inv, revision_id)
210
self._validate_revision(inv, revision_id)
211
self._validate_revision(bundle_tree, revision_id)
212
213
return bundle_tree
240
241
for rev_info in self.revisions:
241
242
checked[rev_info.revision_id] = True
242
243
add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
244
245
for (rev, rev_info) in zip(self.real_revisions, self.revisions):
245
246
add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
249
250
for revision_id, sha1 in rev_to_sha.iteritems():
250
251
if repository.has_revision(revision_id):
251
testament = StrictTestament.from_revision(repository,
252
testament = StrictTestament.from_revision(repository,
253
254
local_sha1 = self._testament_sha1_from_revision(repository,
255
256
if sha1 != local_sha1:
256
raise BzrError('sha1 mismatch. For revision id {%s}'
257
raise BzrError('sha1 mismatch. For revision id {%s}'
257
258
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
260
261
elif revision_id not in checked:
261
262
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
264
if len(missing) > 0:
279
265
# I don't know if this is an error yet
280
266
warning('Not all revision hashes could be validated.'
286
272
"""At this point we should have generated the BundleTree,
287
273
so build up an inventory, and make sure the hashes match.
290
assert inv is not None
292
275
# Now we should have a complete inventory entry.
293
276
s = serializer_v5.write_inventory_to_string(inv)
294
277
sha1 = sha_string(s)
295
278
# Target revision is the last entry in the real_revisions list
296
279
rev = self.get_revision(revision_id)
297
assert rev.revision_id == revision_id
280
if rev.revision_id != revision_id:
281
raise AssertionError()
298
282
if sha1 != rev.inventory_sha1:
299
open(',,bogus-inv', 'wb').write(s)
283
f = open(',,bogus-inv', 'wb')
300
288
warning('Inventory sha hash mismatch for revision %s. %s'
301
289
' != %s' % (revision_id, sha1, rev.inventory_sha1))
303
def _validate_revision(self, inventory, revision_id):
291
def _validate_revision(self, tree, revision_id):
304
292
"""Make sure all revision entries match their checksum."""
306
# This is a mapping from each revision id to it's sha hash
294
# This is a mapping from each revision id to its sha hash
309
297
rev = self.get_revision(revision_id)
310
298
rev_info = self.get_revision_info(revision_id)
311
assert rev.revision_id == rev_info.revision_id
312
assert rev.revision_id == revision_id
313
sha1 = self._testament_sha1(rev, inventory)
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)
314
304
if sha1 != rev_info.sha1:
315
305
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
316
306
if rev.revision_id in rev_to_sha1:
344
334
name, value = info_item.split(':', 1)
345
335
except ValueError:
346
raise 'Value %r has no colon' % info_item
336
raise ValueError('Value %r has no colon' % info_item)
347
337
if name == 'last-changed':
348
338
last_changed = value
349
339
elif name == 'executable':
350
assert value in ('yes', 'no'), value
351
340
val = (value == 'yes')
352
341
bundle_tree.note_executable(new_path, val)
353
342
elif name == 'target':
357
346
return last_changed, encoding
359
348
def do_patch(path, lines, encoding):
360
if encoding is not None:
361
assert encoding == 'base64'
349
if encoding == 'base64':
362
350
patch = base64.decodestring(''.join(lines))
351
elif encoding is None:
364
352
patch = ''.join(lines)
354
raise ValueError(encoding)
365
355
bundle_tree.note_patch(path, patch)
367
357
def renamed(kind, extra, lines):
496
487
def note_rename(self, old_path, new_path):
497
488
"""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
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)
500
493
self._renamed[new_path] = old_path
501
494
self._renamed_r[old_path] = new_path
533
526
def old_path(self, new_path):
534
527
"""Get the old_path (path in the base_tree) for the file at new_path"""
535
assert new_path[:1] not in ('\\', '/')
528
if new_path[:1] in ('\\', '/'):
529
raise ValueError(new_path)
536
530
old_path = self._renamed.get(new_path)
537
531
if old_path is not None:
553
547
if old_path in self._renamed_r:
557
551
def new_path(self, old_path):
558
552
"""Get the new_path (path in the target_tree) for the file at old_path
559
553
in the base tree.
561
assert old_path[:1] not in ('\\', '/')
555
if old_path[:1] in ('\\', '/'):
556
raise ValueError(old_path)
562
557
new_path = self._renamed_r.get(old_path)
563
558
if new_path is not None:
618
613
new_path = self.id2path(file_id)
619
614
return self.base_tree.path2id(new_path)
621
616
def get_file(self, file_id):
622
617
"""Return a file-like object containing the new contents of the
623
618
file given by file_id.
634
629
patch_original = None
635
630
file_patch = self.patches.get(self.id2path(file_id))
636
631
if file_patch is None:
637
if (patch_original is None and
632
if (patch_original is None and
638
633
self.get_kind(file_id) == 'directory'):
639
634
return StringIO()
640
assert patch_original is not None, "None: %s" % file_id
635
if patch_original is None:
636
raise AssertionError("None: %s" % file_id)
641
637
return patch_original
643
assert not file_patch.startswith('\\'), \
644
'Malformed patch for %s, %r' % (file_id, file_patch)
639
if file_patch.startswith('\\'):
641
'Malformed patch for %s, %r' % (file_id, file_patch))
645
642
return patched_file(file_patch, patch_original)
647
def get_symlink_target(self, file_id):
648
new_path = self.id2path(file_id)
644
def get_symlink_target(self, file_id, path=None):
646
path = self.id2path(file_id)
650
return self._targets[new_path]
648
return self._targets[path]
652
650
return self.base_tree.get_symlink_target(file_id)
667
665
path = self.id2path(file_id)
668
666
if path in self._last_changed:
669
667
return self._last_changed[path]
670
return self.base_tree.inventory[file_id].revision
668
return self.base_tree.get_file_revision(file_id)
672
670
def get_size_and_sha1(self, file_id):
673
671
"""Return the size and sha1 hash of the given file id.
694
692
This need to be called before ever accessing self.inventory
696
694
from os.path import dirname, basename
698
assert self.base_tree is not None
699
695
base_inv = self.base_tree.inventory
700
696
inv = Inventory(None, self.revision_id)
720
716
ie.executable = self.is_executable(file_id)
721
717
elif kind == 'symlink':
722
718
ie = InventoryLink(file_id, name, parent_id)
723
ie.symlink_target = self.get_symlink_target(file_id)
719
ie.symlink_target = self.get_symlink_target(file_id, path)
724
720
ie.revision = revision_id
726
if kind in ('directory', 'symlink'):
727
ie.text_size, ie.text_sha1 = None, None
729
723
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)
724
if ie.text_size is None:
726
'Got a text_size of None for file_id %r' % file_id)
734
729
sorted_entries = self.sorted_path_id()
748
743
for path, entry in self.inventory.iter_entries():
749
744
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
751
763
def sorted_path_id(self):
753
765
for result in self._new_id.iteritems():
754
766
paths.append(result)
755
for id in self.base_tree:
767
for id in self.base_tree.all_file_ids():
756
768
path = self.id2path(id)