168
169
raise KeyError(revision_id)
170
def revision_tree(self, repository, revision_id, base=None):
171
revision = self.get_revision(revision_id)
172
base = self.get_base(revision)
173
assert base != revision_id
174
self._validate_references_from_repository(repository)
175
revision_info = self.get_revision_info(revision_id)
176
inventory_revision_id = revision_id
177
bundle_tree = BundleTree(repository.revision_tree(base),
178
inventory_revision_id)
179
self._update_tree(bundle_tree, revision_id)
181
inv = bundle_tree.inventory
182
self._validate_inventory(inv, revision_id)
183
self._validate_revision(inv, revision_id)
172
class BundleReader(object):
173
"""This class reads in a bundle from a file, and returns
174
a Bundle object, which can then be applied against a tree.
176
def __init__(self, from_file):
177
"""Read in the bundle from the file.
179
:param from_file: A file-like object (must have iterator support).
181
object.__init__(self)
182
self.from_file = iter(from_file)
183
self._next_line = None
185
self.info = BundleInfo()
186
# We put the actual inventory ids in the footer, so that the patch
187
# is easier to read for humans.
188
# Unfortunately, that means we need to read everything before we
189
# can create a proper bundle.
195
while self._next_line is not None:
196
self._read_revision_header()
197
if self._next_line is None:
203
"""Make sure that the information read in makes sense
204
and passes appropriate checksums.
206
# Fill in all the missing blanks for the revisions
207
# and generate the real_revisions list.
208
self.info.complete_info()
210
def _validate_revision(self, inventory, revision_id):
211
"""Make sure all revision entries match their checksum."""
213
# This is a mapping from each revision id to it's sha hash
216
rev = self.info.get_revision(revision_id)
217
rev_info = self.info.get_revision_info(revision_id)
218
assert rev.revision_id == rev_info.revision_id
219
assert rev.revision_id == revision_id
220
sha1 = StrictTestament(rev, inventory).as_sha1()
221
if sha1 != rev_info.sha1:
222
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
223
if rev_to_sha1.has_key(rev.revision_id):
224
raise BzrError('Revision {%s} given twice in the list'
226
rev_to_sha1[rev.revision_id] = sha1
187
228
def _validate_references_from_repository(self, repository):
188
229
"""Now that we have a repository which should have some of the
264
305
s = serializer_v5.write_inventory_to_string(inv)
265
306
sha1 = sha_string(s)
266
307
# Target revision is the last entry in the real_revisions list
267
rev = self.get_revision(revision_id)
308
rev = self.info.get_revision(revision_id)
268
309
assert rev.revision_id == revision_id
269
310
if sha1 != rev.inventory_sha1:
270
311
open(',,bogus-inv', 'wb').write(s)
271
312
warning('Inventory sha hash mismatch for revision %s. %s'
272
313
' != %s' % (revision_id, sha1, rev.inventory_sha1))
274
def _validate_revision(self, inventory, revision_id):
275
"""Make sure all revision entries match their checksum."""
277
# This is a mapping from each revision id to it's sha hash
280
rev = self.get_revision(revision_id)
281
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()
285
if sha1 != rev_info.sha1:
286
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
287
if rev_to_sha1.has_key(rev.revision_id):
288
raise BzrError('Revision {%s} given twice in the list'
290
rev_to_sha1[rev.revision_id] = sha1
315
def get_bundle(self, repository):
316
"""Return the meta information, and a Bundle tree which can
317
be used to populate the local stores and working tree, respectively.
319
return self.info, self.revision_tree(repository, self.info.target)
321
def revision_tree(self, repository, revision_id, base=None):
322
revision = self.info.get_revision(revision_id)
323
base = self.info.get_base(revision)
324
assert base != revision_id
325
self._validate_references_from_repository(repository)
326
revision_info = self.info.get_revision_info(revision_id)
327
inventory_revision_id = revision_id
328
bundle_tree = BundleTree(repository.revision_tree(base),
329
inventory_revision_id)
330
self._update_tree(bundle_tree, revision_id)
332
inv = bundle_tree.inventory
333
self._validate_inventory(inv, revision_id)
334
self._validate_revision(inv, revision_id)
339
"""yield the next line, but secretly
340
keep 1 extra line for peeking.
342
for line in self.from_file:
343
last = self._next_line
344
self._next_line = line
346
#mutter('yielding line: %r' % last)
348
last = self._next_line
349
self._next_line = None
350
#mutter('yielding line: %r' % last)
353
def _read_header(self):
354
"""Read the bzr header"""
355
header = get_header()
357
for line in self._next():
359
# not all mailers will keep trailing whitespace
362
if (not line.startswith('# ') or not line.endswith('\n')
363
or line[2:-1].decode('utf-8') != header[0]):
364
raise MalformedHeader('Found a header, but it'
365
' was improperly formatted')
366
header.pop(0) # We read this line.
368
break # We found everything.
369
elif (line.startswith('#') and line.endswith('\n')):
370
line = line[1:-1].strip().decode('utf-8')
371
if line[:len(header_str)] == header_str:
372
if line == header[0]:
375
raise MalformedHeader('Found what looks like'
376
' a header, but did not match')
379
raise NotABundle('Did not find an opening header')
381
def _read_revision_header(self):
382
self.info.revisions.append(RevisionInfo(None))
383
for line in self._next():
384
# The bzr header is terminated with a blank line
385
# which does not start with '#'
386
if line is None or line == '\n':
388
self._handle_next(line)
390
def _read_next_entry(self, line, indent=1):
391
"""Read in a key-value pair
393
if not line.startswith('#'):
394
raise MalformedHeader('Bzr header did not start with #')
395
line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
396
if line[:indent] == ' '*indent:
399
return None, None# Ignore blank lines
401
loc = line.find(': ')
406
value = self._read_many(indent=indent+2)
407
elif line[-1:] == ':':
409
value = self._read_many(indent=indent+2)
411
raise MalformedHeader('While looking for key: value pairs,'
412
' did not find the colon %r' % (line))
414
key = key.replace(' ', '_')
415
#mutter('found %s: %s' % (key, value))
418
def _handle_next(self, line):
421
key, value = self._read_next_entry(line, indent=1)
422
mutter('_handle_next %r => %r' % (key, value))
426
revision_info = self.info.revisions[-1]
427
if hasattr(revision_info, key):
428
if getattr(revision_info, key) is None:
429
setattr(revision_info, key, value)
431
raise MalformedHeader('Duplicated Key: %s' % key)
433
# What do we do with a key we don't recognize
434
raise MalformedHeader('Unknown Key: "%s"' % key)
436
def _read_many(self, indent):
437
"""If a line ends with no entry, that means that it should be
438
followed with multiple lines of values.
440
This detects the end of the list, because it will be a line that
441
does not start properly indented.
444
start = '#' + (' '*indent)
446
if self._next_line is None or self._next_line[:len(start)] != start:
449
for line in self._next():
450
values.append(line[len(start):-1].decode('utf-8'))
451
if self._next_line is None or self._next_line[:len(start)] != start:
455
def _read_one_patch(self):
456
"""Read in one patch, return the complete patch, along with
459
:return: action, lines, do_continue
461
#mutter('_read_one_patch: %r' % self._next_line)
462
# Peek and see if there are no patches
463
if self._next_line is None or self._next_line.startswith('#'):
464
return None, [], False
468
for line in self._next():
470
if not line.startswith('==='):
471
raise MalformedPatches('The first line of all patches'
472
' should be a bzr meta line "==="'
474
action = line[4:-1].decode('utf-8')
475
elif line.startswith('... '):
476
action += line[len('... '):-1].decode('utf-8')
478
if (self._next_line is not None and
479
self._next_line.startswith('===')):
480
return action, lines, True
481
elif self._next_line is None or self._next_line.startswith('#'):
482
return action, lines, False
486
elif not line.startswith('... '):
489
return action, lines, False
491
def _read_patches(self):
493
revision_actions = []
495
action, lines, do_continue = self._read_one_patch()
496
if action is not None:
497
revision_actions.append((action, lines))
498
assert self.info.revisions[-1].tree_actions is None
499
self.info.revisions[-1].tree_actions = revision_actions
501
def _read_footer(self):
502
"""Read the rest of the meta information.
504
:param first_line: The previous step iterates past what it
505
can handle. That extra line is given here.
507
for line in self._next():
508
self._handle_next(line)
509
if not self._next_line.startswith('#'):
512
if self._next_line is None:
292
515
def _update_tree(self, bundle_tree, revision_id):
293
516
"""This fills out a BundleTree based on the information