28
24
import bzrlib.errors
29
from bzrlib.bundle import apply_bundle
30
25
from bzrlib.errors import (TestamentMismatch, BzrError,
31
26
MalformedHeader, MalformedPatches, NotABundle)
32
27
from bzrlib.inventory import (Inventory, InventoryEntry,
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
136
109
split up, based on the assumptions that can be made
137
110
when information is missing.
139
from bzrlib.timestamp import unpack_highres_date
112
from bzrlib.bundle.serializer import unpack_highres_date
140
113
# Put in all of the guessable information.
141
114
if not self.timestamp and self.date:
142
115
self.timestamp, self.timezone = unpack_highres_date(self.date)
197
170
def revision_tree(self, repository, revision_id, base=None):
198
171
revision = self.get_revision(revision_id)
199
172
base = self.get_base(revision)
200
if base == revision_id:
201
raise AssertionError()
202
if not self._validated_revisions_against_repo:
203
self._validate_references_from_repository(repository)
173
assert base != revision_id
174
self._validate_references_from_repository(repository)
204
175
revision_info = self.get_revision_info(revision_id)
205
176
inventory_revision_id = revision_id
206
177
bundle_tree = BundleTree(repository.revision_tree(base),
262
233
elif revision_id not in checked:
263
234
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))
265
251
if len(missing) > 0:
266
252
# I don't know if this is an error yet
267
253
warning('Not all revision hashes could be validated.'
268
254
' Unable validate %d hashes' % len(missing))
269
255
mutter('Verified %d sha hashes for the bundle.' % count)
270
self._validated_revisions_against_repo = True
272
257
def _validate_inventory(self, inv, revision_id):
273
258
"""At this point we should have generated the BundleTree,
274
259
so build up an inventory, and make sure the hashes match.
262
assert inv is not None
276
264
# Now we should have a complete inventory entry.
277
265
s = serializer_v5.write_inventory_to_string(inv)
278
266
sha1 = sha_string(s)
279
267
# Target revision is the last entry in the real_revisions list
280
268
rev = self.get_revision(revision_id)
281
if rev.revision_id != revision_id:
282
raise AssertionError()
269
assert rev.revision_id == revision_id
283
270
if sha1 != rev.inventory_sha1:
284
271
open(',,bogus-inv', 'wb').write(s)
285
272
warning('Inventory sha hash mismatch for revision %s. %s'
294
281
rev = self.get_revision(revision_id)
295
282
rev_info = self.get_revision_info(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()
283
assert rev.revision_id == rev_info.revision_id
284
assert rev.revision_id == revision_id
300
285
sha1 = self._testament_sha1(rev, inventory)
301
286
if sha1 != rev_info.sha1:
302
287
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
315
300
def get_rev_id(last_changed, path, kind):
316
301
if last_changed is not None:
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,
302
changed_revision_id = last_changed.decode('utf-8')
322
304
changed_revision_id = revision_id
323
305
bundle_tree.note_last_changed(path, changed_revision_id)
334
316
if name == 'last-changed':
335
317
last_changed = value
336
318
elif name == 'executable':
319
assert value in ('yes', 'no'), value
337
320
val = (value == 'yes')
338
321
bundle_tree.note_executable(new_path, val)
339
322
elif name == 'target':
343
326
return last_changed, encoding
345
328
def do_patch(path, lines, encoding):
346
if encoding == 'base64':
329
if encoding is not None:
330
assert encoding == 'base64'
347
331
patch = base64.decodestring(''.join(lines))
348
elif encoding is None:
349
333
patch = ''.join(lines)
351
raise ValueError(encoding)
352
334
bundle_tree.note_patch(path, patch)
354
336
def renamed(kind, extra, lines):
390
372
if not info[1].startswith('file-id:'):
391
373
raise BzrError('The file-id should follow the path for an add'
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)
375
file_id = info[1][8:]
397
377
bundle_tree.note_id(file_id, path, kind)
398
378
# this will be overridden in extra_info if executable is specified.
443
423
' (unrecognized action): %r' % action_line)
444
424
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'
463
427
class BundleTree(Tree):
464
428
def __init__(self, base_tree, revision_id):
483
447
def note_rename(self, old_path, new_path):
484
448
"""A file/directory has been renamed from old_path => new_path"""
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)
449
assert new_path not in self._renamed
450
assert old_path not in self._renamed_r
489
451
self._renamed[new_path] = old_path
490
452
self._renamed_r[old_path] = new_path
522
484
def old_path(self, new_path):
523
485
"""Get the old_path (path in the base_tree) for the file at new_path"""
524
if new_path[:1] in ('\\', '/'):
525
raise ValueError(new_path)
486
assert new_path[:1] not in ('\\', '/')
526
487
old_path = self._renamed.get(new_path)
527
488
if old_path is not None:
548
509
"""Get the new_path (path in the target_tree) for the file at old_path
549
510
in the base tree.
551
if old_path[:1] in ('\\', '/'):
552
raise ValueError(old_path)
512
assert old_path[:1] not in ('\\', '/')
553
513
new_path = self._renamed_r.get(old_path)
554
514
if new_path is not None:
628
588
if (patch_original is None and
629
589
self.get_kind(file_id) == 'directory'):
630
590
return StringIO()
631
if patch_original is None:
632
raise AssertionError("None: %s" % file_id)
591
assert patch_original is not None, "None: %s" % file_id
633
592
return patch_original
635
if file_patch.startswith('\\'):
637
'Malformed patch for %s, %r' % (file_id, file_patch))
594
assert not file_patch.startswith('\\'), \
595
'Malformed patch for %s, %r' % (file_id, file_patch)
638
596
return patched_file(file_patch, patch_original)
640
598
def get_symlink_target(self, file_id):
687
645
This need to be called before ever accessing self.inventory
689
647
from os.path import dirname, basename
649
assert self.base_tree is not None
690
650
base_inv = self.base_tree.inventory
691
651
inv = Inventory(None, self.revision_id)