22
22
- InventoryDeltaSerializer - object to read/write inventory deltas.
25
from __future__ import absolute_import
25
27
__all__ = ['InventoryDeltaSerializer']
27
from bzrlib import errors
28
from bzrlib.osutils import basename
29
from bzrlib import inventory
30
from bzrlib.revision import NULL_REVISION
30
from .osutils import basename
31
from . import inventory
32
from .revision import NULL_REVISION
32
34
FORMAT_1 = 'bzr inventory delta v1 (bzr 1.14)'
35
37
class InventoryDeltaError(errors.BzrError):
36
38
"""An error when serializing or deserializing an inventory delta."""
38
40
# Most errors when serializing and deserializing are due to bugs, although
39
41
# damaged input (i.e. a bug in a different process) could cause
40
42
# deserialization errors too.
41
43
internal_error = True
45
def __init__(self, format_string, **kwargs):
46
# Let each error supply a custom format string and arguments.
47
self._fmt = format_string
48
super(InventoryDeltaError, self).__init__(**kwargs)
44
51
class IncompatibleInventoryDelta(errors.BzrError):
45
52
"""The delta could not be deserialised because its contents conflict with
69
76
size_exec_sha = (entry.text_size, exec_bytes, entry.text_sha1)
70
77
if None in size_exec_sha:
71
raise InventoryDeltaError('Missing size or sha for %s' % entry.file_id)
78
raise InventoryDeltaError(
79
'Missing size or sha for %(fileid)r', fileid=entry.file_id)
72
80
return "file\x00%d\x00%s\x00%s" % size_exec_sha
80
88
target = entry.symlink_target
82
raise InventoryDeltaError('Missing target for %s' % entry.file_id)
90
raise InventoryDeltaError(
91
'Missing target for %(fileid)r', fileid=entry.file_id)
83
92
return "link\x00%s" % target.encode('utf8')
91
100
tree_revision = entry.reference_revision
92
101
if tree_revision is None:
93
102
raise InventoryDeltaError(
94
'Missing reference revision for %s' % entry.file_id)
103
'Missing reference revision for %(fileid)r', fileid=entry.file_id)
95
104
return "tree\x00%s" % tree_revision
171
180
:return: The serialized delta as lines.
173
if type(old_name) is not str:
182
if not isinstance(old_name, str):
174
183
raise TypeError('old_name should be str, got %r' % (old_name,))
175
if type(new_name) is not str:
184
if not isinstance(new_name, str):
176
185
raise TypeError('new_name should be str, got %r' % (new_name,))
177
186
lines = ['', '', '', '', '']
178
187
to_line = self._delta_item_to_line
179
188
for delta_item in delta_to_new:
180
189
line = to_line(delta_item, new_name)
181
if line.__class__ != str:
190
# GZ 2017-06-09: Not really worth asserting this here
191
if line.__class__ != bytes:
182
192
raise InventoryDeltaError(
183
'to_line generated non-str output %r' % lines[-1])
193
'to_line gave non-bytes output %(line)r', line=lines[-1])
184
194
lines.append(line)
186
196
lines[0] = "format: %s\n" % FORMAT_1
234
244
# xml5 serializer.
235
245
if last_modified != new_version:
236
246
raise InventoryDeltaError(
237
'Version present for / in %s (%s != %s)'
238
% (file_id, last_modified, new_version))
247
'Version present for / in %(fileid)r'
248
' (%(last)r != %(new)r)',
249
fileid=file_id, last=last_modified, new=new_version)
239
250
if last_modified is None:
240
raise InventoryDeltaError("no version for fileid %s" % file_id)
251
raise InventoryDeltaError(
252
"no version for fileid %(fileid)r", fileid=file_id)
241
253
content = self._entry_to_content[entry.kind](entry)
242
254
return ("%s\x00%s\x00%s\x00%s\x00%s\x00%s\n" %
243
255
(oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
263
275
elif value == "false":
266
raise InventoryDeltaError("value %r is not a bool" % (value,))
278
raise InventoryDeltaError("value %(val)r is not a bool", val=value)
268
280
def parse_text_bytes(self, bytes):
269
281
"""Parse the text bytes of a serialized inventory delta.
280
292
if bytes[-1:] != '\n':
281
293
last_line = bytes.rsplit('\n', 1)[-1]
282
raise InventoryDeltaError('last line not empty: %r' % (last_line,))
294
raise InventoryDeltaError(
295
'last line not empty: %(line)r', line=last_line)
283
296
lines = bytes.split('\n')[:-1] # discard the last empty line
284
297
if not lines or lines[0] != 'format: %s' % FORMAT_1:
285
raise InventoryDeltaError('unknown format %r' % lines[0:1])
298
raise InventoryDeltaError(
299
'unknown format %(line)r', line=lines[0:1])
286
300
if len(lines) < 2 or not lines[1].startswith('parent: '):
287
301
raise InventoryDeltaError('missing parent: marker')
288
302
delta_parent_id = lines[1][8:]
302
316
line_iter = iter(lines)
303
317
for i in range(5):
305
319
for line in line_iter:
306
320
(oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
307
321
content) = line.split('\x00', 5)
308
322
parent_id = parent_id or None
309
323
if file_id in seen_ids:
310
324
raise InventoryDeltaError(
311
"duplicate file id in inventory delta %r" % lines)
325
"duplicate file id %(fileid)r", fileid=file_id)
312
326
seen_ids.add(file_id)
313
327
if (newpath_utf8 == '/' and not delta_versioned_root and
314
328
last_modified != delta_version_id):
315
329
# Delta claims to be not have a versioned root, yet here's
316
330
# a root entry with a non-default version.
317
raise InventoryDeltaError("Versioned root found: %r" % line)
331
raise InventoryDeltaError(
332
"Versioned root found: %(line)r", line=line)
318
333
elif newpath_utf8 != 'None' and last_modified[-1] == ':':
319
334
# Deletes have a last_modified of null:, but otherwise special
320
335
# revision ids should not occur.
321
raise InventoryDeltaError('special revisionid found: %r' % line)
336
raise InventoryDeltaError(
337
'special revisionid found: %(line)r', line=line)
322
338
if content.startswith('tree\x00'):
323
339
if delta_tree_references is False:
324
340
raise InventoryDeltaError(
325
341
"Tree reference found (but header said "
326
"tree_references: false): %r" % line)
342
"tree_references: false): %(line)r", line=line)
327
343
elif not self._allow_tree_references:
328
344
raise IncompatibleInventoryDelta(
329
345
"Tree reference not allowed")
332
348
elif oldpath_utf8[:1] != '/':
333
349
raise InventoryDeltaError(
334
"oldpath invalid (does not start with /): %r"
350
"oldpath invalid (does not start with /): %(path)r",
337
353
oldpath_utf8 = oldpath_utf8[1:]
338
354
oldpath = oldpath_utf8.decode('utf8')
341
357
elif newpath_utf8[:1] != '/':
342
358
raise InventoryDeltaError(
343
"newpath invalid (does not start with /): %r"
359
"newpath invalid (does not start with /): %(path)r",
346
362
# Trim leading slash
347
363
newpath_utf8 = newpath_utf8[1:]