~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory_delta.py

  • Committer: Vincent Ladeuil
  • Date: 2010-01-25 15:55:48 UTC
  • mto: (4985.1.4 add-attr-cleanup)
  • mto: This revision was merged to the branch mainline in revision 4988.
  • Revision ID: v.ladeuil+lp@free.fr-20100125155548-0l352pujvt5bzl5e
Deploy addAttrCleanup on the whole test suite.

Several use case worth mentioning:

- setting a module or any other object attribute is the majority
by far. In some cases the setting itself is deferred but most of
the time we want to set at the same time we add the cleanup.

- there multiple occurrences of protecting hooks or ui factory
which are now useless (the test framework takes care of that now),

- there was some lambda uses that can now be avoided.

That first cleanup already simplifies things a lot.

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
 
32
51
 
33
52
def _directory_content(entry):
34
53
    """Serialize the content component of entry which is a directory.
49
68
        exec_bytes = ''
50
69
    size_exec_sha = (entry.text_size, exec_bytes, entry.text_sha1)
51
70
    if None in size_exec_sha:
52
 
        raise errors.BzrError('Missing size or sha for %s' % entry.file_id)
 
71
        raise InventoryDeltaError('Missing size or sha for %s' % entry.file_id)
53
72
    return "file\x00%d\x00%s\x00%s" % size_exec_sha
54
73
 
55
74
 
60
79
    """
61
80
    target = entry.symlink_target
62
81
    if target is None:
63
 
        raise errors.BzrError('Missing target for %s' % entry.file_id)
 
82
        raise InventoryDeltaError('Missing target for %s' % entry.file_id)
64
83
    return "link\x00%s" % target.encode('utf8')
65
84
 
66
85
 
71
90
    """
72
91
    tree_revision = entry.reference_revision
73
92
    if tree_revision is None:
74
 
        raise errors.BzrError('Missing reference revision for %s' % entry.file_id)
 
93
        raise InventoryDeltaError(
 
94
            'Missing reference revision for %s' % entry.file_id)
75
95
    return "tree\x00%s" % tree_revision
76
96
 
77
97
 
115
135
    return result
116
136
 
117
137
 
118
 
 
119
138
class InventoryDeltaSerializer(object):
120
 
    """Serialize and deserialize inventory deltas."""
121
 
 
122
 
    FORMAT_1 = 'bzr inventory delta v1 (bzr 1.14)'
 
139
    """Serialize inventory deltas."""
123
140
 
124
141
    def __init__(self, versioned_root, tree_references):
125
142
        """Create an InventoryDeltaSerializer.
141
158
    def delta_to_lines(self, old_name, new_name, delta_to_new):
142
159
        """Return a line sequence for delta_to_new.
143
160
 
 
161
        Both the versioned_root and tree_references flags must be set via
 
162
        require_flags before calling this.
 
163
 
144
164
        :param old_name: A UTF8 revision id for the old inventory.  May be
145
165
            NULL_REVISION if there is no older inventory and delta_to_new
146
166
            includes the entire inventory contents.
150
170
            takes.
151
171
        :return: The serialized delta as lines.
152
172
        """
 
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,))
153
177
        lines = ['', '', '', '', '']
154
178
        to_line = self._delta_item_to_line
155
179
        for delta_item in delta_to_new:
156
 
            lines.append(to_line(delta_item))
157
 
            if lines[-1].__class__ != str:
158
 
                raise errors.BzrError(
 
180
            line = to_line(delta_item, new_name)
 
181
            if line.__class__ != str:
 
182
                raise InventoryDeltaError(
159
183
                    'to_line generated non-str output %r' % lines[-1])
 
184
            lines.append(line)
160
185
        lines.sort()
161
 
        lines[0] = "format: %s\n" % InventoryDeltaSerializer.FORMAT_1
 
186
        lines[0] = "format: %s\n" % FORMAT_1
162
187
        lines[1] = "parent: %s\n" % old_name
163
188
        lines[2] = "version: %s\n" % new_name
164
189
        lines[3] = "versioned_root: %s\n" % self._serialize_bool(
173
198
        else:
174
199
            return "false"
175
200
 
176
 
    def _delta_item_to_line(self, delta_item):
 
201
    def _delta_item_to_line(self, delta_item, new_version):
177
202
        """Convert delta_item to a line."""
178
203
        oldpath, newpath, file_id, entry = delta_item
179
204
        if newpath is None:
188
213
                oldpath_utf8 = 'None'
189
214
            else:
190
215
                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,))
191
220
            # TODO: Test real-world utf8 cache hit rate. It may be a win.
192
221
            newpath_utf8 = '/' + newpath.encode('utf8')
193
222
            # Serialize None as ''
196
225
            last_modified = entry.revision
197
226
            # special cases for /
198
227
            if newpath_utf8 == '/' and not self._versioned_root:
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
 
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))
206
239
            if last_modified is None:
207
 
                raise errors.BzrError("no version for fileid %s" % file_id)
 
240
                raise InventoryDeltaError("no version for fileid %s" % file_id)
208
241
            content = self._entry_to_content[entry.kind](entry)
209
242
        return ("%s\x00%s\x00%s\x00%s\x00%s\x00%s\n" %
210
243
            (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
211
244
                content))
212
245
 
 
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
 
213
260
    def _deserialize_bool(self, value):
214
261
        if value == "true":
215
262
            return True
216
263
        elif value == "false":
217
264
            return False
218
265
        else:
219
 
            raise errors.BzrError("value %r is not a bool" % (value,))
 
266
            raise InventoryDeltaError("value %r is not a bool" % (value,))
220
267
 
221
268
    def parse_text_bytes(self, bytes):
222
269
        """Parse the text bytes of a serialized inventory delta.
223
270
 
 
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
 
273
        raised.
 
274
 
224
275
        :param bytes: The bytes to parse. This can be obtained by calling
225
276
            delta_to_lines and then doing ''.join(delta_lines).
226
 
        :return: (parent_id, new_id, inventory_delta)
 
277
        :return: (parent_id, new_id, versioned_root, tree_references,
 
278
            inventory_delta)
227
279
        """
 
280
        if bytes[-1:] != '\n':
 
281
            last_line = bytes.rsplit('\n', 1)[-1]
 
282
            raise InventoryDeltaError('last line not empty: %r' % (last_line,))
228
283
        lines = bytes.split('\n')[:-1] # discard the last empty line
229
 
        if not lines or lines[0] != 'format: %s' % InventoryDeltaSerializer.FORMAT_1:
230
 
            raise errors.BzrError('unknown format %r' % lines[0:1])
 
284
        if not lines or lines[0] != 'format: %s' % FORMAT_1:
 
285
            raise InventoryDeltaError('unknown format %r' % lines[0:1])
231
286
        if len(lines) < 2 or not lines[1].startswith('parent: '):
232
 
            raise errors.BzrError('missing parent: marker')
 
287
            raise InventoryDeltaError('missing parent: marker')
233
288
        delta_parent_id = lines[1][8:]
234
289
        if len(lines) < 3 or not lines[2].startswith('version: '):
235
 
            raise errors.BzrError('missing version: marker')
 
290
            raise InventoryDeltaError('missing version: marker')
236
291
        delta_version_id = lines[2][9:]
237
292
        if len(lines) < 4 or not lines[3].startswith('versioned_root: '):
238
 
            raise errors.BzrError('missing versioned_root: marker')
 
293
            raise InventoryDeltaError('missing versioned_root: marker')
239
294
        delta_versioned_root = self._deserialize_bool(lines[3][16:])
240
295
        if len(lines) < 5 or not lines[4].startswith('tree_references: '):
241
 
            raise errors.BzrError('missing tree_references: marker')
 
296
            raise InventoryDeltaError('missing tree_references: marker')
242
297
        delta_tree_references = self._deserialize_bool(lines[4][17:])
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,))
 
298
        if (not self._allow_versioned_root and delta_versioned_root):
 
299
            raise IncompatibleInventoryDelta("versioned_root not allowed")
251
300
        result = []
252
301
        seen_ids = set()
253
302
        line_iter = iter(lines)
258
307
                content) = line.split('\x00', 5)
259
308
            parent_id = parent_id or None
260
309
            if file_id in seen_ids:
261
 
                raise errors.BzrError(
 
310
                raise InventoryDeltaError(
262
311
                    "duplicate file id in inventory delta %r" % lines)
263
312
            seen_ids.add(file_id)
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)
 
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")
274
330
            if oldpath_utf8 == 'None':
275
331
                oldpath = None
 
332
            elif oldpath_utf8[:1] != '/':
 
333
                raise InventoryDeltaError(
 
334
                    "oldpath invalid (does not start with /): %r"
 
335
                    % (oldpath_utf8,))
276
336
            else:
 
337
                oldpath_utf8 = oldpath_utf8[1:]
277
338
                oldpath = oldpath_utf8.decode('utf8')
278
339
            if newpath_utf8 == 'None':
279
340
                newpath = None
 
341
            elif newpath_utf8[:1] != '/':
 
342
                raise InventoryDeltaError(
 
343
                    "newpath invalid (does not start with /): %r"
 
344
                    % (newpath_utf8,))
280
345
            else:
 
346
                # Trim leading slash
 
347
                newpath_utf8 = newpath_utf8[1:]
281
348
                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)
282
355
            delta_item = (oldpath, newpath, file_id, entry)
283
356
            result.append(delta_item)
284
 
        return delta_parent_id, delta_version_id, result
285
 
 
286
 
 
287
 
def _parse_entry(utf8_path, file_id, parent_id, last_modified, content):
 
357
        return (delta_parent_id, delta_version_id, delta_versioned_root,
 
358
                delta_tree_references, result)
 
359
 
 
360
 
 
361
def _parse_entry(path, file_id, parent_id, last_modified, content):
288
362
    entry_factory = {
289
363
        'dir': _dir_to_entry,
290
364
        'file': _file_to_entry,
292
366
        'tree': _tree_to_entry,
293
367
    }
294
368
    kind = content[0]
295
 
    path = utf8_path[1:].decode('utf8')
 
369
    if path.startswith('/'):
 
370
        raise AssertionError
296
371
    name = basename(path)
297
372
    return entry_factory[content[0]](
298
373
            content, name, parent_id, file_id, last_modified)
299
374
 
 
375