66
54
# TODO: add a 'validate_utf8' for things like revision_id and file_id
67
55
# and a validator for parent-ids
68
_schema = {b'format': (None, int, _is_format_10),
69
b'committer': ('committer', bytes, cache_utf8.decode),
70
b'timezone': ('timezone', int, None),
71
b'timestamp': ('timestamp', bytes, float),
72
b'revision-id': ('revision_id', bytes, None),
73
b'parent-ids': ('parent_ids', list, None),
74
b'inventory-sha1': ('inventory_sha1', bytes, None),
75
b'message': ('message', bytes, cache_utf8.decode),
76
b'properties': ('properties', dict, _validate_properties),
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),
79
67
def write_revision_to_string(self, rev):
80
68
encode_utf8 = cache_utf8._utf8_encode
82
70
# This lets us control the ordering, so that we are able to create
86
(b"committer", encode_utf8(rev.committer)[0]),
74
("committer", encode_utf8(rev.committer)[0]),
88
76
if rev.timezone is not None:
89
ret.append((b"timezone", rev.timezone))
77
ret.append(("timezone", rev.timezone))
90
78
# For bzr revisions, the most common property is just 'branch-nick'
91
79
# which changes infrequently.
93
for key, value in rev.properties.items():
94
revprops[encode_utf8(key)[0]] = encode_utf8(value, 'surrogateescape')[0]
95
ret.append((b'properties', revprops))
81
for key, value in rev.properties.iteritems():
82
revprops[key] = encode_utf8(value)[0]
83
ret.append(('properties', revprops))
97
(b"timestamp", b"%.3f" % rev.timestamp),
98
(b"revision-id", rev.revision_id),
99
(b"parent-ids", rev.parent_ids),
100
(b"inventory-sha1", rev.inventory_sha1),
101
(b"message", encode_utf8(rev.message)[0]),
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]),
103
91
return bencode.bencode(ret)
105
def write_revision_to_lines(self, rev):
106
return self.write_revision_to_string(rev).splitlines(True)
93
def write_revision(self, rev, f):
94
f.write(self.write_revision_to_string(rev))
108
96
def read_revision_from_string(self, text):
109
97
# TODO: consider writing a Revision decoder, rather than using the
143
131
return self.read_revision_from_string(f.read())
146
class CHKSerializer(serializer.Serializer):
134
class CHKSerializerSubtree(BEncodeRevisionSerializer1, xml7.Serializer_v7):
135
"""A CHKInventory based serializer that supports tree references"""
137
supported_kinds = set(['file', 'directory', 'symlink', 'tree-reference'])
139
revision_format_num = None
140
support_altered_by_hack = False
142
def _unpack_entry(self, elt, entry_cache=None, return_from_cache=False):
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,
155
return xml7.Serializer_v7._unpack_entry(self, elt,
156
entry_cache=entry_cache, return_from_cache=return_from_cache)
158
def __init__(self, node_size, search_key_name):
159
self.maximum_size = node_size
160
self.search_key_name = search_key_name
163
class CHKSerializer(xml6.Serializer_v6):
147
164
"""A CHKInventory based serializer with 'plain' behaviour."""
150
167
revision_format_num = None
151
168
support_altered_by_hack = False
152
supported_kinds = {'file', 'directory', 'symlink', 'tree-reference'}
154
170
def __init__(self, node_size, search_key_name):
155
171
self.maximum_size = node_size
156
172
self.search_key_name = search_key_name
158
def _unpack_inventory(self, elt, revision_id=None, entry_cache=None,
159
return_from_cache=False):
160
"""Construct from XML Element"""
161
inv = xml_serializer.unpack_inventory_flat(elt, self.format_num,
162
xml_serializer.unpack_inventory_entry, entry_cache,
166
def read_inventory_from_lines(self, xml_lines, revision_id=None,
167
entry_cache=None, return_from_cache=False):
168
"""Read xml_string into an inventory object.
170
:param xml_string: The xml to read.
171
:param revision_id: If not-None, the expected revision id of the
173
:param entry_cache: An optional cache of InventoryEntry objects. If
174
supplied we will look up entries via (file_id, revision_id) which
175
should map to a valid InventoryEntry (File/Directory/etc) object.
176
:param return_from_cache: Return entries directly from the cache,
177
rather than copying them first. This is only safe if the caller
178
promises not to mutate the returned inventory entries, but it can
179
make some operations significantly faster.
182
return self._unpack_inventory(
183
xml_serializer.fromstringlist(xml_lines), revision_id,
184
entry_cache=entry_cache,
185
return_from_cache=return_from_cache)
186
except xml_serializer.ParseError as e:
187
raise serializer.UnexpectedInventoryFormat(e)
189
def read_inventory(self, f, revision_id=None):
190
"""Read an inventory from a file-like object."""
193
return self._unpack_inventory(self._read_element(f),
197
except xml_serializer.ParseError as e:
198
raise serializer.UnexpectedInventoryFormat(e)
200
def write_inventory_to_lines(self, inv):
201
"""Return a list of lines with the encoded inventory."""
202
return self.write_inventory(inv, None)
204
def write_inventory_to_chunks(self, inv):
205
"""Return a list of lines with the encoded inventory."""
206
return self.write_inventory(inv, None)
208
def write_inventory(self, inv, f, working=False):
209
"""Write inventory to a file.
211
:param inv: the inventory to write.
212
:param f: the file to write. (May be None if the lines are the desired
214
:param working: If True skip history data - text_sha1, text_size,
215
reference_revision, symlink_target.
216
:return: The inventory as a list of lines.
219
append = output.append
220
if inv.revision_id is not None:
223
xml_serializer.encode_and_escape(inv.revision_id), b'"'])
226
append(b'<inventory format="%s"%s>\n' % (
227
self.format_num, revid))
228
append(b'<directory file_id="%s" name="%s" revision="%s" />\n' % (
229
xml_serializer.encode_and_escape(inv.root.file_id),
230
xml_serializer.encode_and_escape(inv.root.name),
231
xml_serializer.encode_and_escape(inv.root.revision)))
232
xml_serializer.serialize_inventory_flat(inv,
234
root_id=None, supported_kinds=self.supported_kinds,
241
chk_serializer_255_bigpage = CHKSerializer(65536, b'hash-255-way')
175
chk_serializer_255_bigpage = CHKSerializer(65536, 'hash-255-way')
244
178
class CHKBEncodeSerializer(BEncodeRevisionSerializer1, CHKSerializer):
245
179
"""A CHKInventory and BEncode based serializer with 'plain' behaviour."""
250
chk_bencode_serializer = CHKBEncodeSerializer(65536, b'hash-255-way')
184
chk_bencode_serializer = CHKBEncodeSerializer(65536, 'hash-255-way')