77
72
if self.properties:
78
73
for property in self.properties:
79
74
key_end = property.find(': ')
81
if not property.endswith(':'):
82
raise ValueError(property)
83
key = str(property[:-1])
86
key = str(property[:key_end])
87
value = property[key_end+2:]
75
assert key_end is not None
76
key = property[:key_end].encode('utf-8')
77
value = property[key_end+2:].encode('utf-8')
88
78
rev.properties[key] = value
93
def from_revision(revision):
94
revision_info = RevisionInfo(revision.revision_id)
95
date = timestamp.format_highres_date(revision.timestamp,
97
revision_info.date = date
98
revision_info.timezone = revision.timezone
99
revision_info.timestamp = revision.timestamp
100
revision_info.message = revision.message.split('\n')
101
revision_info.properties = [': '.join(p) for p in
102
revision.properties.iteritems()]
106
83
class BundleInfo(object):
107
84
"""This contains the meta information. Stuff that allows you to
108
85
recreate the revision or inventory XML.
110
def __init__(self, bundle_format=None):
111
self.bundle_format = None
112
88
self.committer = None
114
90
self.message = None
159
132
def get_base(self, revision):
160
133
revision_info = self.get_revision_info(revision.revision_id)
161
134
if revision_info.base_id is not None:
162
return revision_info.base_id
135
if revision_info.base_id == NULL_REVISION:
138
return revision_info.base_id
163
139
if len(revision.parent_ids) == 0:
164
140
# There is no base listed, and
165
141
# the lowest revision doesn't have a parent
166
142
# so this is probably against the empty tree
167
# and thus base truly is NULL_REVISION
143
# and thus base truly is None
170
146
return revision.parent_ids[-1]
194
170
def revision_tree(self, repository, revision_id, base=None):
195
171
revision = self.get_revision(revision_id)
196
172
base = self.get_base(revision)
197
if base == revision_id:
198
raise AssertionError()
199
if not self._validated_revisions_against_repo:
200
self._validate_references_from_repository(repository)
173
assert base != revision_id
174
self._validate_references_from_repository(repository)
201
175
revision_info = self.get_revision_info(revision_id)
202
176
inventory_revision_id = revision_id
203
bundle_tree = BundleTree(repository.revision_tree(base),
177
bundle_tree = BundleTree(repository.revision_tree(base),
204
178
inventory_revision_id)
205
179
self._update_tree(bundle_tree, revision_id)
248
222
for revision_id, sha1 in rev_to_sha.iteritems():
249
223
if repository.has_revision(revision_id):
250
testament = StrictTestament.from_revision(repository,
224
testament = StrictTestament.from_revision(repository,
252
local_sha1 = self._testament_sha1_from_revision(repository,
226
local_sha1 = testament.as_sha1()
254
227
if sha1 != local_sha1:
255
raise BzrError('sha1 mismatch. For revision id {%s}'
228
raise BzrError('sha1 mismatch. For revision id {%s}'
256
229
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
259
232
elif revision_id not in checked:
260
233
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))
262
250
if len(missing) > 0:
263
251
# I don't know if this is an error yet
264
252
warning('Not all revision hashes could be validated.'
265
253
' Unable validate %d hashes' % len(missing))
266
254
mutter('Verified %d sha hashes for the bundle.' % count)
267
self._validated_revisions_against_repo = True
269
256
def _validate_inventory(self, inv, revision_id):
270
257
"""At this point we should have generated the BundleTree,
271
258
so build up an inventory, and make sure the hashes match.
261
assert inv is not None
273
263
# Now we should have a complete inventory entry.
274
264
s = serializer_v5.write_inventory_to_string(inv)
275
265
sha1 = sha_string(s)
276
266
# Target revision is the last entry in the real_revisions list
277
267
rev = self.get_revision(revision_id)
278
if rev.revision_id != revision_id:
279
raise AssertionError()
268
assert rev.revision_id == revision_id
280
269
if sha1 != rev.inventory_sha1:
281
f = open(',,bogus-inv', 'wb')
270
open(',,bogus-inv', 'wb').write(s)
286
271
warning('Inventory sha hash mismatch for revision %s. %s'
287
272
' != %s' % (revision_id, sha1, rev.inventory_sha1))
289
274
def _validate_revision(self, inventory, revision_id):
290
275
"""Make sure all revision entries match their checksum."""
292
# This is a mapping from each revision id to its sha hash
277
# This is a mapping from each revision id to it's sha hash
295
280
rev = self.get_revision(revision_id)
296
281
rev_info = self.get_revision_info(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()
301
sha1 = self._testament_sha1(rev, inventory)
282
assert rev.revision_id == rev_info.revision_id
283
assert rev.revision_id == revision_id
284
sha1 = StrictTestament(rev, inventory).as_sha1()
302
285
if sha1 != rev_info.sha1:
303
286
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
304
if rev.revision_id in rev_to_sha1:
287
if rev_to_sha1.has_key(rev.revision_id):
305
288
raise BzrError('Revision {%s} given twice in the list'
306
289
% (rev.revision_id))
307
290
rev_to_sha1[rev.revision_id] = sha1
444
422
' (unrecognized action): %r' % action_line)
445
423
valid_actions[action](kind, extra, lines)
447
def install_revisions(self, target_repo, stream_input=True):
448
"""Install revisions and return the target revision
450
:param target_repo: The repository to install into
451
:param stream_input: Ignored by this implementation.
453
apply_bundle.install_bundle(target_repo, self)
456
def get_merge_request(self, target_repo):
457
"""Provide data for performing a merge
459
Returns suggested base, suggested target, and patch verification status
461
return None, self.target, 'inapplicable'
464
426
class BundleTree(Tree):
465
427
def __init__(self, base_tree, revision_id):
541
500
old_path = new_path
542
501
#If the new path wasn't in renamed, the old one shouldn't be in
544
if old_path in self._renamed_r:
503
if self._renamed_r.has_key(old_path):
548
507
def new_path(self, old_path):
549
508
"""Get the new_path (path in the target_tree) for the file at old_path
550
509
in the base tree.
552
if old_path[:1] in ('\\', '/'):
553
raise ValueError(old_path)
511
assert old_path[:1] not in ('\\', '/')
554
512
new_path = self._renamed_r.get(old_path)
555
513
if new_path is not None:
557
if new_path in self._renamed:
515
if self._renamed.has_key(new_path):
559
517
dirname,basename = os.path.split(old_path)
560
518
if dirname != '':
621
579
base_id = self.old_contents_id(file_id)
622
if (base_id is not None and
623
base_id != self.base_tree.inventory.root.file_id):
580
if base_id is not None:
624
581
patch_original = self.base_tree.get_file(base_id)
626
583
patch_original = None
627
584
file_patch = self.patches.get(self.id2path(file_id))
628
585
if file_patch is None:
629
if (patch_original is None and
586
if (patch_original is None and
630
587
self.get_kind(file_id) == 'directory'):
631
588
return StringIO()
632
if patch_original is None:
633
raise AssertionError("None: %s" % file_id)
589
assert patch_original is not None, "None: %s" % file_id
634
590
return patch_original
636
if file_patch.startswith('\\'):
638
'Malformed patch for %s, %r' % (file_id, file_patch))
592
assert not file_patch.startswith('\\'), \
593
'Malformed patch for %s, %r' % (file_id, file_patch)
639
594
return patched_file(file_patch, patch_original)
641
596
def get_symlink_target(self, file_id):
688
643
This need to be called before ever accessing self.inventory
690
645
from os.path import dirname, basename
647
assert self.base_tree is not None
691
648
base_inv = self.base_tree.inventory
692
inv = Inventory(None, self.revision_id)
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)
694
656
def add_entry(file_id):
695
657
path = self.id2path(file_id)
660
parent_path = dirname(path)
661
if parent_path == u'':
701
parent_path = dirname(path)
702
664
parent_id = self.path2id(parent_path)
704
666
kind = self.get_kind(file_id)
715
677
ie.symlink_target = self.get_symlink_target(file_id)
716
678
ie.revision = revision_id
680
if kind in ('directory', 'symlink'):
681
ie.text_size, ie.text_sha1 = None, None
719
683
ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
720
if ie.text_size is None:
722
'Got a text_size of None for file_id %r' % file_id)
684
if (ie.text_size is None) and (kind == 'file'):
685
raise BzrError('Got a text_size of None for file_id %r' % file_id)
725
688
sorted_entries = self.sorted_path_id()
726
689
for path, file_id in sorted_entries:
690
if file_id == inv.root.file_id:
727
692
add_entry(file_id)