/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: John Arbash Meinel
  • Date: 2009-06-18 18:18:36 UTC
  • mto: This revision was merged to the branch mainline in revision 4461.
  • Revision ID: john@arbash-meinel.com-20090618181836-biodfkat9a8eyzjz
The new add_inventory_by_delta is returning a CHKInventory when mapping from NULL
Which is completely valid, but 'broke' one of the tests.
So to fix it, changed the test to use CHKInventories on both sides, and add an __eq__
member. The nice thing is that CHKInventory.__eq__ is fairly cheap, since it only
has to check the root keys.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008, 2009 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Inventory delta serialisation.
 
18
 
 
19
See doc/developers/inventory.txt for the description of the format.
 
20
 
 
21
In this module the interesting classes are:
 
22
 - InventoryDeltaSerializer - object to read/write inventory deltas.
 
23
"""
 
24
 
 
25
__all__ = ['InventoryDeltaSerializer']
 
26
 
 
27
from bzrlib import errors
 
28
from bzrlib.osutils import basename
 
29
from bzrlib import inventory
 
30
from bzrlib.revision import NULL_REVISION
 
31
 
 
32
 
 
33
def _directory_content(entry):
 
34
    """Serialize the content component of entry which is a directory.
 
35
    
 
36
    :param entry: An InventoryDirectory.
 
37
    """
 
38
    return "dir"
 
39
 
 
40
 
 
41
def _file_content(entry):
 
42
    """Serialize the content component of entry which is a file.
 
43
    
 
44
    :param entry: An InventoryFile.
 
45
    """
 
46
    if entry.executable:
 
47
        exec_bytes = 'Y'
 
48
    else:
 
49
        exec_bytes = ''
 
50
    size_exec_sha = (entry.text_size, exec_bytes, entry.text_sha1)
 
51
    if None in size_exec_sha:
 
52
        raise errors.BzrError('Missing size or sha for %s' % entry.file_id)
 
53
    return "file\x00%d\x00%s\x00%s" % size_exec_sha
 
54
 
 
55
 
 
56
def _link_content(entry):
 
57
    """Serialize the content component of entry which is a symlink.
 
58
    
 
59
    :param entry: An InventoryLink.
 
60
    """
 
61
    target = entry.symlink_target
 
62
    if target is None:
 
63
        raise errors.BzrError('Missing target for %s' % entry.file_id)
 
64
    return "link\x00%s" % target.encode('utf8')
 
65
 
 
66
 
 
67
def _reference_content(entry):
 
68
    """Serialize the content component of entry which is a tree-reference.
 
69
    
 
70
    :param entry: A TreeReference.
 
71
    """
 
72
    tree_revision = entry.reference_revision
 
73
    if tree_revision is None:
 
74
        raise errors.BzrError('Missing reference revision for %s' % entry.file_id)
 
75
    return "tree\x00%s" % tree_revision
 
76
 
 
77
 
 
78
def _dir_to_entry(content, name, parent_id, file_id, last_modified,
 
79
    _type=inventory.InventoryDirectory):
 
80
    """Convert a dir content record to an InventoryDirectory."""
 
81
    result = _type(file_id, name, parent_id)
 
82
    result.revision = last_modified
 
83
    return result
 
84
 
 
85
 
 
86
def _file_to_entry(content, name, parent_id, file_id, last_modified,
 
87
    _type=inventory.InventoryFile):
 
88
    """Convert a dir content record to an InventoryFile."""
 
89
    result = _type(file_id, name, parent_id)
 
90
    result.revision = last_modified
 
91
    result.text_size = int(content[1])
 
92
    result.text_sha1 = content[3]
 
93
    if content[2]:
 
94
        result.executable = True
 
95
    else:
 
96
        result.executable = False
 
97
    return result
 
98
 
 
99
 
 
100
def _link_to_entry(content, name, parent_id, file_id, last_modified,
 
101
    _type=inventory.InventoryLink):
 
102
    """Convert a link content record to an InventoryLink."""
 
103
    result = _type(file_id, name, parent_id)
 
104
    result.revision = last_modified
 
105
    result.symlink_target = content[1].decode('utf8')
 
106
    return result
 
107
 
 
108
 
 
109
def _tree_to_entry(content, name, parent_id, file_id, last_modified,
 
110
    _type=inventory.TreeReference):
 
111
    """Convert a tree content record to a TreeReference."""
 
112
    result = _type(file_id, name, parent_id)
 
113
    result.revision = last_modified
 
114
    result.reference_revision = content[1]
 
115
    return result
 
116
 
 
117
 
 
118
 
 
119
class InventoryDeltaSerializer(object):
 
120
    """Serialize and deserialize inventory deltas."""
 
121
 
 
122
    FORMAT_1 = 'bzr inventory delta v1 (bzr 1.14)'
 
123
 
 
124
    def __init__(self, versioned_root, tree_references):
 
125
        """Create an InventoryDeltaSerializer.
 
126
 
 
127
        :param versioned_root: If True, any root entry that is seen is expected
 
128
            to be versioned, and root entries can have any fileid.
 
129
        :param tree_references: If True support tree-reference entries.
 
130
        """
 
131
        self._versioned_root = versioned_root
 
132
        self._tree_references = tree_references
 
133
        self._entry_to_content = {
 
134
            'directory': _directory_content,
 
135
            'file': _file_content,
 
136
            'symlink': _link_content,
 
137
        }
 
138
        if tree_references:
 
139
            self._entry_to_content['tree-reference'] = _reference_content
 
140
 
 
141
    def delta_to_lines(self, old_name, new_name, delta_to_new):
 
142
        """Return a line sequence for delta_to_new.
 
143
 
 
144
        :param old_name: A UTF8 revision id for the old inventory.  May be
 
145
            NULL_REVISION if there is no older inventory and delta_to_new
 
146
            includes the entire inventory contents.
 
147
        :param new_name: The version name of the inventory we create with this
 
148
            delta.
 
149
        :param delta_to_new: An inventory delta such as Inventory.apply_delta
 
150
            takes.
 
151
        :return: The serialized delta as lines.
 
152
        """
 
153
        lines = ['', '', '', '', '']
 
154
        to_line = self._delta_item_to_line
 
155
        for delta_item in delta_to_new:
 
156
            lines.append(to_line(delta_item))
 
157
            if lines[-1].__class__ != str:
 
158
                raise errors.BzrError(
 
159
                    'to_line generated non-str output %r' % lines[-1])
 
160
        lines.sort()
 
161
        lines[0] = "format: %s\n" % InventoryDeltaSerializer.FORMAT_1
 
162
        lines[1] = "parent: %s\n" % old_name
 
163
        lines[2] = "version: %s\n" % new_name
 
164
        lines[3] = "versioned_root: %s\n" % self._serialize_bool(
 
165
            self._versioned_root)
 
166
        lines[4] = "tree_references: %s\n" % self._serialize_bool(
 
167
            self._tree_references)
 
168
        return lines
 
169
 
 
170
    def _serialize_bool(self, value):
 
171
        if value:
 
172
            return "true"
 
173
        else:
 
174
            return "false"
 
175
 
 
176
    def _delta_item_to_line(self, delta_item):
 
177
        """Convert delta_item to a line."""
 
178
        oldpath, newpath, file_id, entry = delta_item
 
179
        if newpath is None:
 
180
            # delete
 
181
            oldpath_utf8 = '/' + oldpath.encode('utf8')
 
182
            newpath_utf8 = 'None'
 
183
            parent_id = ''
 
184
            last_modified = NULL_REVISION
 
185
            content = 'deleted\x00\x00'
 
186
        else:
 
187
            if oldpath is None:
 
188
                oldpath_utf8 = 'None'
 
189
            else:
 
190
                oldpath_utf8 = '/' + oldpath.encode('utf8')
 
191
            # TODO: Test real-world utf8 cache hit rate. It may be a win.
 
192
            newpath_utf8 = '/' + newpath.encode('utf8')
 
193
            # Serialize None as ''
 
194
            parent_id = entry.parent_id or ''
 
195
            # Serialize unknown revisions as NULL_REVISION
 
196
            last_modified = entry.revision
 
197
            # special cases for /
 
198
            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
 
206
            if last_modified is None:
 
207
                raise errors.BzrError("no version for fileid %s" % file_id)
 
208
            content = self._entry_to_content[entry.kind](entry)
 
209
        return ("%s\x00%s\x00%s\x00%s\x00%s\x00%s\n" %
 
210
            (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
 
211
                content))
 
212
 
 
213
    def _deserialize_bool(self, value):
 
214
        if value == "true":
 
215
            return True
 
216
        elif value == "false":
 
217
            return False
 
218
        else:
 
219
            raise errors.BzrError("value %r is not a bool" % (value,))
 
220
 
 
221
    def parse_text_bytes(self, bytes):
 
222
        """Parse the text bytes of a serialized inventory delta.
 
223
 
 
224
        :param bytes: The bytes to parse. This can be obtained by calling
 
225
            delta_to_lines and then doing ''.join(delta_lines).
 
226
        :return: (parent_id, new_id, inventory_delta)
 
227
        """
 
228
        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])
 
231
        if len(lines) < 2 or not lines[1].startswith('parent: '):
 
232
            raise errors.BzrError('missing parent: marker')
 
233
        delta_parent_id = lines[1][8:]
 
234
        if len(lines) < 3 or not lines[2].startswith('version: '):
 
235
            raise errors.BzrError('missing version: marker')
 
236
        delta_version_id = lines[2][9:]
 
237
        if len(lines) < 4 or not lines[3].startswith('versioned_root: '):
 
238
            raise errors.BzrError('missing versioned_root: marker')
 
239
        delta_versioned_root = self._deserialize_bool(lines[3][16:])
 
240
        if len(lines) < 5 or not lines[4].startswith('tree_references: '):
 
241
            raise errors.BzrError('missing tree_references: marker')
 
242
        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,))
 
251
        result = []
 
252
        seen_ids = set()
 
253
        line_iter = iter(lines)
 
254
        for i in range(5):
 
255
            line_iter.next()
 
256
        for line in line_iter:
 
257
            (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
 
258
                content) = line.split('\x00', 5)
 
259
            parent_id = parent_id or None
 
260
            if file_id in seen_ids:
 
261
                raise errors.BzrError(
 
262
                    "duplicate file id in inventory delta %r" % lines)
 
263
            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)
 
274
            if oldpath_utf8 == 'None':
 
275
                oldpath = None
 
276
            else:
 
277
                oldpath = oldpath_utf8.decode('utf8')
 
278
            if newpath_utf8 == 'None':
 
279
                newpath = None
 
280
            else:
 
281
                newpath = newpath_utf8.decode('utf8')
 
282
            delta_item = (oldpath, newpath, file_id, entry)
 
283
            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):
 
288
    entry_factory = {
 
289
        'dir': _dir_to_entry,
 
290
        'file': _file_to_entry,
 
291
        'link': _link_to_entry,
 
292
        'tree': _tree_to_entry,
 
293
    }
 
294
    kind = content[0]
 
295
    path = utf8_path[1:].decode('utf8')
 
296
    name = basename(path)
 
297
    return entry_factory[content[0]](
 
298
            content, name, parent_id, file_id, last_modified)
 
299