/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 breezy/bzr/chk_serializer.py

  • Committer: Jelmer Vernooij
  • Date: 2020-08-22 22:46:24 UTC
  • mfrom: (7490.40.105 work)
  • mto: This revision was merged to the branch mainline in revision 7521.
  • Revision ID: jelmer@jelmer.uk-20200822224624-om4a4idsr7cn8jew
merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Serializer object for CHK based inventory storage."""
18
18
 
19
 
from bzrlib import (
 
19
from io import (
 
20
    BytesIO,
 
21
    )
 
22
 
 
23
from .. import lazy_import
 
24
lazy_import.lazy_import(globals(),
 
25
                        """
 
26
from breezy.bzr import (
 
27
    serializer,
 
28
    xml_serializer,
 
29
    )
 
30
""")
 
31
from .. import (
20
32
    bencode,
21
33
    cache_utf8,
22
 
    inventory,
 
34
    errors,
23
35
    revision as _mod_revision,
24
 
    xml6,
25
 
    xml7,
 
36
    )
 
37
from . import (
 
38
    serializer,
26
39
    )
27
40
 
28
41
 
29
42
def _validate_properties(props, _decode=cache_utf8._utf8_decode):
30
43
    # TODO: we really want an 'isascii' check for key
31
44
    # Cast the utf8 properties into Unicode 'in place'
32
 
    for key, value in props.iteritems():
33
 
        props[key] = _decode(value)[0]
34
 
    return props
 
45
    return {_decode(key)[0]: _decode(value, 'surrogateescape')[0] for key, value in props.items()}
35
46
 
36
47
 
37
48
def _is_format_10(value):
53
64
    # the type.
54
65
    # TODO: add a 'validate_utf8' for things like revision_id and file_id
55
66
    #       and a validator for parent-ids
56
 
    _schema = {'format': (None, int, _is_format_10),
57
 
               'committer': ('committer', str, cache_utf8.decode),
58
 
               'timezone': ('timezone', int, None),
59
 
               'timestamp': ('timestamp', str, float),
60
 
               'revision-id': ('revision_id', str, None),
61
 
               'parent-ids': ('parent_ids', list, None),
62
 
               'inventory-sha1': ('inventory_sha1', str, None),
63
 
               'message': ('message', str, cache_utf8.decode),
64
 
               'properties': ('properties', dict, _validate_properties),
65
 
    }
 
67
    _schema = {b'format': (None, int, _is_format_10),
 
68
               b'committer': ('committer', bytes, cache_utf8.decode),
 
69
               b'timezone': ('timezone', int, None),
 
70
               b'timestamp': ('timestamp', bytes, float),
 
71
               b'revision-id': ('revision_id', bytes, None),
 
72
               b'parent-ids': ('parent_ids', list, None),
 
73
               b'inventory-sha1': ('inventory_sha1', bytes, None),
 
74
               b'message': ('message', bytes, cache_utf8.decode),
 
75
               b'properties': ('properties', dict, _validate_properties),
 
76
               }
66
77
 
67
78
    def write_revision_to_string(self, rev):
68
79
        encode_utf8 = cache_utf8._utf8_encode
70
81
        # This lets us control the ordering, so that we are able to create
71
82
        # smaller deltas
72
83
        ret = [
73
 
            ("format", 10),
74
 
            ("committer", encode_utf8(rev.committer)[0]),
 
84
            (b"format", 10),
 
85
            (b"committer", encode_utf8(rev.committer)[0]),
75
86
        ]
76
87
        if rev.timezone is not None:
77
 
            ret.append(("timezone", rev.timezone))
 
88
            ret.append((b"timezone", rev.timezone))
78
89
        # For bzr revisions, the most common property is just 'branch-nick'
79
90
        # which changes infrequently.
80
91
        revprops = {}
81
 
        for key, value in rev.properties.iteritems():
82
 
            revprops[key] = encode_utf8(value)[0]
83
 
        ret.append(('properties', revprops))
 
92
        for key, value in rev.properties.items():
 
93
            revprops[encode_utf8(key)[0]] = encode_utf8(value, 'surrogateescape')[0]
 
94
        ret.append((b'properties', revprops))
84
95
        ret.extend([
85
 
            ("timestamp", "%.3f" % rev.timestamp),
86
 
            ("revision-id", rev.revision_id),
87
 
            ("parent-ids", rev.parent_ids),
88
 
            ("inventory-sha1", rev.inventory_sha1),
89
 
            ("message", encode_utf8(rev.message)[0]),
 
96
            (b"timestamp", b"%.3f" % rev.timestamp),
 
97
            (b"revision-id", rev.revision_id),
 
98
            (b"parent-ids", rev.parent_ids),
 
99
            (b"inventory-sha1", rev.inventory_sha1),
 
100
            (b"message", encode_utf8(rev.message)[0]),
90
101
        ])
91
102
        return bencode.bencode(ret)
92
103
 
93
 
    def write_revision(self, rev, f):
94
 
        f.write(self.write_revision_to_string(rev))
 
104
    def write_revision_to_lines(self, rev):
 
105
        return self.write_revision_to_string(rev).splitlines(True)
95
106
 
96
107
    def read_revision_from_string(self, text):
97
108
        # TODO: consider writing a Revision decoder, rather than using the
99
110
        #       However, to decode all 25k revisions of bzr takes approx 1.3s
100
111
        #       If we remove all extra validation that goes down to about 1.2s.
101
112
        #       Of that time, probably 0.6s is spend in bencode.bdecode().
102
 
        #       Regardless 'time bzr log' of everything is 7+s, so 1.3s to
 
113
        #       Regardless 'time brz log' of everything is 7+s, so 1.3s to
103
114
        #       extract revision texts isn't a majority of time.
104
115
        ret = bencode.bdecode(text)
105
116
        if not isinstance(ret, list):
119
130
                value = validator(value)
120
131
            bits[var_name] = value
121
132
        if len(bits) != len(schema):
122
 
            missing = [key for key, (var_name, _, _) in schema.iteritems()
 
133
            missing = [key for key, (var_name, _, _) in schema.items()
123
134
                       if var_name not in bits]
124
135
            raise ValueError('Revision text was missing expected keys %s.'
125
136
                             ' text %r' % (missing, text))
131
142
        return self.read_revision_from_string(f.read())
132
143
 
133
144
 
134
 
class CHKSerializerSubtree(BEncodeRevisionSerializer1, xml7.Serializer_v7):
135
 
    """A CHKInventory based serializer that supports tree references"""
 
145
class CHKSerializer(serializer.Serializer):
 
146
    """A CHKInventory based serializer with 'plain' behaviour."""
136
147
 
137
 
    supported_kinds = set(['file', 'directory', 'symlink', 'tree-reference'])
138
 
    format_num = '9'
 
148
    format_num = b'9'
139
149
    revision_format_num = None
140
150
    support_altered_by_hack = False
141
 
 
142
 
    def _unpack_entry(self, elt, entry_cache=None, return_from_cache=False):
143
 
        kind = elt.tag
144
 
        if not kind in self.supported_kinds:
145
 
            raise AssertionError('unsupported entry kind %s' % kind)
146
 
        if kind == 'tree-reference':
147
 
            file_id = elt.attrib['file_id']
148
 
            name = elt.attrib['name']
149
 
            parent_id = elt.attrib['parent_id']
150
 
            revision = elt.get('revision')
151
 
            reference_revision = elt.get('reference_revision')
152
 
            return inventory.TreeReference(file_id, name, parent_id, revision,
153
 
                                           reference_revision)
 
151
    supported_kinds = {'file', 'directory', 'symlink', 'tree-reference'}
 
152
 
 
153
    def __init__(self, node_size, search_key_name):
 
154
        self.maximum_size = node_size
 
155
        self.search_key_name = search_key_name
 
156
 
 
157
    def _unpack_inventory(self, elt, revision_id=None, entry_cache=None,
 
158
                          return_from_cache=False):
 
159
        """Construct from XML Element"""
 
160
        inv = xml_serializer.unpack_inventory_flat(elt, self.format_num,
 
161
                                                   xml_serializer.unpack_inventory_entry, entry_cache,
 
162
                                                   return_from_cache)
 
163
        return inv
 
164
 
 
165
    def read_inventory_from_lines(self, xml_lines, revision_id=None,
 
166
                                  entry_cache=None, return_from_cache=False):
 
167
        """Read xml_string into an inventory object.
 
168
 
 
169
        :param xml_string: The xml to read.
 
170
        :param revision_id: If not-None, the expected revision id of the
 
171
            inventory.
 
172
        :param entry_cache: An optional cache of InventoryEntry objects. If
 
173
            supplied we will look up entries via (file_id, revision_id) which
 
174
            should map to a valid InventoryEntry (File/Directory/etc) object.
 
175
        :param return_from_cache: Return entries directly from the cache,
 
176
            rather than copying them first. This is only safe if the caller
 
177
            promises not to mutate the returned inventory entries, but it can
 
178
            make some operations significantly faster.
 
179
        """
 
180
        try:
 
181
            return self._unpack_inventory(
 
182
                xml_serializer.fromstringlist(xml_lines), revision_id,
 
183
                entry_cache=entry_cache,
 
184
                return_from_cache=return_from_cache)
 
185
        except xml_serializer.ParseError as e:
 
186
            raise serializer.UnexpectedInventoryFormat(e)
 
187
 
 
188
    def read_inventory(self, f, revision_id=None):
 
189
        """Read an inventory from a file-like object."""
 
190
        try:
 
191
            try:
 
192
                return self._unpack_inventory(self._read_element(f),
 
193
                                              revision_id=None)
 
194
            finally:
 
195
                f.close()
 
196
        except xml_serializer.ParseError as e:
 
197
            raise serializer.UnexpectedInventoryFormat(e)
 
198
 
 
199
    def write_inventory_to_lines(self, inv):
 
200
        """Return a list of lines with the encoded inventory."""
 
201
        return self.write_inventory(inv, None)
 
202
 
 
203
    def write_inventory_to_chunks(self, inv):
 
204
        """Return a list of lines with the encoded inventory."""
 
205
        return self.write_inventory(inv, None)
 
206
 
 
207
    def write_inventory(self, inv, f, working=False):
 
208
        """Write inventory to a file.
 
209
 
 
210
        :param inv: the inventory to write.
 
211
        :param f: the file to write. (May be None if the lines are the desired
 
212
            output).
 
213
        :param working: If True skip history data - text_sha1, text_size,
 
214
            reference_revision, symlink_target.
 
215
        :return: The inventory as a list of lines.
 
216
        """
 
217
        output = []
 
218
        append = output.append
 
219
        if inv.revision_id is not None:
 
220
            revid = b''.join(
 
221
                [b' revision_id="',
 
222
                 xml_serializer.encode_and_escape(inv.revision_id), b'"'])
154
223
        else:
155
 
            return xml7.Serializer_v7._unpack_entry(self, elt,
156
 
                entry_cache=entry_cache, return_from_cache=return_from_cache)
157
 
 
158
 
    def __init__(self, node_size, search_key_name):
159
 
        self.maximum_size = node_size
160
 
        self.search_key_name = search_key_name
161
 
 
162
 
 
163
 
class CHKSerializer(xml6.Serializer_v6):
164
 
    """A CHKInventory based serializer with 'plain' behaviour."""
165
 
 
166
 
    format_num = '9'
167
 
    revision_format_num = None
168
 
    support_altered_by_hack = False
169
 
 
170
 
    def __init__(self, node_size, search_key_name):
171
 
        self.maximum_size = node_size
172
 
        self.search_key_name = search_key_name
173
 
 
174
 
 
175
 
chk_serializer_255_bigpage = CHKSerializer(65536, 'hash-255-way')
 
224
            revid = b""
 
225
        append(b'<inventory format="%s"%s>\n' % (
 
226
            self.format_num, revid))
 
227
        append(b'<directory file_id="%s" name="%s" revision="%s" />\n' % (
 
228
            xml_serializer.encode_and_escape(inv.root.file_id),
 
229
            xml_serializer.encode_and_escape(inv.root.name),
 
230
            xml_serializer.encode_and_escape(inv.root.revision)))
 
231
        xml_serializer.serialize_inventory_flat(inv,
 
232
                                                append,
 
233
                                                root_id=None, supported_kinds=self.supported_kinds,
 
234
                                                working=working)
 
235
        if f is not None:
 
236
            f.writelines(output)
 
237
        return output
 
238
 
 
239
 
 
240
chk_serializer_255_bigpage = CHKSerializer(65536, b'hash-255-way')
176
241
 
177
242
 
178
243
class CHKBEncodeSerializer(BEncodeRevisionSerializer1, CHKSerializer):
179
244
    """A CHKInventory and BEncode based serializer with 'plain' behaviour."""
180
245
 
181
 
    format_num = '10'
182
 
 
183
 
 
184
 
chk_bencode_serializer = CHKBEncodeSerializer(65536, 'hash-255-way')
 
246
    format_num = b'10'
 
247
 
 
248
 
 
249
chk_bencode_serializer = CHKBEncodeSerializer(65536, b'hash-255-way')