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