~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory_delta.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-03-06 06:48:25 UTC
  • mfrom: (4070.8.6 debug-config)
  • Revision ID: pqm@pqm.ubuntu.com-20090306064825-kbpwggw21dygeix6
(mbp) debug_flags configuration option

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