/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: Andrew Bennetts
  • Date: 2009-07-08 02:23:24 UTC
  • mto: (4476.3.44 inventory-delta)
  • mto: This revision was merged to the branch mainline in revision 4608.
  • Revision ID: andrew.bennetts@canonical.com-20090708022324-jka0d4wnu239e7g2
Clean up some more nits.

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
from bzrlib import inventory
30
30
from bzrlib.revision import NULL_REVISION
31
31
 
32
 
FORMAT_1 = 'bzr inventory delta v1 (bzr 1.14)'
33
 
 
34
 
 
35
 
class InventoryDeltaError(errors.BzrError):
36
 
    """An error when serializing or deserializing an inventory delta."""
37
 
    
38
 
    # Most errors when serializing and deserializing are due to bugs, although
39
 
    # damaged input (i.e. a bug in a different process) could cause
40
 
    # deserialization errors too.
41
 
    internal_error = True
42
 
 
43
 
 
44
 
class IncompatibleInventoryDelta(errors.BzrError):
45
 
    """The delta could not be deserialised because its contents conflict with
46
 
    the allow_versioned_root or allow_tree_references flags of the
47
 
    deserializer.
48
 
    """
49
 
    internal_error = False
50
 
 
51
32
 
52
33
def _directory_content(entry):
53
34
    """Serialize the content component of entry which is a directory.
68
49
        exec_bytes = ''
69
50
    size_exec_sha = (entry.text_size, exec_bytes, entry.text_sha1)
70
51
    if None in size_exec_sha:
71
 
        raise InventoryDeltaError('Missing size or sha for %s' % entry.file_id)
 
52
        raise errors.BzrError('Missing size or sha for %s' % entry.file_id)
72
53
    return "file\x00%d\x00%s\x00%s" % size_exec_sha
73
54
 
74
55
 
79
60
    """
80
61
    target = entry.symlink_target
81
62
    if target is None:
82
 
        raise InventoryDeltaError('Missing target for %s' % entry.file_id)
 
63
        raise errors.BzrError('Missing target for %s' % entry.file_id)
83
64
    return "link\x00%s" % target.encode('utf8')
84
65
 
85
66
 
90
71
    """
91
72
    tree_revision = entry.reference_revision
92
73
    if tree_revision is None:
93
 
        raise InventoryDeltaError(
94
 
            'Missing reference revision for %s' % entry.file_id)
 
74
        raise errors.BzrError('Missing reference revision for %s' % entry.file_id)
95
75
    return "tree\x00%s" % tree_revision
96
76
 
97
77
 
135
115
    return result
136
116
 
137
117
 
 
118
 
138
119
class InventoryDeltaSerializer(object):
139
 
    """Serialize inventory deltas."""
140
 
 
141
 
    def __init__(self, versioned_root, tree_references):
142
 
        """Create an InventoryDeltaSerializer.
143
 
 
144
 
        :param versioned_root: If True, any root entry that is seen is expected
145
 
            to be versioned, and root entries can have any fileid.
146
 
        :param tree_references: If True support tree-reference entries.
147
 
        """
148
 
        self._versioned_root = versioned_root
149
 
        self._tree_references = tree_references
 
120
    """Serialize and deserialize inventory deltas."""
 
121
 
 
122
    # XXX: really, the serializer and deserializer should be two separate
 
123
    # classes.
 
124
 
 
125
    FORMAT_1 = 'bzr inventory delta v1 (bzr 1.14)'
 
126
 
 
127
    def __init__(self):
 
128
        """Create an InventoryDeltaSerializer."""
 
129
        self._versioned_root = None
 
130
        self._tree_references = None
150
131
        self._entry_to_content = {
151
132
            'directory': _directory_content,
152
133
            'file': _file_content,
153
134
            'symlink': _link_content,
154
135
        }
 
136
 
 
137
    def require_flags(self, versioned_root=None, tree_references=None):
 
138
        """Set the versioned_root and/or tree_references flags for this
 
139
        (de)serializer.
 
140
 
 
141
        :param versioned_root: If True, any root entry that is seen is expected
 
142
            to be versioned, and root entries can have any fileid.
 
143
        :param tree_references: If True support tree-reference entries.
 
144
        """
 
145
        if versioned_root is not None and self._versioned_root is not None:
 
146
            raise AssertionError(
 
147
                "require_flags(versioned_root=...) already called.")
 
148
        if tree_references is not None and self._tree_references is not None:
 
149
            raise AssertionError(
 
150
                "require_flags(tree_references=...) already called.")
 
151
        self._versioned_root = versioned_root
 
152
        self._tree_references = tree_references
155
153
        if tree_references:
156
154
            self._entry_to_content['tree-reference'] = _reference_content
157
155
 
170
168
            takes.
171
169
        :return: The serialized delta as lines.
172
170
        """
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,))
 
171
        if self._versioned_root is None or self._tree_references is None:
 
172
            raise AssertionError(
 
173
                "Cannot serialise unless versioned_root/tree_references flags "
 
174
                "are both set.")
177
175
        lines = ['', '', '', '', '']
178
176
        to_line = self._delta_item_to_line
179
177
        for delta_item in delta_to_new:
180
 
            line = to_line(delta_item, new_name)
181
 
            if line.__class__ != str:
182
 
                raise InventoryDeltaError(
 
178
            lines.append(to_line(delta_item))
 
179
            if lines[-1].__class__ != str:
 
180
                raise errors.BzrError(
183
181
                    'to_line generated non-str output %r' % lines[-1])
184
 
            lines.append(line)
185
182
        lines.sort()
186
 
        lines[0] = "format: %s\n" % FORMAT_1
 
183
        lines[0] = "format: %s\n" % InventoryDeltaSerializer.FORMAT_1
187
184
        lines[1] = "parent: %s\n" % old_name
188
185
        lines[2] = "version: %s\n" % new_name
189
186
        lines[3] = "versioned_root: %s\n" % self._serialize_bool(
198
195
        else:
199
196
            return "false"
200
197
 
201
 
    def _delta_item_to_line(self, delta_item, new_version):
 
198
    def _delta_item_to_line(self, delta_item):
202
199
        """Convert delta_item to a line."""
203
200
        oldpath, newpath, file_id, entry = delta_item
204
201
        if newpath is None:
213
210
                oldpath_utf8 = 'None'
214
211
            else:
215
212
                oldpath_utf8 = '/' + oldpath.encode('utf8')
216
 
            if newpath == '/':
217
 
                raise AssertionError(
218
 
                    "Bad inventory delta: '/' is not a valid newpath "
219
 
                    "(should be '') in delta item %r" % (delta_item,))
220
213
            # TODO: Test real-world utf8 cache hit rate. It may be a win.
221
214
            newpath_utf8 = '/' + newpath.encode('utf8')
222
215
            # Serialize None as ''
225
218
            last_modified = entry.revision
226
219
            # special cases for /
227
220
            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
231
 
                # delta is invalid.
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
234
 
                # xml5 serializer.
235
 
                if last_modified != new_version:
236
 
                    raise InventoryDeltaError(
237
 
                        'Version present for / in %s (%s != %s)'
238
 
                        % (file_id, last_modified, new_version))
 
221
                if file_id != 'TREE_ROOT':
 
222
                    raise errors.BzrError(
 
223
                        'file_id %s is not TREE_ROOT for /' % file_id)
 
224
                if last_modified is not None:
 
225
                    raise errors.BzrError(
 
226
                        'Version present for / in %s' % file_id)
 
227
                last_modified = NULL_REVISION
239
228
            if last_modified is None:
240
 
                raise InventoryDeltaError("no version for fileid %s" % file_id)
 
229
                raise errors.BzrError("no version for fileid %s" % file_id)
241
230
            content = self._entry_to_content[entry.kind](entry)
242
231
        return ("%s\x00%s\x00%s\x00%s\x00%s\x00%s\n" %
243
232
            (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
244
233
                content))
245
234
 
246
 
 
247
 
class InventoryDeltaDeserializer(object):
248
 
    """Deserialize inventory deltas."""
249
 
 
250
 
    def __init__(self, allow_versioned_root=True, allow_tree_references=True):
251
 
        """Create an InventoryDeltaDeserializer.
252
 
 
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.
256
 
        """
257
 
        self._allow_versioned_root = allow_versioned_root
258
 
        self._allow_tree_references = allow_tree_references
259
 
 
260
235
    def _deserialize_bool(self, value):
261
236
        if value == "true":
262
237
            return True
263
238
        elif value == "false":
264
239
            return False
265
240
        else:
266
 
            raise InventoryDeltaError("value %r is not a bool" % (value,))
 
241
            raise errors.BzrError("value %r is not a bool" % (value,))
267
242
 
268
243
    def parse_text_bytes(self, bytes):
269
244
        """Parse the text bytes of a serialized inventory delta.
277
252
        :return: (parent_id, new_id, versioned_root, tree_references,
278
253
            inventory_delta)
279
254
        """
280
 
        if bytes[-1:] != '\n':
281
 
            last_line = bytes.rsplit('\n', 1)[-1]
282
 
            raise InventoryDeltaError('last line not empty: %r' % (last_line,))
283
255
        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])
 
256
        if not lines or lines[0] != 'format: %s' % InventoryDeltaSerializer.FORMAT_1:
 
257
            raise errors.BzrError('unknown format %r' % lines[0:1])
286
258
        if len(lines) < 2 or not lines[1].startswith('parent: '):
287
 
            raise InventoryDeltaError('missing parent: marker')
 
259
            raise errors.BzrError('missing parent: marker')
288
260
        delta_parent_id = lines[1][8:]
289
261
        if len(lines) < 3 or not lines[2].startswith('version: '):
290
 
            raise InventoryDeltaError('missing version: marker')
 
262
            raise errors.BzrError('missing version: marker')
291
263
        delta_version_id = lines[2][9:]
292
264
        if len(lines) < 4 or not lines[3].startswith('versioned_root: '):
293
 
            raise InventoryDeltaError('missing versioned_root: marker')
 
265
            raise errors.BzrError('missing versioned_root: marker')
294
266
        delta_versioned_root = self._deserialize_bool(lines[3][16:])
295
267
        if len(lines) < 5 or not lines[4].startswith('tree_references: '):
296
 
            raise InventoryDeltaError('missing tree_references: marker')
 
268
            raise errors.BzrError('missing tree_references: marker')
297
269
        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")
 
270
        if (self._versioned_root is not None and
 
271
            delta_versioned_root != self._versioned_root):
 
272
            raise errors.BzrError(
 
273
                "serialized versioned_root flag is wrong: %s" %
 
274
                (delta_versioned_root,))
 
275
        if (self._tree_references is not None
 
276
            and delta_tree_references != self._tree_references):
 
277
            raise errors.BzrError(
 
278
                "serialized tree_references flag is wrong: %s" %
 
279
                (delta_tree_references,))
300
280
        result = []
301
281
        seen_ids = set()
302
282
        line_iter = iter(lines)
307
287
                content) = line.split('\x00', 5)
308
288
            parent_id = parent_id or None
309
289
            if file_id in seen_ids:
310
 
                raise InventoryDeltaError(
 
290
                raise errors.BzrError(
311
291
                    "duplicate file id in inventory delta %r" % lines)
312
292
            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")
 
293
            if newpath_utf8 == '/' and not delta_versioned_root and (
 
294
                last_modified != 'null:' or file_id != 'TREE_ROOT'):
 
295
                    raise errors.BzrError("Versioned root found: %r" % line)
 
296
            elif last_modified[-1] == ':':
 
297
                    raise errors.BzrError('special revisionid found: %r' % line)
 
298
            if delta_tree_references is False and content.startswith('tree\x00'):
 
299
                raise errors.BzrError("Tree reference found: %r" % line)
 
300
            content_tuple = tuple(content.split('\x00'))
 
301
            entry = _parse_entry(
 
302
                newpath_utf8, file_id, parent_id, last_modified, content_tuple)
330
303
            if oldpath_utf8 == 'None':
331
304
                oldpath = None
332
 
            elif oldpath_utf8[:1] != '/':
333
 
                raise InventoryDeltaError(
334
 
                    "oldpath invalid (does not start with /): %r"
335
 
                    % (oldpath_utf8,))
336
305
            else:
337
 
                oldpath_utf8 = oldpath_utf8[1:]
338
306
                oldpath = oldpath_utf8.decode('utf8')
339
307
            if newpath_utf8 == 'None':
340
308
                newpath = None
341
 
            elif newpath_utf8[:1] != '/':
342
 
                raise InventoryDeltaError(
343
 
                    "newpath invalid (does not start with /): %r"
344
 
                    % (newpath_utf8,))
345
309
            else:
346
 
                # Trim leading slash
347
 
                newpath_utf8 = newpath_utf8[1:]
348
310
                newpath = newpath_utf8.decode('utf8')
349
 
            content_tuple = tuple(content.split('\x00'))
350
 
            if content_tuple[0] == 'deleted':
351
 
                entry = None
352
 
            else:
353
 
                entry = _parse_entry(
354
 
                    newpath, file_id, parent_id, last_modified, content_tuple)
355
311
            delta_item = (oldpath, newpath, file_id, entry)
356
312
            result.append(delta_item)
357
313
        return (delta_parent_id, delta_version_id, delta_versioned_root,
358
314
                delta_tree_references, result)
359
315
 
360
316
 
361
 
def _parse_entry(path, file_id, parent_id, last_modified, content):
 
317
def _parse_entry(utf8_path, file_id, parent_id, last_modified, content):
362
318
    entry_factory = {
363
319
        'dir': _dir_to_entry,
364
320
        'file': _file_to_entry,
366
322
        'tree': _tree_to_entry,
367
323
    }
368
324
    kind = content[0]
369
 
    if path.startswith('/'):
370
 
        raise AssertionError
 
325
    path = utf8_path[1:].decode('utf8')
371
326
    name = basename(path)
372
327
    return entry_factory[content[0]](
373
328
            content, name, parent_id, file_id, last_modified)
374
329
 
375