/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 bzrlib/inventory_delta.py

  • Committer: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

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