151
173
:return: The serialized delta as lines.
175
if type(old_name) is not str:
176
raise TypeError('old_name should be str, got %r' % (old_name,))
177
if type(new_name) is not str:
178
raise TypeError('new_name should be str, got %r' % (new_name,))
153
179
lines = ['', '', '', '', '']
154
180
to_line = self._delta_item_to_line
155
181
for delta_item in delta_to_new:
156
lines.append(to_line(delta_item))
157
if lines[-1].__class__ != str:
158
raise errors.BzrError(
182
line = to_line(delta_item, new_name)
183
if line.__class__ != str:
184
raise InventoryDeltaError(
159
185
'to_line generated non-str output %r' % lines[-1])
161
lines[0] = "format: %s\n" % InventoryDeltaSerializer.FORMAT_1
188
lines[0] = "format: %s\n" % FORMAT_1
162
189
lines[1] = "parent: %s\n" % old_name
163
190
lines[2] = "version: %s\n" % new_name
164
191
lines[3] = "versioned_root: %s\n" % self._serialize_bool(
196
227
last_modified = entry.revision
197
228
# special cases for /
198
229
if newpath_utf8 == '/' and not self._versioned_root:
199
if file_id != 'TREE_ROOT':
200
raise errors.BzrError(
201
'file_id %s is not TREE_ROOT for /' % file_id)
202
if last_modified is not None:
203
raise errors.BzrError(
204
'Version present for / in %s' % file_id)
205
last_modified = NULL_REVISION
230
# This is an entry for the root, this inventory does not
231
# support versioned roots. So this must be an unversioned
232
# root, i.e. last_modified == new revision. Otherwise, this
234
# Note: the non-rich-root repositories *can* have roots with
235
# file-ids other than TREE_ROOT, e.g. repo formats that use the
237
if last_modified != new_version:
238
raise InventoryDeltaError(
239
'Version present for / in %s (%s != %s)'
240
% (file_id, last_modified, new_version))
206
241
if last_modified is None:
207
raise errors.BzrError("no version for fileid %s" % file_id)
242
raise InventoryDeltaError("no version for fileid %s" % file_id)
208
243
content = self._entry_to_content[entry.kind](entry)
209
244
return ("%s\x00%s\x00%s\x00%s\x00%s\x00%s\n" %
210
245
(oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
249
class InventoryDeltaDeserializer(object):
250
"""Deserialize inventory deltas."""
252
def __init__(self, allow_versioned_root=True, allow_tree_references=True):
253
"""Create an InventoryDeltaDeserializer.
255
:param versioned_root: If True, any root entry that is seen is expected
256
to be versioned, and root entries can have any fileid.
257
:param tree_references: If True support tree-reference entries.
259
self._allow_versioned_root = allow_versioned_root
260
self._allow_tree_references = allow_tree_references
213
262
def _deserialize_bool(self, value):
214
263
if value == "true":
216
265
elif value == "false":
219
raise errors.BzrError("value %r is not a bool" % (value,))
268
raise InventoryDeltaError("value %r is not a bool" % (value,))
221
270
def parse_text_bytes(self, bytes):
222
271
"""Parse the text bytes of a serialized inventory delta.
273
If versioned_root and/or tree_references flags were set via
274
require_flags, then the parsed flags must match or a BzrError will be
224
277
:param bytes: The bytes to parse. This can be obtained by calling
225
278
delta_to_lines and then doing ''.join(delta_lines).
226
:return: (parent_id, new_id, inventory_delta)
279
:return: (parent_id, new_id, versioned_root, tree_references,
282
if bytes[-1:] != '\n':
283
last_line = bytes.rsplit('\n', 1)[-1]
284
raise InventoryDeltaError('last line not empty: %r' % (last_line,))
228
285
lines = bytes.split('\n')[:-1] # discard the last empty line
229
if not lines or lines[0] != 'format: %s' % InventoryDeltaSerializer.FORMAT_1:
230
raise errors.BzrError('unknown format %r' % lines[0:1])
286
if not lines or lines[0] != 'format: %s' % FORMAT_1:
287
raise InventoryDeltaError('unknown format %r' % lines[0:1])
231
288
if len(lines) < 2 or not lines[1].startswith('parent: '):
232
raise errors.BzrError('missing parent: marker')
289
raise InventoryDeltaError('missing parent: marker')
233
290
delta_parent_id = lines[1][8:]
234
291
if len(lines) < 3 or not lines[2].startswith('version: '):
235
raise errors.BzrError('missing version: marker')
292
raise InventoryDeltaError('missing version: marker')
236
293
delta_version_id = lines[2][9:]
237
294
if len(lines) < 4 or not lines[3].startswith('versioned_root: '):
238
raise errors.BzrError('missing versioned_root: marker')
295
raise InventoryDeltaError('missing versioned_root: marker')
239
296
delta_versioned_root = self._deserialize_bool(lines[3][16:])
240
297
if len(lines) < 5 or not lines[4].startswith('tree_references: '):
241
raise errors.BzrError('missing tree_references: marker')
298
raise InventoryDeltaError('missing tree_references: marker')
242
299
delta_tree_references = self._deserialize_bool(lines[4][17:])
243
if delta_versioned_root != self._versioned_root:
244
raise errors.BzrError(
245
"serialized versioned_root flag is wrong: %s" %
246
(delta_versioned_root,))
247
if delta_tree_references != self._tree_references:
248
raise errors.BzrError(
249
"serialized tree_references flag is wrong: %s" %
250
(delta_tree_references,))
300
if (not self._allow_versioned_root and delta_versioned_root):
301
raise IncompatibleInventoryDelta("versioned_root not allowed")
253
304
line_iter = iter(lines)
258
309
content) = line.split('\x00', 5)
259
310
parent_id = parent_id or None
260
311
if file_id in seen_ids:
261
raise errors.BzrError(
312
raise InventoryDeltaError(
262
313
"duplicate file id in inventory delta %r" % lines)
263
314
seen_ids.add(file_id)
264
if newpath_utf8 == '/' and not delta_versioned_root and (
265
last_modified != 'null:' or file_id != 'TREE_ROOT'):
266
raise errors.BzrError("Versioned root found: %r" % line)
267
elif last_modified[-1] == ':':
268
raise errors.BzrError('special revisionid found: %r' % line)
269
if not delta_tree_references and content.startswith('tree\x00'):
270
raise errors.BzrError("Tree reference found: %r" % line)
271
content_tuple = tuple(content.split('\x00'))
272
entry = _parse_entry(
273
newpath_utf8, file_id, parent_id, last_modified, content_tuple)
315
if (newpath_utf8 == '/' and not delta_versioned_root and
316
last_modified != delta_version_id):
317
# Delta claims to be not have a versioned root, yet here's
318
# a root entry with a non-default version.
319
raise InventoryDeltaError("Versioned root found: %r" % line)
320
elif newpath_utf8 != 'None' and last_modified[-1] == ':':
321
# Deletes have a last_modified of null:, but otherwise special
322
# revision ids should not occur.
323
raise InventoryDeltaError('special revisionid found: %r' % line)
324
if content.startswith('tree\x00'):
325
if delta_tree_references is False:
326
raise InventoryDeltaError(
327
"Tree reference found (but header said "
328
"tree_references: false): %r" % line)
329
elif not self._allow_tree_references:
330
raise IncompatibleInventoryDelta(
331
"Tree reference not allowed")
274
332
if oldpath_utf8 == 'None':
334
elif oldpath_utf8[:1] != '/':
335
raise InventoryDeltaError(
336
"oldpath invalid (does not start with /): %r"
339
oldpath_utf8 = oldpath_utf8[1:]
277
340
oldpath = oldpath_utf8.decode('utf8')
278
341
if newpath_utf8 == 'None':
343
elif newpath_utf8[:1] != '/':
344
raise InventoryDeltaError(
345
"newpath invalid (does not start with /): %r"
349
newpath_utf8 = newpath_utf8[1:]
281
350
newpath = newpath_utf8.decode('utf8')
351
content_tuple = tuple(content.split('\x00'))
352
if content_tuple[0] == 'deleted':
355
entry = _parse_entry(
356
newpath, file_id, parent_id, last_modified, content_tuple)
282
357
delta_item = (oldpath, newpath, file_id, entry)
283
358
result.append(delta_item)
284
return delta_parent_id, delta_version_id, result
287
def _parse_entry(utf8_path, file_id, parent_id, last_modified, content):
359
return (delta_parent_id, delta_version_id, delta_versioned_root,
360
delta_tree_references, result)
363
def _parse_entry(path, file_id, parent_id, last_modified, content):
288
364
entry_factory = {
289
365
'dir': _dir_to_entry,
290
366
'file': _file_to_entry,