24
28
import bzrlib.errors
29
from bzrlib.bundle import apply_bundle
25
30
from bzrlib.errors import (TestamentMismatch, BzrError,
26
31
MalformedHeader, MalformedPatches, NotABundle)
27
32
from bzrlib.inventory import (Inventory, InventoryEntry,
72
77
if self.properties:
73
78
for property in self.properties:
74
79
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')
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:]
78
88
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()]
83
106
class BundleInfo(object):
84
107
"""This contains the meta information. Stuff that allows you to
85
108
recreate the revision or inventory XML.
110
def __init__(self, bundle_format=None):
111
self.bundle_format = None
88
112
self.committer = None
90
114
self.message = None
109
136
split up, based on the assumptions that can be made
110
137
when information is missing.
112
from bzrlib.bundle.serializer import unpack_highres_date
139
from bzrlib.timestamp import unpack_highres_date
113
140
# Put in all of the guessable information.
114
141
if not self.timestamp and self.date:
115
142
self.timestamp, self.timezone = unpack_highres_date(self.date)
170
197
def revision_tree(self, repository, revision_id, base=None):
171
198
revision = self.get_revision(revision_id)
172
199
base = self.get_base(revision)
173
assert base != revision_id
174
self._validate_references_from_repository(repository)
200
if base == revision_id:
201
raise AssertionError()
202
if not self._validated_revisions_against_repo:
203
self._validate_references_from_repository(repository)
175
204
revision_info = self.get_revision_info(revision_id)
176
205
inventory_revision_id = revision_id
177
206
bundle_tree = BundleTree(repository.revision_tree(base),
233
262
elif revision_id not in checked:
234
263
missing[revision_id] = sha1
236
for inv_id, sha1 in inv_to_sha.iteritems():
237
if repository.has_revision(inv_id):
238
# Note: branch.get_inventory_sha1() just returns the value that
239
# is stored in the revision text, and that value may be out
240
# of date. This is bogus, because that means we aren't
241
# validating the actual text, just that we wrote and read the
242
# string. But for now, what the hell.
243
local_sha1 = repository.get_inventory_sha1(inv_id)
244
if sha1 != local_sha1:
245
raise BzrError('sha1 mismatch. For inventory id {%s}'
246
'local: %s, bundle: %s' %
247
(inv_id, local_sha1, sha1))
251
265
if len(missing) > 0:
252
266
# I don't know if this is an error yet
253
267
warning('Not all revision hashes could be validated.'
254
268
' Unable validate %d hashes' % len(missing))
255
269
mutter('Verified %d sha hashes for the bundle.' % count)
270
self._validated_revisions_against_repo = True
257
272
def _validate_inventory(self, inv, revision_id):
258
273
"""At this point we should have generated the BundleTree,
259
274
so build up an inventory, and make sure the hashes match.
262
assert inv is not None
264
276
# Now we should have a complete inventory entry.
265
277
s = serializer_v5.write_inventory_to_string(inv)
266
278
sha1 = sha_string(s)
267
279
# Target revision is the last entry in the real_revisions list
268
280
rev = self.get_revision(revision_id)
269
assert rev.revision_id == revision_id
281
if rev.revision_id != revision_id:
282
raise AssertionError()
270
283
if sha1 != rev.inventory_sha1:
271
284
open(',,bogus-inv', 'wb').write(s)
272
285
warning('Inventory sha hash mismatch for revision %s. %s'
281
294
rev = self.get_revision(revision_id)
282
295
rev_info = self.get_revision_info(revision_id)
283
assert rev.revision_id == rev_info.revision_id
284
assert rev.revision_id == revision_id
296
if not (rev.revision_id == rev_info.revision_id):
297
raise AssertionError()
298
if not (rev.revision_id == revision_id):
299
raise AssertionError()
285
300
sha1 = self._testament_sha1(rev, inventory)
286
301
if sha1 != rev_info.sha1:
287
302
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
300
315
def get_rev_id(last_changed, path, kind):
301
316
if last_changed is not None:
302
changed_revision_id = last_changed.decode('utf-8')
317
# last_changed will be a Unicode string because of how it was
318
# read. Convert it back to utf8.
319
changed_revision_id = osutils.safe_revision_id(last_changed,
304
322
changed_revision_id = revision_id
305
323
bundle_tree.note_last_changed(path, changed_revision_id)
316
334
if name == 'last-changed':
317
335
last_changed = value
318
336
elif name == 'executable':
319
assert value in ('yes', 'no'), value
320
337
val = (value == 'yes')
321
338
bundle_tree.note_executable(new_path, val)
322
339
elif name == 'target':
326
343
return last_changed, encoding
328
345
def do_patch(path, lines, encoding):
329
if encoding is not None:
330
assert encoding == 'base64'
346
if encoding == 'base64':
331
347
patch = base64.decodestring(''.join(lines))
348
elif encoding is None:
333
349
patch = ''.join(lines)
351
raise ValueError(encoding)
334
352
bundle_tree.note_patch(path, patch)
336
354
def renamed(kind, extra, lines):
372
390
if not info[1].startswith('file-id:'):
373
391
raise BzrError('The file-id should follow the path for an add'
375
file_id = info[1][8:]
393
# This will be Unicode because of how the stream is read. Turn it
394
# back into a utf8 file_id
395
file_id = osutils.safe_file_id(info[1][8:], warn=False)
377
397
bundle_tree.note_id(file_id, path, kind)
378
398
# this will be overridden in extra_info if executable is specified.
423
443
' (unrecognized action): %r' % action_line)
424
444
valid_actions[action](kind, extra, lines)
446
def install_revisions(self, target_repo, stream_input=True):
447
"""Install revisions and return the target revision
449
:param target_repo: The repository to install into
450
:param stream_input: Ignored by this implementation.
452
apply_bundle.install_bundle(target_repo, self)
455
def get_merge_request(self, target_repo):
456
"""Provide data for performing a merge
458
Returns suggested base, suggested target, and patch verification status
460
return None, self.target, 'inapplicable'
427
463
class BundleTree(Tree):
428
464
def __init__(self, base_tree, revision_id):
447
483
def note_rename(self, old_path, new_path):
448
484
"""A file/directory has been renamed from old_path => new_path"""
449
assert new_path not in self._renamed
450
assert old_path not in self._renamed_r
485
if new_path in self._renamed:
486
raise AssertionError(new_path)
487
if old_path in self._renamed_r:
488
raise AssertionError(old_path)
451
489
self._renamed[new_path] = old_path
452
490
self._renamed_r[old_path] = new_path
484
522
def old_path(self, new_path):
485
523
"""Get the old_path (path in the base_tree) for the file at new_path"""
486
assert new_path[:1] not in ('\\', '/')
524
if new_path[:1] in ('\\', '/'):
525
raise ValueError(new_path)
487
526
old_path = self._renamed.get(new_path)
488
527
if old_path is not None:
509
548
"""Get the new_path (path in the target_tree) for the file at old_path
510
549
in the base tree.
512
assert old_path[:1] not in ('\\', '/')
551
if old_path[:1] in ('\\', '/'):
552
raise ValueError(old_path)
513
553
new_path = self._renamed_r.get(old_path)
514
554
if new_path is not None:
588
628
if (patch_original is None and
589
629
self.get_kind(file_id) == 'directory'):
590
630
return StringIO()
591
assert patch_original is not None, "None: %s" % file_id
631
if patch_original is None:
632
raise AssertionError("None: %s" % file_id)
592
633
return patch_original
594
assert not file_patch.startswith('\\'), \
595
'Malformed patch for %s, %r' % (file_id, file_patch)
635
if file_patch.startswith('\\'):
637
'Malformed patch for %s, %r' % (file_id, file_patch))
596
638
return patched_file(file_patch, patch_original)
598
640
def get_symlink_target(self, file_id):
645
687
This need to be called before ever accessing self.inventory
647
689
from os.path import dirname, basename
649
assert self.base_tree is not None
650
690
base_inv = self.base_tree.inventory
651
691
inv = Inventory(None, self.revision_id)