/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to brzlib/inventory_delta.py

  • Committer: Jelmer Vernooij
  • Date: 2017-05-21 12:41:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6623.
  • Revision ID: jelmer@jelmer.uk-20170521124127-iv8etg0vwymyai6y
s/bzr/brz/ in apport config.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
 - InventoryDeltaSerializer - object to read/write inventory deltas.
23
23
"""
24
24
 
 
25
from __future__ import absolute_import
 
26
 
25
27
__all__ = ['InventoryDeltaSerializer']
26
28
 
27
 
from .. import errors
28
 
from ..osutils import basename
29
 
from . import inventory
30
 
from ..revision import NULL_REVISION
 
29
from brzlib import errors
 
30
from brzlib.osutils import basename
 
31
from brzlib import inventory
 
32
from brzlib.revision import NULL_REVISION
31
33
 
32
 
FORMAT_1 = b'bzr inventory delta v1 (bzr 1.14)'
 
34
FORMAT_1 = 'bzr inventory delta v1 (bzr 1.14)'
33
35
 
34
36
 
35
37
class InventoryDeltaError(errors.BzrError):
36
38
    """An error when serializing or deserializing an inventory delta."""
37
 
 
 
39
    
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
42
44
 
43
 
    def __init__(self, format_string, **kwargs):
44
 
        # Let each error supply a custom format string and arguments.
45
 
        self._fmt = format_string
46
 
        super(InventoryDeltaError, self).__init__(**kwargs)
47
 
 
48
45
 
49
46
class IncompatibleInventoryDelta(errors.BzrError):
50
47
    """The delta could not be deserialised because its contents conflict with
56
53
 
57
54
def _directory_content(entry):
58
55
    """Serialize the content component of entry which is a directory.
59
 
 
 
56
    
60
57
    :param entry: An InventoryDirectory.
61
58
    """
62
 
    return b"dir"
 
59
    return "dir"
63
60
 
64
61
 
65
62
def _file_content(entry):
66
63
    """Serialize the content component of entry which is a file.
67
 
 
 
64
    
68
65
    :param entry: An InventoryFile.
69
66
    """
70
67
    if entry.executable:
71
 
        exec_bytes = b'Y'
 
68
        exec_bytes = 'Y'
72
69
    else:
73
 
        exec_bytes = b''
74
 
    size_exec_sha = entry.text_size, exec_bytes, entry.text_sha1
 
70
        exec_bytes = ''
 
71
    size_exec_sha = (entry.text_size, exec_bytes, entry.text_sha1)
75
72
    if None in size_exec_sha:
76
 
        raise InventoryDeltaError(
77
 
            'Missing size or sha for %(fileid)r', fileid=entry.file_id)
78
 
    return b"file\x00%d\x00%s\x00%s" % size_exec_sha
 
73
        raise InventoryDeltaError('Missing size or sha for %s' % entry.file_id)
 
74
    return "file\x00%d\x00%s\x00%s" % size_exec_sha
79
75
 
80
76
 
81
77
def _link_content(entry):
82
78
    """Serialize the content component of entry which is a symlink.
83
 
 
 
79
    
84
80
    :param entry: An InventoryLink.
85
81
    """
86
82
    target = entry.symlink_target
87
83
    if target is None:
88
 
        raise InventoryDeltaError(
89
 
            'Missing target for %(fileid)r', fileid=entry.file_id)
90
 
    return b"link\x00%s" % target.encode('utf8')
 
84
        raise InventoryDeltaError('Missing target for %s' % entry.file_id)
 
85
    return "link\x00%s" % target.encode('utf8')
91
86
 
92
87
 
93
88
def _reference_content(entry):
94
89
    """Serialize the content component of entry which is a tree-reference.
95
 
 
 
90
    
96
91
    :param entry: A TreeReference.
97
92
    """
98
93
    tree_revision = entry.reference_revision
99
94
    if tree_revision is None:
100
95
        raise InventoryDeltaError(
101
 
            'Missing reference revision for %(fileid)r', fileid=entry.file_id)
102
 
    return b"tree\x00%s" % tree_revision
 
96
            'Missing reference revision for %s' % entry.file_id)
 
97
    return "tree\x00%s" % tree_revision
103
98
 
104
99
 
105
100
def _dir_to_entry(content, name, parent_id, file_id, last_modified,
106
 
                  _type=inventory.InventoryDirectory):
 
101
    _type=inventory.InventoryDirectory):
107
102
    """Convert a dir content record to an InventoryDirectory."""
108
103
    result = _type(file_id, name, parent_id)
109
104
    result.revision = last_modified
111
106
 
112
107
 
113
108
def _file_to_entry(content, name, parent_id, file_id, last_modified,
114
 
                   _type=inventory.InventoryFile):
 
109
    _type=inventory.InventoryFile):
115
110
    """Convert a dir content record to an InventoryFile."""
116
111
    result = _type(file_id, name, parent_id)
117
112
    result.revision = last_modified
125
120
 
126
121
 
127
122
def _link_to_entry(content, name, parent_id, file_id, last_modified,
128
 
                   _type=inventory.InventoryLink):
 
123
    _type=inventory.InventoryLink):
129
124
    """Convert a link content record to an InventoryLink."""
130
125
    result = _type(file_id, name, parent_id)
131
126
    result.revision = last_modified
134
129
 
135
130
 
136
131
def _tree_to_entry(content, name, parent_id, file_id, last_modified,
137
 
                   _type=inventory.TreeReference):
 
132
    _type=inventory.TreeReference):
138
133
    """Convert a tree content record to a TreeReference."""
139
134
    result = _type(file_id, name, parent_id)
140
135
    result.revision = last_modified
177
172
            takes.
178
173
        :return: The serialized delta as lines.
179
174
        """
180
 
        if not isinstance(old_name, bytes):
 
175
        if type(old_name) is not str:
181
176
            raise TypeError('old_name should be str, got %r' % (old_name,))
182
 
        if not isinstance(new_name, bytes):
 
177
        if type(new_name) is not str:
183
178
            raise TypeError('new_name should be str, got %r' % (new_name,))
184
 
        lines = [b'', b'', b'', b'', b'']
 
179
        lines = ['', '', '', '', '']
185
180
        to_line = self._delta_item_to_line
186
181
        for delta_item in delta_to_new:
187
182
            line = to_line(delta_item, new_name)
188
 
            # GZ 2017-06-09: Not really worth asserting this here
189
 
            if line.__class__ != bytes:
 
183
            if line.__class__ != str:
190
184
                raise InventoryDeltaError(
191
 
                    'to_line gave non-bytes output %(line)r', line=lines[-1])
 
185
                    'to_line generated non-str output %r' % lines[-1])
192
186
            lines.append(line)
193
187
        lines.sort()
194
 
        lines[0] = b"format: %s\n" % FORMAT_1
195
 
        lines[1] = b"parent: %s\n" % old_name
196
 
        lines[2] = b"version: %s\n" % new_name
197
 
        lines[3] = b"versioned_root: %s\n" % self._serialize_bool(
 
188
        lines[0] = "format: %s\n" % FORMAT_1
 
189
        lines[1] = "parent: %s\n" % old_name
 
190
        lines[2] = "version: %s\n" % new_name
 
191
        lines[3] = "versioned_root: %s\n" % self._serialize_bool(
198
192
            self._versioned_root)
199
 
        lines[4] = b"tree_references: %s\n" % self._serialize_bool(
 
193
        lines[4] = "tree_references: %s\n" % self._serialize_bool(
200
194
            self._tree_references)
201
195
        return lines
202
196
 
203
197
    def _serialize_bool(self, value):
204
198
        if value:
205
 
            return b"true"
 
199
            return "true"
206
200
        else:
207
 
            return b"false"
 
201
            return "false"
208
202
 
209
203
    def _delta_item_to_line(self, delta_item, new_version):
210
204
        """Convert delta_item to a line."""
211
205
        oldpath, newpath, file_id, entry = delta_item
212
206
        if newpath is None:
213
207
            # delete
214
 
            oldpath_utf8 = b'/' + oldpath.encode('utf8')
215
 
            newpath_utf8 = b'None'
216
 
            parent_id = b''
 
208
            oldpath_utf8 = '/' + oldpath.encode('utf8')
 
209
            newpath_utf8 = 'None'
 
210
            parent_id = ''
217
211
            last_modified = NULL_REVISION
218
 
            content = b'deleted\x00\x00'
 
212
            content = 'deleted\x00\x00'
219
213
        else:
220
214
            if oldpath is None:
221
 
                oldpath_utf8 = b'None'
 
215
                oldpath_utf8 = 'None'
222
216
            else:
223
 
                oldpath_utf8 = b'/' + oldpath.encode('utf8')
 
217
                oldpath_utf8 = '/' + oldpath.encode('utf8')
224
218
            if newpath == '/':
225
219
                raise AssertionError(
226
220
                    "Bad inventory delta: '/' is not a valid newpath "
227
221
                    "(should be '') in delta item %r" % (delta_item,))
228
222
            # TODO: Test real-world utf8 cache hit rate. It may be a win.
229
 
            newpath_utf8 = b'/' + newpath.encode('utf8')
 
223
            newpath_utf8 = '/' + newpath.encode('utf8')
230
224
            # Serialize None as ''
231
 
            parent_id = entry.parent_id or b''
 
225
            parent_id = entry.parent_id or ''
232
226
            # Serialize unknown revisions as NULL_REVISION
233
227
            last_modified = entry.revision
234
228
            # special cases for /
235
 
            if newpath_utf8 == b'/' and not self._versioned_root:
 
229
            if newpath_utf8 == '/' and not self._versioned_root:
236
230
                # This is an entry for the root, this inventory does not
237
231
                # support versioned roots.  So this must be an unversioned
238
232
                # root, i.e. last_modified == new revision.  Otherwise, this
242
236
                # xml5 serializer.
243
237
                if last_modified != new_version:
244
238
                    raise InventoryDeltaError(
245
 
                        'Version present for / in %(fileid)r'
246
 
                        ' (%(last)r != %(new)r)',
247
 
                        fileid=file_id, last=last_modified, new=new_version)
 
239
                        'Version present for / in %s (%s != %s)'
 
240
                        % (file_id, last_modified, new_version))
248
241
            if last_modified is None:
249
 
                raise InventoryDeltaError(
250
 
                    "no version for fileid %(fileid)r", fileid=file_id)
 
242
                raise InventoryDeltaError("no version for fileid %s" % file_id)
251
243
            content = self._entry_to_content[entry.kind](entry)
252
 
        return (b"%s\x00%s\x00%s\x00%s\x00%s\x00%s\n" %
253
 
                (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
254
 
                 content))
 
244
        return ("%s\x00%s\x00%s\x00%s\x00%s\x00%s\n" %
 
245
            (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
 
246
                content))
255
247
 
256
248
 
257
249
class InventoryDeltaDeserializer(object):
268
260
        self._allow_tree_references = allow_tree_references
269
261
 
270
262
    def _deserialize_bool(self, value):
271
 
        if value == b"true":
 
263
        if value == "true":
272
264
            return True
273
 
        elif value == b"false":
 
265
        elif value == "false":
274
266
            return False
275
267
        else:
276
 
            raise InventoryDeltaError("value %(val)r is not a bool", val=value)
 
268
            raise InventoryDeltaError("value %r is not a bool" % (value,))
277
269
 
278
 
    def parse_text_bytes(self, lines):
 
270
    def parse_text_bytes(self, bytes):
279
271
        """Parse the text bytes of a serialized inventory delta.
280
272
 
281
273
        If versioned_root and/or tree_references flags were set via
282
274
        require_flags, then the parsed flags must match or a BzrError will be
283
275
        raised.
284
276
 
285
 
        :param lines: The lines to parse. This can be obtained by calling
286
 
            delta_to_lines.
 
277
        :param bytes: The bytes to parse. This can be obtained by calling
 
278
            delta_to_lines and then doing ''.join(delta_lines).
287
279
        :return: (parent_id, new_id, versioned_root, tree_references,
288
280
            inventory_delta)
289
281
        """
290
 
        if not lines:
291
 
            raise InventoryDeltaError(
292
 
                'inventory delta is empty')
293
 
        if not lines[-1].endswith(b'\n'):
294
 
            raise InventoryDeltaError(
295
 
                'last line not empty: %(line)r', line=lines[-1])
296
 
        lines = [line.rstrip(b'\n') for line in lines]  # discard the last empty line
297
 
        if not lines or lines[0] != b'format: %s' % FORMAT_1:
298
 
            raise InventoryDeltaError(
299
 
                'unknown format %(line)r', line=lines[0:1])
300
 
        if len(lines) < 2 or not lines[1].startswith(b'parent: '):
 
282
        if bytes[-1:] != '\n':
 
283
            last_line = bytes.rsplit('\n', 1)[-1]
 
284
            raise InventoryDeltaError('last line not empty: %r' % (last_line,))
 
285
        lines = bytes.split('\n')[:-1] # discard the last empty line
 
286
        if not lines or lines[0] != 'format: %s' % FORMAT_1:
 
287
            raise InventoryDeltaError('unknown format %r' % lines[0:1])
 
288
        if len(lines) < 2 or not lines[1].startswith('parent: '):
301
289
            raise InventoryDeltaError('missing parent: marker')
302
290
        delta_parent_id = lines[1][8:]
303
 
        if len(lines) < 3 or not lines[2].startswith(b'version: '):
 
291
        if len(lines) < 3 or not lines[2].startswith('version: '):
304
292
            raise InventoryDeltaError('missing version: marker')
305
293
        delta_version_id = lines[2][9:]
306
 
        if len(lines) < 4 or not lines[3].startswith(b'versioned_root: '):
 
294
        if len(lines) < 4 or not lines[3].startswith('versioned_root: '):
307
295
            raise InventoryDeltaError('missing versioned_root: marker')
308
296
        delta_versioned_root = self._deserialize_bool(lines[3][16:])
309
 
        if len(lines) < 5 or not lines[4].startswith(b'tree_references: '):
 
297
        if len(lines) < 5 or not lines[4].startswith('tree_references: '):
310
298
            raise InventoryDeltaError('missing tree_references: marker')
311
299
        delta_tree_references = self._deserialize_bool(lines[4][17:])
312
300
        if (not self._allow_versioned_root and delta_versioned_root):
315
303
        seen_ids = set()
316
304
        line_iter = iter(lines)
317
305
        for i in range(5):
318
 
            next(line_iter)
 
306
            line_iter.next()
319
307
        for line in line_iter:
320
308
            (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
321
 
                content) = line.split(b'\x00', 5)
 
309
                content) = line.split('\x00', 5)
322
310
            parent_id = parent_id or None
323
311
            if file_id in seen_ids:
324
312
                raise InventoryDeltaError(
325
 
                    "duplicate file id %(fileid)r", fileid=file_id)
 
313
                    "duplicate file id in inventory delta %r" % lines)
326
314
            seen_ids.add(file_id)
327
 
            if (newpath_utf8 == b'/' and not delta_versioned_root and
328
 
                    last_modified != delta_version_id):
329
 
                # Delta claims to be not have a versioned root, yet here's
330
 
                # a root entry with a non-default version.
331
 
                raise InventoryDeltaError(
332
 
                    "Versioned root found: %(line)r", line=line)
333
 
            elif newpath_utf8 != b'None' and last_modified[-1:] == b':':
 
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] == ':':
334
321
                # Deletes have a last_modified of null:, but otherwise special
335
322
                # revision ids should not occur.
336
 
                raise InventoryDeltaError(
337
 
                    'special revisionid found: %(line)r', line=line)
338
 
            if content.startswith(b'tree\x00'):
 
323
                raise InventoryDeltaError('special revisionid found: %r' % line)
 
324
            if content.startswith('tree\x00'):
339
325
                if delta_tree_references is False:
340
326
                    raise InventoryDeltaError(
341
 
                        "Tree reference found (but header said "
342
 
                        "tree_references: false): %(line)r", line=line)
 
327
                            "Tree reference found (but header said "
 
328
                            "tree_references: false): %r" % line)
343
329
                elif not self._allow_tree_references:
344
330
                    raise IncompatibleInventoryDelta(
345
331
                        "Tree reference not allowed")
346
 
            if oldpath_utf8 == b'None':
 
332
            if oldpath_utf8 == 'None':
347
333
                oldpath = None
348
 
            elif oldpath_utf8[:1] != b'/':
 
334
            elif oldpath_utf8[:1] != '/':
349
335
                raise InventoryDeltaError(
350
 
                    "oldpath invalid (does not start with /): %(path)r",
351
 
                    path=oldpath_utf8)
 
336
                    "oldpath invalid (does not start with /): %r"
 
337
                    % (oldpath_utf8,))
352
338
            else:
353
339
                oldpath_utf8 = oldpath_utf8[1:]
354
340
                oldpath = oldpath_utf8.decode('utf8')
355
 
            if newpath_utf8 == b'None':
 
341
            if newpath_utf8 == 'None':
356
342
                newpath = None
357
 
            elif newpath_utf8[:1] != b'/':
 
343
            elif newpath_utf8[:1] != '/':
358
344
                raise InventoryDeltaError(
359
 
                    "newpath invalid (does not start with /): %(path)r",
360
 
                    path=newpath_utf8)
 
345
                    "newpath invalid (does not start with /): %r"
 
346
                    % (newpath_utf8,))
361
347
            else:
362
348
                # Trim leading slash
363
349
                newpath_utf8 = newpath_utf8[1:]
364
350
                newpath = newpath_utf8.decode('utf8')
365
 
            content_tuple = tuple(content.split(b'\x00'))
366
 
            if content_tuple[0] == b'deleted':
 
351
            content_tuple = tuple(content.split('\x00'))
 
352
            if content_tuple[0] == 'deleted':
367
353
                entry = None
368
354
            else:
369
355
                entry = _parse_entry(
376
362
 
377
363
def _parse_entry(path, file_id, parent_id, last_modified, content):
378
364
    entry_factory = {
379
 
        b'dir': _dir_to_entry,
380
 
        b'file': _file_to_entry,
381
 
        b'link': _link_to_entry,
382
 
        b'tree': _tree_to_entry,
 
365
        'dir': _dir_to_entry,
 
366
        'file': _file_to_entry,
 
367
        'link': _link_to_entry,
 
368
        'tree': _tree_to_entry,
383
369
    }
384
370
    kind = content[0]
385
371
    if path.startswith('/'):
386
372
        raise AssertionError
387
373
    name = basename(path)
388
374
    return entry_factory[content[0]](
389
 
        content, name, parent_id, file_id, last_modified)
 
375
            content, name, parent_id, file_id, last_modified)
 
376
 
 
377