25
from bzrlib.errors import (TestamentMismatch, BzrError,
26
MalformedHeader, MalformedPatches, NotABundle)
27
from bzrlib.inventory import (Inventory, InventoryEntry,
28
InventoryDirectory, InventoryFile,
30
from bzrlib.osutils import sha_file, sha_string, pathjoin
28
from bzrlib.bundle import apply_bundle
29
from bzrlib.errors import (
33
from bzrlib.inventory import (
39
from bzrlib.osutils import sha_string, pathjoin
31
40
from bzrlib.revision import Revision, NULL_REVISION
32
41
from bzrlib.testament import StrictTestament
33
42
from bzrlib.trace import mutter, warning
34
import bzrlib.transport
35
43
from bzrlib.tree import Tree
36
import bzrlib.urlutils
37
44
from bzrlib.xml5 import serializer_v5
72
79
if self.properties:
73
80
for property in self.properties:
74
81
key_end = property.find(': ')
75
assert key_end is not None
76
key = property[:key_end].encode('utf-8')
77
value = property[key_end+2:].encode('utf-8')
83
if not property.endswith(':'):
84
raise ValueError(property)
85
key = str(property[:-1])
88
key = str(property[:key_end])
89
value = property[key_end+2:]
78
90
rev.properties[key] = value
95
def from_revision(revision):
96
revision_info = RevisionInfo(revision.revision_id)
97
date = timestamp.format_highres_date(revision.timestamp,
99
revision_info.date = date
100
revision_info.timezone = revision.timezone
101
revision_info.timestamp = revision.timestamp
102
revision_info.message = revision.message.split('\n')
103
revision_info.properties = [': '.join(p) for p in
104
revision.properties.iteritems()]
83
108
class BundleInfo(object):
84
109
"""This contains the meta information. Stuff that allows you to
85
110
recreate the revision or inventory XML.
112
def __init__(self, bundle_format=None):
113
self.bundle_format = None
88
114
self.committer = None
90
116
self.message = None
132
161
def get_base(self, revision):
133
162
revision_info = self.get_revision_info(revision.revision_id)
134
163
if revision_info.base_id is not None:
135
if revision_info.base_id == NULL_REVISION:
138
return revision_info.base_id
164
return revision_info.base_id
139
165
if len(revision.parent_ids) == 0:
140
166
# There is no base listed, and
141
167
# the lowest revision doesn't have a parent
142
168
# so this is probably against the empty tree
143
# and thus base truly is None
169
# and thus base truly is NULL_REVISION
146
172
return revision.parent_ids[-1]
170
196
def revision_tree(self, repository, revision_id, base=None):
171
197
revision = self.get_revision(revision_id)
172
198
base = self.get_base(revision)
173
assert base != revision_id
174
self._validate_references_from_repository(repository)
199
if base == revision_id:
200
raise AssertionError()
201
if not self._validated_revisions_against_repo:
202
self._validate_references_from_repository(repository)
175
203
revision_info = self.get_revision_info(revision_id)
176
204
inventory_revision_id = revision_id
177
bundle_tree = BundleTree(repository.revision_tree(base),
205
bundle_tree = BundleTree(repository.revision_tree(base),
178
206
inventory_revision_id)
179
207
self._update_tree(bundle_tree, revision_id)
181
209
inv = bundle_tree.inventory
182
210
self._validate_inventory(inv, revision_id)
183
self._validate_revision(inv, revision_id)
211
self._validate_revision(bundle_tree, revision_id)
185
213
return bundle_tree
222
250
for revision_id, sha1 in rev_to_sha.iteritems():
223
251
if repository.has_revision(revision_id):
224
testament = StrictTestament.from_revision(repository,
252
testament = StrictTestament.from_revision(repository,
226
local_sha1 = testament.as_sha1()
254
local_sha1 = self._testament_sha1_from_revision(repository,
227
256
if sha1 != local_sha1:
228
raise BzrError('sha1 mismatch. For revision id {%s}'
257
raise BzrError('sha1 mismatch. For revision id {%s}'
229
258
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
232
261
elif revision_id not in checked:
233
262
missing[revision_id] = sha1
235
for inv_id, sha1 in inv_to_sha.iteritems():
236
if repository.has_revision(inv_id):
237
# Note: branch.get_inventory_sha1() just returns the value that
238
# is stored in the revision text, and that value may be out
239
# of date. This is bogus, because that means we aren't
240
# validating the actual text, just that we wrote and read the
241
# string. But for now, what the hell.
242
local_sha1 = repository.get_inventory_sha1(inv_id)
243
if sha1 != local_sha1:
244
raise BzrError('sha1 mismatch. For inventory id {%s}'
245
'local: %s, bundle: %s' %
246
(inv_id, local_sha1, sha1))
250
264
if len(missing) > 0:
251
265
# I don't know if this is an error yet
252
266
warning('Not all revision hashes could be validated.'
253
267
' Unable validate %d hashes' % len(missing))
254
268
mutter('Verified %d sha hashes for the bundle.' % count)
269
self._validated_revisions_against_repo = True
256
271
def _validate_inventory(self, inv, revision_id):
257
272
"""At this point we should have generated the BundleTree,
258
273
so build up an inventory, and make sure the hashes match.
261
assert inv is not None
263
275
# Now we should have a complete inventory entry.
264
276
s = serializer_v5.write_inventory_to_string(inv)
265
277
sha1 = sha_string(s)
266
278
# Target revision is the last entry in the real_revisions list
267
279
rev = self.get_revision(revision_id)
268
assert rev.revision_id == revision_id
280
if rev.revision_id != revision_id:
281
raise AssertionError()
269
282
if sha1 != rev.inventory_sha1:
270
open(',,bogus-inv', 'wb').write(s)
283
f = open(',,bogus-inv', 'wb')
271
288
warning('Inventory sha hash mismatch for revision %s. %s'
272
289
' != %s' % (revision_id, sha1, rev.inventory_sha1))
274
def _validate_revision(self, inventory, revision_id):
291
def _validate_revision(self, tree, revision_id):
275
292
"""Make sure all revision entries match their checksum."""
277
# 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
280
297
rev = self.get_revision(revision_id)
281
298
rev_info = self.get_revision_info(revision_id)
282
assert rev.revision_id == rev_info.revision_id
283
assert rev.revision_id == revision_id
284
sha1 = StrictTestament(rev, inventory).as_sha1()
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)
285
304
if sha1 != rev_info.sha1:
286
305
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
287
306
if rev.revision_id in rev_to_sha1:
422
446
' (unrecognized action): %r' % action_line)
423
447
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.
455
apply_bundle.install_bundle(target_repo, self)
458
def get_merge_request(self, target_repo):
459
"""Provide data for performing a merge
461
Returns suggested base, suggested target, and patch verification status
463
return None, self.target, 'inapplicable'
426
466
class BundleTree(Tree):
427
468
def __init__(self, base_tree, revision_id):
428
469
self.base_tree = base_tree
429
470
self._renamed = {} # Mapping from old_path => new_path
579
624
base_id = self.old_contents_id(file_id)
580
if base_id is not None:
625
if (base_id is not None and
626
base_id != self.base_tree.inventory.root.file_id):
581
627
patch_original = self.base_tree.get_file(base_id)
583
629
patch_original = None
584
630
file_patch = self.patches.get(self.id2path(file_id))
585
631
if file_patch is None:
586
if (patch_original is None and
632
if (patch_original is None and
587
633
self.get_kind(file_id) == 'directory'):
588
634
return StringIO()
589
assert patch_original is not None, "None: %s" % file_id
635
if patch_original is None:
636
raise AssertionError("None: %s" % file_id)
590
637
return patch_original
592
assert not file_patch.startswith('\\'), \
593
'Malformed patch for %s, %r' % (file_id, file_patch)
639
if file_patch.startswith('\\'):
641
'Malformed patch for %s, %r' % (file_id, file_patch))
594
642
return patched_file(file_patch, patch_original)
596
644
def get_symlink_target(self, file_id):
643
691
This need to be called before ever accessing self.inventory
645
693
from os.path import dirname, basename
647
assert self.base_tree is not None
648
694
base_inv = self.base_tree.inventory
649
root_id = base_inv.root.file_id
651
# New inventories have a unique root_id
652
inv = Inventory(root_id, self.revision_id)
654
inv = Inventory(revision_id=self.revision_id)
655
inv.root.revision = self.get_last_changed(root_id)
695
inv = Inventory(None, self.revision_id)
657
697
def add_entry(file_id):
658
698
path = self.id2path(file_id)
661
parent_path = dirname(path)
662
if parent_path == u'':
704
parent_path = dirname(path)
665
705
parent_id = self.path2id(parent_path)
667
707
kind = self.get_kind(file_id)
678
718
ie.symlink_target = self.get_symlink_target(file_id)
679
719
ie.revision = revision_id
681
if kind in ('directory', 'symlink'):
682
ie.text_size, ie.text_sha1 = None, None
684
722
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
685
if (ie.text_size is None) and (kind == 'file'):
686
raise BzrError('Got a text_size of None for file_id %r' % file_id)
723
if ie.text_size is None:
725
'Got a text_size of None for file_id %r' % file_id)
689
728
sorted_entries = self.sorted_path_id()
690
729
for path, file_id in sorted_entries:
691
if file_id == inv.root.file_id:
693
730
add_entry(file_id)
705
742
for path, entry in self.inventory.iter_entries():
706
743
yield entry.file_id
745
def list_files(self, include_root=False, from_dir=None, recursive=True):
746
# The only files returned by this are those from the version
751
from_dir_id = inv.path2id(from_dir)
752
if from_dir_id is None:
753
# Directory not versioned
755
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
756
if inv.root is not None and not include_root and from_dir is None:
757
# skip the root for compatability with the current apis.
759
for path, entry in entries:
760
yield path, 'V', entry.kind, entry.file_id, entry
708
762
def sorted_path_id(self):
710
764
for result in self._new_id.iteritems():