##// END OF EJS Templates
revlog: move the details of revlog "v1" index inside revlog.utils.constants...
marmoute -
r47616:cc65cea9 default
parent child Browse files
Show More
@@ -1,364 +1,365 b''
1 1 # parsers.py - Python implementation of parsers.c
2 2 #
3 3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import struct
11 11 import zlib
12 12
13 13 from ..node import nullid, nullrev
14 14 from .. import (
15 15 pycompat,
16 16 util,
17 17 )
18 18
19 19 from ..revlogutils import nodemap as nodemaputil
20 from ..revlogutils import constants as revlog_constants
20 21
21 22 stringio = pycompat.bytesio
22 23
23 24
24 25 _pack = struct.pack
25 26 _unpack = struct.unpack
26 27 _compress = zlib.compress
27 28 _decompress = zlib.decompress
28 29
29 30 # Some code below makes tuples directly because it's more convenient. However,
30 31 # code outside this module should always use dirstatetuple.
31 32 def dirstatetuple(*x):
32 33 # x is a tuple
33 34 return x
34 35
35 36
36 37 def gettype(q):
37 38 return int(q & 0xFFFF)
38 39
39 40
40 41 def offset_type(offset, type):
41 42 return int(int(offset) << 16 | type)
42 43
43 44
44 45 class BaseIndexObject(object):
45 46 # Format of an index entry according to Python's `struct` language
46 index_format = b">Qiiiiii20s12x"
47 index_format = revlog_constants.INDEX_ENTRY_V1.format
47 48 # Size of a C unsigned long long int, platform independent
48 49 big_int_size = struct.calcsize(b'>Q')
49 50 # Size of a C long int, platform independent
50 51 int_size = struct.calcsize(b'>i')
51 52 # Size of the entire index format
52 index_size = struct.calcsize(index_format)
53 index_size = revlog_constants.INDEX_ENTRY_V1.size
53 54 # An empty index entry, used as a default value to be overridden, or nullrev
54 55 null_item = (0, 0, 0, -1, -1, -1, -1, nullid)
55 56
56 57 @property
57 58 def nodemap(self):
58 59 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
59 60 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
60 61 return self._nodemap
61 62
62 63 @util.propertycache
63 64 def _nodemap(self):
64 65 nodemap = nodemaputil.NodeMap({nullid: nullrev})
65 66 for r in range(0, len(self)):
66 67 n = self[r][7]
67 68 nodemap[n] = r
68 69 return nodemap
69 70
70 71 def has_node(self, node):
71 72 """return True if the node exist in the index"""
72 73 return node in self._nodemap
73 74
74 75 def rev(self, node):
75 76 """return a revision for a node
76 77
77 78 If the node is unknown, raise a RevlogError"""
78 79 return self._nodemap[node]
79 80
80 81 def get_rev(self, node):
81 82 """return a revision for a node
82 83
83 84 If the node is unknown, return None"""
84 85 return self._nodemap.get(node)
85 86
86 87 def _stripnodes(self, start):
87 88 if '_nodemap' in vars(self):
88 89 for r in range(start, len(self)):
89 90 n = self[r][7]
90 91 del self._nodemap[n]
91 92
92 93 def clearcaches(self):
93 94 self.__dict__.pop('_nodemap', None)
94 95
95 96 def __len__(self):
96 97 return self._lgt + len(self._extra)
97 98
98 99 def append(self, tup):
99 100 if '_nodemap' in vars(self):
100 101 self._nodemap[tup[7]] = len(self)
101 102 data = _pack(self.index_format, *tup)
102 103 self._extra.append(data)
103 104
104 105 def _check_index(self, i):
105 106 if not isinstance(i, int):
106 107 raise TypeError(b"expecting int indexes")
107 108 if i < 0 or i >= len(self):
108 109 raise IndexError
109 110
110 111 def __getitem__(self, i):
111 112 if i == -1:
112 113 return self.null_item
113 114 self._check_index(i)
114 115 if i >= self._lgt:
115 116 data = self._extra[i - self._lgt]
116 117 else:
117 118 index = self._calculate_index(i)
118 119 data = self._data[index : index + self.index_size]
119 120 r = _unpack(self.index_format, data)
120 121 if self._lgt and i == 0:
121 122 r = (offset_type(0, gettype(r[0])),) + r[1:]
122 123 return r
123 124
124 125
125 126 class IndexObject(BaseIndexObject):
126 127 def __init__(self, data):
127 128 assert len(data) % self.index_size == 0
128 129 self._data = data
129 130 self._lgt = len(data) // self.index_size
130 131 self._extra = []
131 132
132 133 def _calculate_index(self, i):
133 134 return i * self.index_size
134 135
135 136 def __delitem__(self, i):
136 137 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
137 138 raise ValueError(b"deleting slices only supports a:-1 with step 1")
138 139 i = i.start
139 140 self._check_index(i)
140 141 self._stripnodes(i)
141 142 if i < self._lgt:
142 143 self._data = self._data[: i * self.index_size]
143 144 self._lgt = i
144 145 self._extra = []
145 146 else:
146 147 self._extra = self._extra[: i - self._lgt]
147 148
148 149
149 150 class PersistentNodeMapIndexObject(IndexObject):
150 151 """a Debug oriented class to test persistent nodemap
151 152
152 153 We need a simple python object to test API and higher level behavior. See
153 154 the Rust implementation for more serious usage. This should be used only
154 155 through the dedicated `devel.persistent-nodemap` config.
155 156 """
156 157
157 158 def nodemap_data_all(self):
158 159 """Return bytes containing a full serialization of a nodemap
159 160
160 161 The nodemap should be valid for the full set of revisions in the
161 162 index."""
162 163 return nodemaputil.persistent_data(self)
163 164
164 165 def nodemap_data_incremental(self):
165 166 """Return bytes containing a incremental update to persistent nodemap
166 167
167 168 This containst the data for an append-only update of the data provided
168 169 in the last call to `update_nodemap_data`.
169 170 """
170 171 if self._nm_root is None:
171 172 return None
172 173 docket = self._nm_docket
173 174 changed, data = nodemaputil.update_persistent_data(
174 175 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
175 176 )
176 177
177 178 self._nm_root = self._nm_max_idx = self._nm_docket = None
178 179 return docket, changed, data
179 180
180 181 def update_nodemap_data(self, docket, nm_data):
181 182 """provide full block of persisted binary data for a nodemap
182 183
183 184 The data are expected to come from disk. See `nodemap_data_all` for a
184 185 produceur of such data."""
185 186 if nm_data is not None:
186 187 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
187 188 if self._nm_root:
188 189 self._nm_docket = docket
189 190 else:
190 191 self._nm_root = self._nm_max_idx = self._nm_docket = None
191 192
192 193
193 194 class InlinedIndexObject(BaseIndexObject):
194 195 def __init__(self, data, inline=0):
195 196 self._data = data
196 197 self._lgt = self._inline_scan(None)
197 198 self._inline_scan(self._lgt)
198 199 self._extra = []
199 200
200 201 def _inline_scan(self, lgt):
201 202 off = 0
202 203 if lgt is not None:
203 204 self._offsets = [0] * lgt
204 205 count = 0
205 206 while off <= len(self._data) - self.index_size:
206 207 start = off + self.big_int_size
207 208 (s,) = struct.unpack(
208 209 b'>i',
209 210 self._data[start : start + self.int_size],
210 211 )
211 212 if lgt is not None:
212 213 self._offsets[count] = off
213 214 count += 1
214 215 off += self.index_size + s
215 216 if off != len(self._data):
216 217 raise ValueError(b"corrupted data")
217 218 return count
218 219
219 220 def __delitem__(self, i):
220 221 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
221 222 raise ValueError(b"deleting slices only supports a:-1 with step 1")
222 223 i = i.start
223 224 self._check_index(i)
224 225 self._stripnodes(i)
225 226 if i < self._lgt:
226 227 self._offsets = self._offsets[:i]
227 228 self._lgt = i
228 229 self._extra = []
229 230 else:
230 231 self._extra = self._extra[: i - self._lgt]
231 232
232 233 def _calculate_index(self, i):
233 234 return self._offsets[i]
234 235
235 236
236 237 def parse_index2(data, inline, revlogv2=False):
237 238 if not inline:
238 239 cls = IndexObject2 if revlogv2 else IndexObject
239 240 return cls(data), None
240 241 cls = InlinedIndexObject2 if revlogv2 else InlinedIndexObject
241 242 return cls(data, inline), (0, data)
242 243
243 244
244 245 class Index2Mixin(object):
245 246 # 6 bytes: offset
246 247 # 2 bytes: flags
247 248 # 4 bytes: compressed length
248 249 # 4 bytes: uncompressed length
249 250 # 4 bytes: base rev
250 251 # 4 bytes: link rev
251 252 # 4 bytes: parent 1 rev
252 253 # 4 bytes: parent 2 rev
253 254 # 32 bytes: nodeid
254 255 # 8 bytes: sidedata offset
255 256 # 4 bytes: sidedata compressed length
256 257 # 20 bytes: Padding to align to 96 bytes (see RevlogV2Plan wiki page)
257 258 index_format = b">Qiiiiii20s12xQi20x"
258 259 index_size = struct.calcsize(index_format)
259 260 assert index_size == 96, index_size
260 261 null_item = (0, 0, 0, -1, -1, -1, -1, nullid, 0, 0)
261 262
262 263 def replace_sidedata_info(self, i, sidedata_offset, sidedata_length):
263 264 """
264 265 Replace an existing index entry's sidedata offset and length with new
265 266 ones.
266 267 This cannot be used outside of the context of sidedata rewriting,
267 268 inside the transaction that creates the revision `i`.
268 269 """
269 270 if i < 0:
270 271 raise KeyError
271 272 self._check_index(i)
272 273 sidedata_format = b">Qi"
273 274 packed_size = struct.calcsize(sidedata_format)
274 275 if i >= self._lgt:
275 276 packed = _pack(sidedata_format, sidedata_offset, sidedata_length)
276 277 old = self._extra[i - self._lgt]
277 278 new = old[:64] + packed + old[64 + packed_size :]
278 279 self._extra[i - self._lgt] = new
279 280 else:
280 281 msg = b"cannot rewrite entries outside of this transaction"
281 282 raise KeyError(msg)
282 283
283 284
284 285 class IndexObject2(Index2Mixin, IndexObject):
285 286 pass
286 287
287 288
288 289 class InlinedIndexObject2(Index2Mixin, InlinedIndexObject):
289 290 def _inline_scan(self, lgt):
290 291 sidedata_length_pos = 72
291 292 off = 0
292 293 if lgt is not None:
293 294 self._offsets = [0] * lgt
294 295 count = 0
295 296 while off <= len(self._data) - self.index_size:
296 297 start = off + self.big_int_size
297 298 (data_size,) = struct.unpack(
298 299 b'>i',
299 300 self._data[start : start + self.int_size],
300 301 )
301 302 start = off + sidedata_length_pos
302 303 (side_data_size,) = struct.unpack(
303 304 b'>i', self._data[start : start + self.int_size]
304 305 )
305 306 if lgt is not None:
306 307 self._offsets[count] = off
307 308 count += 1
308 309 off += self.index_size + data_size + side_data_size
309 310 if off != len(self._data):
310 311 raise ValueError(b"corrupted data")
311 312 return count
312 313
313 314
314 315 def parse_index_devel_nodemap(data, inline):
315 316 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
316 317 return PersistentNodeMapIndexObject(data), None
317 318
318 319
319 320 def parse_dirstate(dmap, copymap, st):
320 321 parents = [st[:20], st[20:40]]
321 322 # dereference fields so they will be local in loop
322 323 format = b">cllll"
323 324 e_size = struct.calcsize(format)
324 325 pos1 = 40
325 326 l = len(st)
326 327
327 328 # the inner loop
328 329 while pos1 < l:
329 330 pos2 = pos1 + e_size
330 331 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
331 332 pos1 = pos2 + e[4]
332 333 f = st[pos2:pos1]
333 334 if b'\0' in f:
334 335 f, c = f.split(b'\0')
335 336 copymap[f] = c
336 337 dmap[f] = e[:4]
337 338 return parents
338 339
339 340
340 341 def pack_dirstate(dmap, copymap, pl, now):
341 342 now = int(now)
342 343 cs = stringio()
343 344 write = cs.write
344 345 write(b"".join(pl))
345 346 for f, e in pycompat.iteritems(dmap):
346 347 if e[0] == b'n' and e[3] == now:
347 348 # The file was last modified "simultaneously" with the current
348 349 # write to dirstate (i.e. within the same second for file-
349 350 # systems with a granularity of 1 sec). This commonly happens
350 351 # for at least a couple of files on 'update'.
351 352 # The user could change the file without changing its size
352 353 # within the same second. Invalidate the file's mtime in
353 354 # dirstate, forcing future 'status' calls to compare the
354 355 # contents of the file if the size is the same. This prevents
355 356 # mistakenly treating such files as clean.
356 357 e = dirstatetuple(e[0], e[1], e[2], -1)
357 358 dmap[f] = e
358 359
359 360 if f in copymap:
360 361 f = b"%s\0%s" % (f, copymap[f])
361 362 e = _pack(b">cllll", e[0], e[1], e[2], e[3], len(f))
362 363 write(e)
363 364 write(f)
364 365 return cs.getvalue()
@@ -1,3267 +1,3256 b''
1 1 # revlog.py - storage back-end for mercurial
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """Storage back-end for Mercurial.
9 9
10 10 This provides efficient delta storage with O(1) retrieve and append
11 11 and O(changes) merge between branches.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 import binascii
17 17 import collections
18 18 import contextlib
19 19 import errno
20 20 import io
21 21 import os
22 22 import struct
23 23 import zlib
24 24
25 25 # import stuff from node for others to import from revlog
26 26 from .node import (
27 27 bin,
28 28 hex,
29 29 nullhex,
30 30 nullid,
31 31 nullrev,
32 32 sha1nodeconstants,
33 33 short,
34 34 wdirfilenodeids,
35 35 wdirhex,
36 36 wdirid,
37 37 wdirrev,
38 38 )
39 39 from .i18n import _
40 40 from .pycompat import getattr
41 41 from .revlogutils.constants import (
42 42 FLAG_GENERALDELTA,
43 43 FLAG_INLINE_DATA,
44 44 INDEX_ENTRY_V0,
45 INDEX_ENTRY_V1,
45 46 REVLOGV0,
46 47 REVLOGV1,
47 48 REVLOGV1_FLAGS,
48 49 REVLOGV2,
49 50 REVLOGV2_FLAGS,
50 51 REVLOG_DEFAULT_FLAGS,
51 52 REVLOG_DEFAULT_FORMAT,
52 53 REVLOG_DEFAULT_VERSION,
53 54 )
54 55 from .revlogutils.flagutil import (
55 56 REVIDX_DEFAULT_FLAGS,
56 57 REVIDX_ELLIPSIS,
57 58 REVIDX_EXTSTORED,
58 59 REVIDX_FLAGS_ORDER,
59 60 REVIDX_HASCOPIESINFO,
60 61 REVIDX_ISCENSORED,
61 62 REVIDX_RAWTEXT_CHANGING_FLAGS,
62 63 REVIDX_SIDEDATA,
63 64 )
64 65 from .thirdparty import attr
65 66 from . import (
66 67 ancestor,
67 68 dagop,
68 69 error,
69 70 mdiff,
70 71 policy,
71 72 pycompat,
72 73 templatefilters,
73 74 util,
74 75 )
75 76 from .interfaces import (
76 77 repository,
77 78 util as interfaceutil,
78 79 )
79 80 from .revlogutils import (
80 81 deltas as deltautil,
81 82 flagutil,
82 83 nodemap as nodemaputil,
83 84 sidedata as sidedatautil,
84 85 )
85 86 from .utils import (
86 87 storageutil,
87 88 stringutil,
88 89 )
89 90 from .pure import parsers as pureparsers
90 91
91 92 # blanked usage of all the name to prevent pyflakes constraints
92 93 # We need these name available in the module for extensions.
93 94 REVLOGV0
94 95 REVLOGV1
95 96 REVLOGV2
96 97 FLAG_INLINE_DATA
97 98 FLAG_GENERALDELTA
98 99 REVLOG_DEFAULT_FLAGS
99 100 REVLOG_DEFAULT_FORMAT
100 101 REVLOG_DEFAULT_VERSION
101 102 REVLOGV1_FLAGS
102 103 REVLOGV2_FLAGS
103 104 REVIDX_ISCENSORED
104 105 REVIDX_ELLIPSIS
105 106 REVIDX_SIDEDATA
106 107 REVIDX_HASCOPIESINFO
107 108 REVIDX_EXTSTORED
108 109 REVIDX_DEFAULT_FLAGS
109 110 REVIDX_FLAGS_ORDER
110 111 REVIDX_RAWTEXT_CHANGING_FLAGS
111 112
112 113 parsers = policy.importmod('parsers')
113 114 rustancestor = policy.importrust('ancestor')
114 115 rustdagop = policy.importrust('dagop')
115 116 rustrevlog = policy.importrust('revlog')
116 117
117 118 # Aliased for performance.
118 119 _zlibdecompress = zlib.decompress
119 120
120 121 # max size of revlog with inline data
121 122 _maxinline = 131072
122 123 _chunksize = 1048576
123 124
124 125 # Flag processors for REVIDX_ELLIPSIS.
125 126 def ellipsisreadprocessor(rl, text):
126 127 return text, False
127 128
128 129
129 130 def ellipsiswriteprocessor(rl, text):
130 131 return text, False
131 132
132 133
133 134 def ellipsisrawprocessor(rl, text):
134 135 return False
135 136
136 137
137 138 ellipsisprocessor = (
138 139 ellipsisreadprocessor,
139 140 ellipsiswriteprocessor,
140 141 ellipsisrawprocessor,
141 142 )
142 143
143 144
144 145 def getoffset(q):
145 146 return int(q >> 16)
146 147
147 148
148 149 def gettype(q):
149 150 return int(q & 0xFFFF)
150 151
151 152
152 153 def offset_type(offset, type):
153 154 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
154 155 raise ValueError(b'unknown revlog index flags')
155 156 return int(int(offset) << 16 | type)
156 157
157 158
158 159 def _verify_revision(rl, skipflags, state, node):
159 160 """Verify the integrity of the given revlog ``node`` while providing a hook
160 161 point for extensions to influence the operation."""
161 162 if skipflags:
162 163 state[b'skipread'].add(node)
163 164 else:
164 165 # Side-effect: read content and verify hash.
165 166 rl.revision(node)
166 167
167 168
168 169 # True if a fast implementation for persistent-nodemap is available
169 170 #
170 171 # We also consider we have a "fast" implementation in "pure" python because
171 172 # people using pure don't really have performance consideration (and a
172 173 # wheelbarrow of other slowness source)
173 174 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
174 175 parsers, 'BaseIndexObject'
175 176 )
176 177
177 178
178 179 @attr.s(slots=True, frozen=True)
179 180 class _revisioninfo(object):
180 181 """Information about a revision that allows building its fulltext
181 182 node: expected hash of the revision
182 183 p1, p2: parent revs of the revision
183 184 btext: built text cache consisting of a one-element list
184 185 cachedelta: (baserev, uncompressed_delta) or None
185 186 flags: flags associated to the revision storage
186 187
187 188 One of btext[0] or cachedelta must be set.
188 189 """
189 190
190 191 node = attr.ib()
191 192 p1 = attr.ib()
192 193 p2 = attr.ib()
193 194 btext = attr.ib()
194 195 textlen = attr.ib()
195 196 cachedelta = attr.ib()
196 197 flags = attr.ib()
197 198
198 199
199 200 @interfaceutil.implementer(repository.irevisiondelta)
200 201 @attr.s(slots=True)
201 202 class revlogrevisiondelta(object):
202 203 node = attr.ib()
203 204 p1node = attr.ib()
204 205 p2node = attr.ib()
205 206 basenode = attr.ib()
206 207 flags = attr.ib()
207 208 baserevisionsize = attr.ib()
208 209 revision = attr.ib()
209 210 delta = attr.ib()
210 211 sidedata = attr.ib()
211 212 linknode = attr.ib(default=None)
212 213
213 214
214 215 @interfaceutil.implementer(repository.iverifyproblem)
215 216 @attr.s(frozen=True)
216 217 class revlogproblem(object):
217 218 warning = attr.ib(default=None)
218 219 error = attr.ib(default=None)
219 220 node = attr.ib(default=None)
220 221
221 222
222 223 class revlogoldindex(list):
223 224 @property
224 225 def nodemap(self):
225 226 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
226 227 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
227 228 return self._nodemap
228 229
229 230 @util.propertycache
230 231 def _nodemap(self):
231 232 nodemap = nodemaputil.NodeMap({nullid: nullrev})
232 233 for r in range(0, len(self)):
233 234 n = self[r][7]
234 235 nodemap[n] = r
235 236 return nodemap
236 237
237 238 def has_node(self, node):
238 239 """return True if the node exist in the index"""
239 240 return node in self._nodemap
240 241
241 242 def rev(self, node):
242 243 """return a revision for a node
243 244
244 245 If the node is unknown, raise a RevlogError"""
245 246 return self._nodemap[node]
246 247
247 248 def get_rev(self, node):
248 249 """return a revision for a node
249 250
250 251 If the node is unknown, return None"""
251 252 return self._nodemap.get(node)
252 253
253 254 def append(self, tup):
254 255 self._nodemap[tup[7]] = len(self)
255 256 super(revlogoldindex, self).append(tup)
256 257
257 258 def __delitem__(self, i):
258 259 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
259 260 raise ValueError(b"deleting slices only supports a:-1 with step 1")
260 261 for r in pycompat.xrange(i.start, len(self)):
261 262 del self._nodemap[self[r][7]]
262 263 super(revlogoldindex, self).__delitem__(i)
263 264
264 265 def clearcaches(self):
265 266 self.__dict__.pop('_nodemap', None)
266 267
267 268 def __getitem__(self, i):
268 269 if i == -1:
269 270 return (0, 0, 0, -1, -1, -1, -1, nullid)
270 271 return list.__getitem__(self, i)
271 272
272 273
273 274 class revlogoldio(object):
274 275 def __init__(self):
275 276 self.size = INDEX_ENTRY_V0.size
276 277
277 278 def parseindex(self, data, inline):
278 279 s = self.size
279 280 index = []
280 281 nodemap = nodemaputil.NodeMap({nullid: nullrev})
281 282 n = off = 0
282 283 l = len(data)
283 284 while off + s <= l:
284 285 cur = data[off : off + s]
285 286 off += s
286 287 e = INDEX_ENTRY_V0.unpack(cur)
287 288 # transform to revlogv1 format
288 289 e2 = (
289 290 offset_type(e[0], 0),
290 291 e[1],
291 292 -1,
292 293 e[2],
293 294 e[3],
294 295 nodemap.get(e[4], nullrev),
295 296 nodemap.get(e[5], nullrev),
296 297 e[6],
297 298 )
298 299 index.append(e2)
299 300 nodemap[e[6]] = n
300 301 n += 1
301 302
302 303 index = revlogoldindex(index)
303 304 return index, None
304 305
305 306 def packentry(self, entry, node, version, rev):
306 307 """return the binary representation of an entry
307 308
308 309 entry: a tuple containing all the values (see index.__getitem__)
309 310 node: a callback to convert a revision to nodeid
310 311 version: the changelog version
311 312 rev: the revision number
312 313 """
313 314 if gettype(entry[0]):
314 315 raise error.RevlogError(
315 316 _(b'index entry flags need revlog version 1')
316 317 )
317 318 e2 = (
318 319 getoffset(entry[0]),
319 320 entry[1],
320 321 entry[3],
321 322 entry[4],
322 323 node(entry[5]),
323 324 node(entry[6]),
324 325 entry[7],
325 326 )
326 327 return INDEX_ENTRY_V0.pack(*e2)
327 328
328 329
329 # index ng:
330 # 6 bytes: offset
331 # 2 bytes: flags
332 # 4 bytes: compressed length
333 # 4 bytes: uncompressed length
334 # 4 bytes: base rev
335 # 4 bytes: link rev
336 # 4 bytes: parent 1 rev
337 # 4 bytes: parent 2 rev
338 # 32 bytes: nodeid
339 indexformatng = struct.Struct(b">Qiiiiii20s12x")
340 indexformatng_pack = indexformatng.pack
341 330 versionformat = struct.Struct(b">I")
342 331 versionformat_pack = versionformat.pack
343 332 versionformat_unpack = versionformat.unpack
344 333
345 334 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
346 335 # signed integer)
347 336 _maxentrysize = 0x7FFFFFFF
348 337
349 338
350 339 class revlogio(object):
351 340 def __init__(self):
352 self.size = indexformatng.size
341 self.size = INDEX_ENTRY_V1.size
353 342
354 343 def parseindex(self, data, inline):
355 344 # call the C implementation to parse the index data
356 345 index, cache = parsers.parse_index2(data, inline)
357 346 return index, cache
358 347
359 348 def packentry(self, entry, node, version, rev):
360 p = indexformatng_pack(*entry)
349 p = INDEX_ENTRY_V1.pack(*entry)
361 350 if rev == 0:
362 351 p = versionformat_pack(version) + p[4:]
363 352 return p
364 353
365 354
366 355 indexformatv2 = struct.Struct(pureparsers.Index2Mixin.index_format)
367 356 indexformatv2_pack = indexformatv2.pack
368 357
369 358
370 359 class revlogv2io(object):
371 360 def __init__(self):
372 361 self.size = indexformatv2.size
373 362
374 363 def parseindex(self, data, inline):
375 364 index, cache = parsers.parse_index2(data, inline, revlogv2=True)
376 365 return index, cache
377 366
378 367 def packentry(self, entry, node, version, rev):
379 368 p = indexformatv2_pack(*entry)
380 369 if rev == 0:
381 370 p = versionformat_pack(version) + p[4:]
382 371 return p
383 372
384 373
385 374 NodemapRevlogIO = None
386 375
387 376 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
388 377
389 378 class NodemapRevlogIO(revlogio):
390 379 """A debug oriented IO class that return a PersistentNodeMapIndexObject
391 380
392 381 The PersistentNodeMapIndexObject object is meant to test the persistent nodemap feature.
393 382 """
394 383
395 384 def parseindex(self, data, inline):
396 385 index, cache = parsers.parse_index_devel_nodemap(data, inline)
397 386 return index, cache
398 387
399 388
400 389 class rustrevlogio(revlogio):
401 390 def parseindex(self, data, inline):
402 391 index, cache = super(rustrevlogio, self).parseindex(data, inline)
403 392 return rustrevlog.MixedIndex(index), cache
404 393
405 394
406 395 class revlog(object):
407 396 """
408 397 the underlying revision storage object
409 398
410 399 A revlog consists of two parts, an index and the revision data.
411 400
412 401 The index is a file with a fixed record size containing
413 402 information on each revision, including its nodeid (hash), the
414 403 nodeids of its parents, the position and offset of its data within
415 404 the data file, and the revision it's based on. Finally, each entry
416 405 contains a linkrev entry that can serve as a pointer to external
417 406 data.
418 407
419 408 The revision data itself is a linear collection of data chunks.
420 409 Each chunk represents a revision and is usually represented as a
421 410 delta against the previous chunk. To bound lookup time, runs of
422 411 deltas are limited to about 2 times the length of the original
423 412 version data. This makes retrieval of a version proportional to
424 413 its size, or O(1) relative to the number of revisions.
425 414
426 415 Both pieces of the revlog are written to in an append-only
427 416 fashion, which means we never need to rewrite a file to insert or
428 417 remove data, and can use some simple techniques to avoid the need
429 418 for locking while reading.
430 419
431 420 If checkambig, indexfile is opened with checkambig=True at
432 421 writing, to avoid file stat ambiguity.
433 422
434 423 If mmaplargeindex is True, and an mmapindexthreshold is set, the
435 424 index will be mmapped rather than read if it is larger than the
436 425 configured threshold.
437 426
438 427 If censorable is True, the revlog can have censored revisions.
439 428
440 429 If `upperboundcomp` is not None, this is the expected maximal gain from
441 430 compression for the data content.
442 431
443 432 `concurrencychecker` is an optional function that receives 3 arguments: a
444 433 file handle, a filename, and an expected position. It should check whether
445 434 the current position in the file handle is valid, and log/warn/fail (by
446 435 raising).
447 436 """
448 437
449 438 _flagserrorclass = error.RevlogError
450 439
451 440 def __init__(
452 441 self,
453 442 opener,
454 443 indexfile,
455 444 datafile=None,
456 445 checkambig=False,
457 446 mmaplargeindex=False,
458 447 censorable=False,
459 448 upperboundcomp=None,
460 449 persistentnodemap=False,
461 450 concurrencychecker=None,
462 451 ):
463 452 """
464 453 create a revlog object
465 454
466 455 opener is a function that abstracts the file opening operation
467 456 and can be used to implement COW semantics or the like.
468 457
469 458 """
470 459 self.upperboundcomp = upperboundcomp
471 460 self.indexfile = indexfile
472 461 self.datafile = datafile or (indexfile[:-2] + b".d")
473 462 self.nodemap_file = None
474 463 if persistentnodemap:
475 464 self.nodemap_file = nodemaputil.get_nodemap_file(
476 465 opener, self.indexfile
477 466 )
478 467
479 468 self.opener = opener
480 469 # When True, indexfile is opened with checkambig=True at writing, to
481 470 # avoid file stat ambiguity.
482 471 self._checkambig = checkambig
483 472 self._mmaplargeindex = mmaplargeindex
484 473 self._censorable = censorable
485 474 # 3-tuple of (node, rev, text) for a raw revision.
486 475 self._revisioncache = None
487 476 # Maps rev to chain base rev.
488 477 self._chainbasecache = util.lrucachedict(100)
489 478 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
490 479 self._chunkcache = (0, b'')
491 480 # How much data to read and cache into the raw revlog data cache.
492 481 self._chunkcachesize = 65536
493 482 self._maxchainlen = None
494 483 self._deltabothparents = True
495 484 self.index = None
496 485 self._nodemap_docket = None
497 486 # Mapping of partial identifiers to full nodes.
498 487 self._pcache = {}
499 488 # Mapping of revision integer to full node.
500 489 self._compengine = b'zlib'
501 490 self._compengineopts = {}
502 491 self._maxdeltachainspan = -1
503 492 self._withsparseread = False
504 493 self._sparserevlog = False
505 494 self._srdensitythreshold = 0.50
506 495 self._srmingapsize = 262144
507 496
508 497 # Make copy of flag processors so each revlog instance can support
509 498 # custom flags.
510 499 self._flagprocessors = dict(flagutil.flagprocessors)
511 500
512 501 # 2-tuple of file handles being used for active writing.
513 502 self._writinghandles = None
514 503
515 504 self._loadindex()
516 505
517 506 self._concurrencychecker = concurrencychecker
518 507
519 508 def _loadindex(self):
520 509 mmapindexthreshold = None
521 510 opts = self.opener.options
522 511
523 512 if b'revlogv2' in opts:
524 513 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
525 514 elif b'revlogv1' in opts:
526 515 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
527 516 if b'generaldelta' in opts:
528 517 newversionflags |= FLAG_GENERALDELTA
529 518 elif b'revlogv0' in self.opener.options:
530 519 newversionflags = REVLOGV0
531 520 else:
532 521 newversionflags = REVLOG_DEFAULT_VERSION
533 522
534 523 if b'chunkcachesize' in opts:
535 524 self._chunkcachesize = opts[b'chunkcachesize']
536 525 if b'maxchainlen' in opts:
537 526 self._maxchainlen = opts[b'maxchainlen']
538 527 if b'deltabothparents' in opts:
539 528 self._deltabothparents = opts[b'deltabothparents']
540 529 self._lazydelta = bool(opts.get(b'lazydelta', True))
541 530 self._lazydeltabase = False
542 531 if self._lazydelta:
543 532 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
544 533 if b'compengine' in opts:
545 534 self._compengine = opts[b'compengine']
546 535 if b'zlib.level' in opts:
547 536 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
548 537 if b'zstd.level' in opts:
549 538 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
550 539 if b'maxdeltachainspan' in opts:
551 540 self._maxdeltachainspan = opts[b'maxdeltachainspan']
552 541 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
553 542 mmapindexthreshold = opts[b'mmapindexthreshold']
554 543 self.hassidedata = bool(opts.get(b'side-data', False))
555 544 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
556 545 withsparseread = bool(opts.get(b'with-sparse-read', False))
557 546 # sparse-revlog forces sparse-read
558 547 self._withsparseread = self._sparserevlog or withsparseread
559 548 if b'sparse-read-density-threshold' in opts:
560 549 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
561 550 if b'sparse-read-min-gap-size' in opts:
562 551 self._srmingapsize = opts[b'sparse-read-min-gap-size']
563 552 if opts.get(b'enableellipsis'):
564 553 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
565 554
566 555 # revlog v0 doesn't have flag processors
567 556 for flag, processor in pycompat.iteritems(
568 557 opts.get(b'flagprocessors', {})
569 558 ):
570 559 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
571 560
572 561 if self._chunkcachesize <= 0:
573 562 raise error.RevlogError(
574 563 _(b'revlog chunk cache size %r is not greater than 0')
575 564 % self._chunkcachesize
576 565 )
577 566 elif self._chunkcachesize & (self._chunkcachesize - 1):
578 567 raise error.RevlogError(
579 568 _(b'revlog chunk cache size %r is not a power of 2')
580 569 % self._chunkcachesize
581 570 )
582 571
583 572 indexdata = b''
584 573 self._initempty = True
585 574 try:
586 575 with self._indexfp() as f:
587 576 if (
588 577 mmapindexthreshold is not None
589 578 and self.opener.fstat(f).st_size >= mmapindexthreshold
590 579 ):
591 580 # TODO: should .close() to release resources without
592 581 # relying on Python GC
593 582 indexdata = util.buffer(util.mmapread(f))
594 583 else:
595 584 indexdata = f.read()
596 585 if len(indexdata) > 0:
597 586 versionflags = versionformat_unpack(indexdata[:4])[0]
598 587 self._initempty = False
599 588 else:
600 589 versionflags = newversionflags
601 590 except IOError as inst:
602 591 if inst.errno != errno.ENOENT:
603 592 raise
604 593
605 594 versionflags = newversionflags
606 595
607 596 self.version = versionflags
608 597
609 598 flags = versionflags & ~0xFFFF
610 599 fmt = versionflags & 0xFFFF
611 600
612 601 if fmt == REVLOGV0:
613 602 if flags:
614 603 raise error.RevlogError(
615 604 _(b'unknown flags (%#04x) in version %d revlog %s')
616 605 % (flags >> 16, fmt, self.indexfile)
617 606 )
618 607
619 608 self._inline = False
620 609 self._generaldelta = False
621 610
622 611 elif fmt == REVLOGV1:
623 612 if flags & ~REVLOGV1_FLAGS:
624 613 raise error.RevlogError(
625 614 _(b'unknown flags (%#04x) in version %d revlog %s')
626 615 % (flags >> 16, fmt, self.indexfile)
627 616 )
628 617
629 618 self._inline = versionflags & FLAG_INLINE_DATA
630 619 self._generaldelta = versionflags & FLAG_GENERALDELTA
631 620
632 621 elif fmt == REVLOGV2:
633 622 if flags & ~REVLOGV2_FLAGS:
634 623 raise error.RevlogError(
635 624 _(b'unknown flags (%#04x) in version %d revlog %s')
636 625 % (flags >> 16, fmt, self.indexfile)
637 626 )
638 627
639 628 # There is a bug in the transaction handling when going from an
640 629 # inline revlog to a separate index and data file. Turn it off until
641 630 # it's fixed, since v2 revlogs sometimes get rewritten on exchange.
642 631 # See issue6485
643 632 self._inline = False
644 633 # generaldelta implied by version 2 revlogs.
645 634 self._generaldelta = True
646 635
647 636 else:
648 637 raise error.RevlogError(
649 638 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
650 639 )
651 640
652 641 self.nodeconstants = sha1nodeconstants
653 642 self.nullid = self.nodeconstants.nullid
654 643
655 644 # sparse-revlog can't be on without general-delta (issue6056)
656 645 if not self._generaldelta:
657 646 self._sparserevlog = False
658 647
659 648 self._storedeltachains = True
660 649
661 650 devel_nodemap = (
662 651 self.nodemap_file
663 652 and opts.get(b'devel-force-nodemap', False)
664 653 and NodemapRevlogIO is not None
665 654 )
666 655
667 656 use_rust_index = False
668 657 if rustrevlog is not None:
669 658 if self.nodemap_file is not None:
670 659 use_rust_index = True
671 660 else:
672 661 use_rust_index = self.opener.options.get(b'rust.index')
673 662
674 663 self._io = revlogio()
675 664 if self.version == REVLOGV0:
676 665 self._io = revlogoldio()
677 666 elif fmt == REVLOGV2:
678 667 self._io = revlogv2io()
679 668 elif devel_nodemap:
680 669 self._io = NodemapRevlogIO()
681 670 elif use_rust_index:
682 671 self._io = rustrevlogio()
683 672 try:
684 673 d = self._io.parseindex(indexdata, self._inline)
685 674 index, _chunkcache = d
686 675 use_nodemap = (
687 676 not self._inline
688 677 and self.nodemap_file is not None
689 678 and util.safehasattr(index, 'update_nodemap_data')
690 679 )
691 680 if use_nodemap:
692 681 nodemap_data = nodemaputil.persisted_data(self)
693 682 if nodemap_data is not None:
694 683 docket = nodemap_data[0]
695 684 if (
696 685 len(d[0]) > docket.tip_rev
697 686 and d[0][docket.tip_rev][7] == docket.tip_node
698 687 ):
699 688 # no changelog tampering
700 689 self._nodemap_docket = docket
701 690 index.update_nodemap_data(*nodemap_data)
702 691 except (ValueError, IndexError):
703 692 raise error.RevlogError(
704 693 _(b"index %s is corrupted") % self.indexfile
705 694 )
706 695 self.index, self._chunkcache = d
707 696 if not self._chunkcache:
708 697 self._chunkclear()
709 698 # revnum -> (chain-length, sum-delta-length)
710 699 self._chaininfocache = util.lrucachedict(500)
711 700 # revlog header -> revlog compressor
712 701 self._decompressors = {}
713 702
714 703 @util.propertycache
715 704 def _compressor(self):
716 705 engine = util.compengines[self._compengine]
717 706 return engine.revlogcompressor(self._compengineopts)
718 707
719 708 def _indexfp(self, mode=b'r'):
720 709 """file object for the revlog's index file"""
721 710 args = {'mode': mode}
722 711 if mode != b'r':
723 712 args['checkambig'] = self._checkambig
724 713 if mode == b'w':
725 714 args['atomictemp'] = True
726 715 return self.opener(self.indexfile, **args)
727 716
728 717 def _datafp(self, mode=b'r'):
729 718 """file object for the revlog's data file"""
730 719 return self.opener(self.datafile, mode=mode)
731 720
732 721 @contextlib.contextmanager
733 722 def _datareadfp(self, existingfp=None):
734 723 """file object suitable to read data"""
735 724 # Use explicit file handle, if given.
736 725 if existingfp is not None:
737 726 yield existingfp
738 727
739 728 # Use a file handle being actively used for writes, if available.
740 729 # There is some danger to doing this because reads will seek the
741 730 # file. However, _writeentry() performs a SEEK_END before all writes,
742 731 # so we should be safe.
743 732 elif self._writinghandles:
744 733 if self._inline:
745 734 yield self._writinghandles[0]
746 735 else:
747 736 yield self._writinghandles[1]
748 737
749 738 # Otherwise open a new file handle.
750 739 else:
751 740 if self._inline:
752 741 func = self._indexfp
753 742 else:
754 743 func = self._datafp
755 744 with func() as fp:
756 745 yield fp
757 746
758 747 def tiprev(self):
759 748 return len(self.index) - 1
760 749
761 750 def tip(self):
762 751 return self.node(self.tiprev())
763 752
764 753 def __contains__(self, rev):
765 754 return 0 <= rev < len(self)
766 755
767 756 def __len__(self):
768 757 return len(self.index)
769 758
770 759 def __iter__(self):
771 760 return iter(pycompat.xrange(len(self)))
772 761
773 762 def revs(self, start=0, stop=None):
774 763 """iterate over all rev in this revlog (from start to stop)"""
775 764 return storageutil.iterrevs(len(self), start=start, stop=stop)
776 765
777 766 @property
778 767 def nodemap(self):
779 768 msg = (
780 769 b"revlog.nodemap is deprecated, "
781 770 b"use revlog.index.[has_node|rev|get_rev]"
782 771 )
783 772 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
784 773 return self.index.nodemap
785 774
786 775 @property
787 776 def _nodecache(self):
788 777 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
789 778 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
790 779 return self.index.nodemap
791 780
792 781 def hasnode(self, node):
793 782 try:
794 783 self.rev(node)
795 784 return True
796 785 except KeyError:
797 786 return False
798 787
799 788 def candelta(self, baserev, rev):
800 789 """whether two revisions (baserev, rev) can be delta-ed or not"""
801 790 # Disable delta if either rev requires a content-changing flag
802 791 # processor (ex. LFS). This is because such flag processor can alter
803 792 # the rawtext content that the delta will be based on, and two clients
804 793 # could have a same revlog node with different flags (i.e. different
805 794 # rawtext contents) and the delta could be incompatible.
806 795 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
807 796 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
808 797 ):
809 798 return False
810 799 return True
811 800
812 801 def update_caches(self, transaction):
813 802 if self.nodemap_file is not None:
814 803 if transaction is None:
815 804 nodemaputil.update_persistent_nodemap(self)
816 805 else:
817 806 nodemaputil.setup_persistent_nodemap(transaction, self)
818 807
819 808 def clearcaches(self):
820 809 self._revisioncache = None
821 810 self._chainbasecache.clear()
822 811 self._chunkcache = (0, b'')
823 812 self._pcache = {}
824 813 self._nodemap_docket = None
825 814 self.index.clearcaches()
826 815 # The python code is the one responsible for validating the docket, we
827 816 # end up having to refresh it here.
828 817 use_nodemap = (
829 818 not self._inline
830 819 and self.nodemap_file is not None
831 820 and util.safehasattr(self.index, 'update_nodemap_data')
832 821 )
833 822 if use_nodemap:
834 823 nodemap_data = nodemaputil.persisted_data(self)
835 824 if nodemap_data is not None:
836 825 self._nodemap_docket = nodemap_data[0]
837 826 self.index.update_nodemap_data(*nodemap_data)
838 827
839 828 def rev(self, node):
840 829 try:
841 830 return self.index.rev(node)
842 831 except TypeError:
843 832 raise
844 833 except error.RevlogError:
845 834 # parsers.c radix tree lookup failed
846 835 if node == wdirid or node in wdirfilenodeids:
847 836 raise error.WdirUnsupported
848 837 raise error.LookupError(node, self.indexfile, _(b'no node'))
849 838
850 839 # Accessors for index entries.
851 840
852 841 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
853 842 # are flags.
854 843 def start(self, rev):
855 844 return int(self.index[rev][0] >> 16)
856 845
857 846 def flags(self, rev):
858 847 return self.index[rev][0] & 0xFFFF
859 848
860 849 def length(self, rev):
861 850 return self.index[rev][1]
862 851
863 852 def sidedata_length(self, rev):
864 853 if self.version & 0xFFFF != REVLOGV2:
865 854 return 0
866 855 return self.index[rev][9]
867 856
868 857 def rawsize(self, rev):
869 858 """return the length of the uncompressed text for a given revision"""
870 859 l = self.index[rev][2]
871 860 if l >= 0:
872 861 return l
873 862
874 863 t = self.rawdata(rev)
875 864 return len(t)
876 865
877 866 def size(self, rev):
878 867 """length of non-raw text (processed by a "read" flag processor)"""
879 868 # fast path: if no "read" flag processor could change the content,
880 869 # size is rawsize. note: ELLIPSIS is known to not change the content.
881 870 flags = self.flags(rev)
882 871 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
883 872 return self.rawsize(rev)
884 873
885 874 return len(self.revision(rev, raw=False))
886 875
887 876 def chainbase(self, rev):
888 877 base = self._chainbasecache.get(rev)
889 878 if base is not None:
890 879 return base
891 880
892 881 index = self.index
893 882 iterrev = rev
894 883 base = index[iterrev][3]
895 884 while base != iterrev:
896 885 iterrev = base
897 886 base = index[iterrev][3]
898 887
899 888 self._chainbasecache[rev] = base
900 889 return base
901 890
902 891 def linkrev(self, rev):
903 892 return self.index[rev][4]
904 893
905 894 def parentrevs(self, rev):
906 895 try:
907 896 entry = self.index[rev]
908 897 except IndexError:
909 898 if rev == wdirrev:
910 899 raise error.WdirUnsupported
911 900 raise
912 901 if entry[5] == nullrev:
913 902 return entry[6], entry[5]
914 903 else:
915 904 return entry[5], entry[6]
916 905
917 906 # fast parentrevs(rev) where rev isn't filtered
918 907 _uncheckedparentrevs = parentrevs
919 908
920 909 def node(self, rev):
921 910 try:
922 911 return self.index[rev][7]
923 912 except IndexError:
924 913 if rev == wdirrev:
925 914 raise error.WdirUnsupported
926 915 raise
927 916
928 917 # Derived from index values.
929 918
930 919 def end(self, rev):
931 920 return self.start(rev) + self.length(rev)
932 921
933 922 def parents(self, node):
934 923 i = self.index
935 924 d = i[self.rev(node)]
936 925 # inline node() to avoid function call overhead
937 926 if d[5] == nullid:
938 927 return i[d[6]][7], i[d[5]][7]
939 928 else:
940 929 return i[d[5]][7], i[d[6]][7]
941 930
942 931 def chainlen(self, rev):
943 932 return self._chaininfo(rev)[0]
944 933
945 934 def _chaininfo(self, rev):
946 935 chaininfocache = self._chaininfocache
947 936 if rev in chaininfocache:
948 937 return chaininfocache[rev]
949 938 index = self.index
950 939 generaldelta = self._generaldelta
951 940 iterrev = rev
952 941 e = index[iterrev]
953 942 clen = 0
954 943 compresseddeltalen = 0
955 944 while iterrev != e[3]:
956 945 clen += 1
957 946 compresseddeltalen += e[1]
958 947 if generaldelta:
959 948 iterrev = e[3]
960 949 else:
961 950 iterrev -= 1
962 951 if iterrev in chaininfocache:
963 952 t = chaininfocache[iterrev]
964 953 clen += t[0]
965 954 compresseddeltalen += t[1]
966 955 break
967 956 e = index[iterrev]
968 957 else:
969 958 # Add text length of base since decompressing that also takes
970 959 # work. For cache hits the length is already included.
971 960 compresseddeltalen += e[1]
972 961 r = (clen, compresseddeltalen)
973 962 chaininfocache[rev] = r
974 963 return r
975 964
976 965 def _deltachain(self, rev, stoprev=None):
977 966 """Obtain the delta chain for a revision.
978 967
979 968 ``stoprev`` specifies a revision to stop at. If not specified, we
980 969 stop at the base of the chain.
981 970
982 971 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
983 972 revs in ascending order and ``stopped`` is a bool indicating whether
984 973 ``stoprev`` was hit.
985 974 """
986 975 # Try C implementation.
987 976 try:
988 977 return self.index.deltachain(rev, stoprev, self._generaldelta)
989 978 except AttributeError:
990 979 pass
991 980
992 981 chain = []
993 982
994 983 # Alias to prevent attribute lookup in tight loop.
995 984 index = self.index
996 985 generaldelta = self._generaldelta
997 986
998 987 iterrev = rev
999 988 e = index[iterrev]
1000 989 while iterrev != e[3] and iterrev != stoprev:
1001 990 chain.append(iterrev)
1002 991 if generaldelta:
1003 992 iterrev = e[3]
1004 993 else:
1005 994 iterrev -= 1
1006 995 e = index[iterrev]
1007 996
1008 997 if iterrev == stoprev:
1009 998 stopped = True
1010 999 else:
1011 1000 chain.append(iterrev)
1012 1001 stopped = False
1013 1002
1014 1003 chain.reverse()
1015 1004 return chain, stopped
1016 1005
1017 1006 def ancestors(self, revs, stoprev=0, inclusive=False):
1018 1007 """Generate the ancestors of 'revs' in reverse revision order.
1019 1008 Does not generate revs lower than stoprev.
1020 1009
1021 1010 See the documentation for ancestor.lazyancestors for more details."""
1022 1011
1023 1012 # first, make sure start revisions aren't filtered
1024 1013 revs = list(revs)
1025 1014 checkrev = self.node
1026 1015 for r in revs:
1027 1016 checkrev(r)
1028 1017 # and we're sure ancestors aren't filtered as well
1029 1018
1030 1019 if rustancestor is not None:
1031 1020 lazyancestors = rustancestor.LazyAncestors
1032 1021 arg = self.index
1033 1022 else:
1034 1023 lazyancestors = ancestor.lazyancestors
1035 1024 arg = self._uncheckedparentrevs
1036 1025 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1037 1026
1038 1027 def descendants(self, revs):
1039 1028 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1040 1029
1041 1030 def findcommonmissing(self, common=None, heads=None):
1042 1031 """Return a tuple of the ancestors of common and the ancestors of heads
1043 1032 that are not ancestors of common. In revset terminology, we return the
1044 1033 tuple:
1045 1034
1046 1035 ::common, (::heads) - (::common)
1047 1036
1048 1037 The list is sorted by revision number, meaning it is
1049 1038 topologically sorted.
1050 1039
1051 1040 'heads' and 'common' are both lists of node IDs. If heads is
1052 1041 not supplied, uses all of the revlog's heads. If common is not
1053 1042 supplied, uses nullid."""
1054 1043 if common is None:
1055 1044 common = [nullid]
1056 1045 if heads is None:
1057 1046 heads = self.heads()
1058 1047
1059 1048 common = [self.rev(n) for n in common]
1060 1049 heads = [self.rev(n) for n in heads]
1061 1050
1062 1051 # we want the ancestors, but inclusive
1063 1052 class lazyset(object):
1064 1053 def __init__(self, lazyvalues):
1065 1054 self.addedvalues = set()
1066 1055 self.lazyvalues = lazyvalues
1067 1056
1068 1057 def __contains__(self, value):
1069 1058 return value in self.addedvalues or value in self.lazyvalues
1070 1059
1071 1060 def __iter__(self):
1072 1061 added = self.addedvalues
1073 1062 for r in added:
1074 1063 yield r
1075 1064 for r in self.lazyvalues:
1076 1065 if not r in added:
1077 1066 yield r
1078 1067
1079 1068 def add(self, value):
1080 1069 self.addedvalues.add(value)
1081 1070
1082 1071 def update(self, values):
1083 1072 self.addedvalues.update(values)
1084 1073
1085 1074 has = lazyset(self.ancestors(common))
1086 1075 has.add(nullrev)
1087 1076 has.update(common)
1088 1077
1089 1078 # take all ancestors from heads that aren't in has
1090 1079 missing = set()
1091 1080 visit = collections.deque(r for r in heads if r not in has)
1092 1081 while visit:
1093 1082 r = visit.popleft()
1094 1083 if r in missing:
1095 1084 continue
1096 1085 else:
1097 1086 missing.add(r)
1098 1087 for p in self.parentrevs(r):
1099 1088 if p not in has:
1100 1089 visit.append(p)
1101 1090 missing = list(missing)
1102 1091 missing.sort()
1103 1092 return has, [self.node(miss) for miss in missing]
1104 1093
1105 1094 def incrementalmissingrevs(self, common=None):
1106 1095 """Return an object that can be used to incrementally compute the
1107 1096 revision numbers of the ancestors of arbitrary sets that are not
1108 1097 ancestors of common. This is an ancestor.incrementalmissingancestors
1109 1098 object.
1110 1099
1111 1100 'common' is a list of revision numbers. If common is not supplied, uses
1112 1101 nullrev.
1113 1102 """
1114 1103 if common is None:
1115 1104 common = [nullrev]
1116 1105
1117 1106 if rustancestor is not None:
1118 1107 return rustancestor.MissingAncestors(self.index, common)
1119 1108 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1120 1109
1121 1110 def findmissingrevs(self, common=None, heads=None):
1122 1111 """Return the revision numbers of the ancestors of heads that
1123 1112 are not ancestors of common.
1124 1113
1125 1114 More specifically, return a list of revision numbers corresponding to
1126 1115 nodes N such that every N satisfies the following constraints:
1127 1116
1128 1117 1. N is an ancestor of some node in 'heads'
1129 1118 2. N is not an ancestor of any node in 'common'
1130 1119
1131 1120 The list is sorted by revision number, meaning it is
1132 1121 topologically sorted.
1133 1122
1134 1123 'heads' and 'common' are both lists of revision numbers. If heads is
1135 1124 not supplied, uses all of the revlog's heads. If common is not
1136 1125 supplied, uses nullid."""
1137 1126 if common is None:
1138 1127 common = [nullrev]
1139 1128 if heads is None:
1140 1129 heads = self.headrevs()
1141 1130
1142 1131 inc = self.incrementalmissingrevs(common=common)
1143 1132 return inc.missingancestors(heads)
1144 1133
1145 1134 def findmissing(self, common=None, heads=None):
1146 1135 """Return the ancestors of heads that are not ancestors of common.
1147 1136
1148 1137 More specifically, return a list of nodes N such that every N
1149 1138 satisfies the following constraints:
1150 1139
1151 1140 1. N is an ancestor of some node in 'heads'
1152 1141 2. N is not an ancestor of any node in 'common'
1153 1142
1154 1143 The list is sorted by revision number, meaning it is
1155 1144 topologically sorted.
1156 1145
1157 1146 'heads' and 'common' are both lists of node IDs. If heads is
1158 1147 not supplied, uses all of the revlog's heads. If common is not
1159 1148 supplied, uses nullid."""
1160 1149 if common is None:
1161 1150 common = [nullid]
1162 1151 if heads is None:
1163 1152 heads = self.heads()
1164 1153
1165 1154 common = [self.rev(n) for n in common]
1166 1155 heads = [self.rev(n) for n in heads]
1167 1156
1168 1157 inc = self.incrementalmissingrevs(common=common)
1169 1158 return [self.node(r) for r in inc.missingancestors(heads)]
1170 1159
1171 1160 def nodesbetween(self, roots=None, heads=None):
1172 1161 """Return a topological path from 'roots' to 'heads'.
1173 1162
1174 1163 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1175 1164 topologically sorted list of all nodes N that satisfy both of
1176 1165 these constraints:
1177 1166
1178 1167 1. N is a descendant of some node in 'roots'
1179 1168 2. N is an ancestor of some node in 'heads'
1180 1169
1181 1170 Every node is considered to be both a descendant and an ancestor
1182 1171 of itself, so every reachable node in 'roots' and 'heads' will be
1183 1172 included in 'nodes'.
1184 1173
1185 1174 'outroots' is the list of reachable nodes in 'roots', i.e., the
1186 1175 subset of 'roots' that is returned in 'nodes'. Likewise,
1187 1176 'outheads' is the subset of 'heads' that is also in 'nodes'.
1188 1177
1189 1178 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1190 1179 unspecified, uses nullid as the only root. If 'heads' is
1191 1180 unspecified, uses list of all of the revlog's heads."""
1192 1181 nonodes = ([], [], [])
1193 1182 if roots is not None:
1194 1183 roots = list(roots)
1195 1184 if not roots:
1196 1185 return nonodes
1197 1186 lowestrev = min([self.rev(n) for n in roots])
1198 1187 else:
1199 1188 roots = [nullid] # Everybody's a descendant of nullid
1200 1189 lowestrev = nullrev
1201 1190 if (lowestrev == nullrev) and (heads is None):
1202 1191 # We want _all_ the nodes!
1203 1192 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1204 1193 if heads is None:
1205 1194 # All nodes are ancestors, so the latest ancestor is the last
1206 1195 # node.
1207 1196 highestrev = len(self) - 1
1208 1197 # Set ancestors to None to signal that every node is an ancestor.
1209 1198 ancestors = None
1210 1199 # Set heads to an empty dictionary for later discovery of heads
1211 1200 heads = {}
1212 1201 else:
1213 1202 heads = list(heads)
1214 1203 if not heads:
1215 1204 return nonodes
1216 1205 ancestors = set()
1217 1206 # Turn heads into a dictionary so we can remove 'fake' heads.
1218 1207 # Also, later we will be using it to filter out the heads we can't
1219 1208 # find from roots.
1220 1209 heads = dict.fromkeys(heads, False)
1221 1210 # Start at the top and keep marking parents until we're done.
1222 1211 nodestotag = set(heads)
1223 1212 # Remember where the top was so we can use it as a limit later.
1224 1213 highestrev = max([self.rev(n) for n in nodestotag])
1225 1214 while nodestotag:
1226 1215 # grab a node to tag
1227 1216 n = nodestotag.pop()
1228 1217 # Never tag nullid
1229 1218 if n == nullid:
1230 1219 continue
1231 1220 # A node's revision number represents its place in a
1232 1221 # topologically sorted list of nodes.
1233 1222 r = self.rev(n)
1234 1223 if r >= lowestrev:
1235 1224 if n not in ancestors:
1236 1225 # If we are possibly a descendant of one of the roots
1237 1226 # and we haven't already been marked as an ancestor
1238 1227 ancestors.add(n) # Mark as ancestor
1239 1228 # Add non-nullid parents to list of nodes to tag.
1240 1229 nodestotag.update(
1241 1230 [p for p in self.parents(n) if p != nullid]
1242 1231 )
1243 1232 elif n in heads: # We've seen it before, is it a fake head?
1244 1233 # So it is, real heads should not be the ancestors of
1245 1234 # any other heads.
1246 1235 heads.pop(n)
1247 1236 if not ancestors:
1248 1237 return nonodes
1249 1238 # Now that we have our set of ancestors, we want to remove any
1250 1239 # roots that are not ancestors.
1251 1240
1252 1241 # If one of the roots was nullid, everything is included anyway.
1253 1242 if lowestrev > nullrev:
1254 1243 # But, since we weren't, let's recompute the lowest rev to not
1255 1244 # include roots that aren't ancestors.
1256 1245
1257 1246 # Filter out roots that aren't ancestors of heads
1258 1247 roots = [root for root in roots if root in ancestors]
1259 1248 # Recompute the lowest revision
1260 1249 if roots:
1261 1250 lowestrev = min([self.rev(root) for root in roots])
1262 1251 else:
1263 1252 # No more roots? Return empty list
1264 1253 return nonodes
1265 1254 else:
1266 1255 # We are descending from nullid, and don't need to care about
1267 1256 # any other roots.
1268 1257 lowestrev = nullrev
1269 1258 roots = [nullid]
1270 1259 # Transform our roots list into a set.
1271 1260 descendants = set(roots)
1272 1261 # Also, keep the original roots so we can filter out roots that aren't
1273 1262 # 'real' roots (i.e. are descended from other roots).
1274 1263 roots = descendants.copy()
1275 1264 # Our topologically sorted list of output nodes.
1276 1265 orderedout = []
1277 1266 # Don't start at nullid since we don't want nullid in our output list,
1278 1267 # and if nullid shows up in descendants, empty parents will look like
1279 1268 # they're descendants.
1280 1269 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1281 1270 n = self.node(r)
1282 1271 isdescendant = False
1283 1272 if lowestrev == nullrev: # Everybody is a descendant of nullid
1284 1273 isdescendant = True
1285 1274 elif n in descendants:
1286 1275 # n is already a descendant
1287 1276 isdescendant = True
1288 1277 # This check only needs to be done here because all the roots
1289 1278 # will start being marked is descendants before the loop.
1290 1279 if n in roots:
1291 1280 # If n was a root, check if it's a 'real' root.
1292 1281 p = tuple(self.parents(n))
1293 1282 # If any of its parents are descendants, it's not a root.
1294 1283 if (p[0] in descendants) or (p[1] in descendants):
1295 1284 roots.remove(n)
1296 1285 else:
1297 1286 p = tuple(self.parents(n))
1298 1287 # A node is a descendant if either of its parents are
1299 1288 # descendants. (We seeded the dependents list with the roots
1300 1289 # up there, remember?)
1301 1290 if (p[0] in descendants) or (p[1] in descendants):
1302 1291 descendants.add(n)
1303 1292 isdescendant = True
1304 1293 if isdescendant and ((ancestors is None) or (n in ancestors)):
1305 1294 # Only include nodes that are both descendants and ancestors.
1306 1295 orderedout.append(n)
1307 1296 if (ancestors is not None) and (n in heads):
1308 1297 # We're trying to figure out which heads are reachable
1309 1298 # from roots.
1310 1299 # Mark this head as having been reached
1311 1300 heads[n] = True
1312 1301 elif ancestors is None:
1313 1302 # Otherwise, we're trying to discover the heads.
1314 1303 # Assume this is a head because if it isn't, the next step
1315 1304 # will eventually remove it.
1316 1305 heads[n] = True
1317 1306 # But, obviously its parents aren't.
1318 1307 for p in self.parents(n):
1319 1308 heads.pop(p, None)
1320 1309 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1321 1310 roots = list(roots)
1322 1311 assert orderedout
1323 1312 assert roots
1324 1313 assert heads
1325 1314 return (orderedout, roots, heads)
1326 1315
1327 1316 def headrevs(self, revs=None):
1328 1317 if revs is None:
1329 1318 try:
1330 1319 return self.index.headrevs()
1331 1320 except AttributeError:
1332 1321 return self._headrevs()
1333 1322 if rustdagop is not None:
1334 1323 return rustdagop.headrevs(self.index, revs)
1335 1324 return dagop.headrevs(revs, self._uncheckedparentrevs)
1336 1325
1337 1326 def computephases(self, roots):
1338 1327 return self.index.computephasesmapsets(roots)
1339 1328
1340 1329 def _headrevs(self):
1341 1330 count = len(self)
1342 1331 if not count:
1343 1332 return [nullrev]
1344 1333 # we won't iter over filtered rev so nobody is a head at start
1345 1334 ishead = [0] * (count + 1)
1346 1335 index = self.index
1347 1336 for r in self:
1348 1337 ishead[r] = 1 # I may be an head
1349 1338 e = index[r]
1350 1339 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1351 1340 return [r for r, val in enumerate(ishead) if val]
1352 1341
1353 1342 def heads(self, start=None, stop=None):
1354 1343 """return the list of all nodes that have no children
1355 1344
1356 1345 if start is specified, only heads that are descendants of
1357 1346 start will be returned
1358 1347 if stop is specified, it will consider all the revs from stop
1359 1348 as if they had no children
1360 1349 """
1361 1350 if start is None and stop is None:
1362 1351 if not len(self):
1363 1352 return [nullid]
1364 1353 return [self.node(r) for r in self.headrevs()]
1365 1354
1366 1355 if start is None:
1367 1356 start = nullrev
1368 1357 else:
1369 1358 start = self.rev(start)
1370 1359
1371 1360 stoprevs = {self.rev(n) for n in stop or []}
1372 1361
1373 1362 revs = dagop.headrevssubset(
1374 1363 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1375 1364 )
1376 1365
1377 1366 return [self.node(rev) for rev in revs]
1378 1367
1379 1368 def children(self, node):
1380 1369 """find the children of a given node"""
1381 1370 c = []
1382 1371 p = self.rev(node)
1383 1372 for r in self.revs(start=p + 1):
1384 1373 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1385 1374 if prevs:
1386 1375 for pr in prevs:
1387 1376 if pr == p:
1388 1377 c.append(self.node(r))
1389 1378 elif p == nullrev:
1390 1379 c.append(self.node(r))
1391 1380 return c
1392 1381
1393 1382 def commonancestorsheads(self, a, b):
1394 1383 """calculate all the heads of the common ancestors of nodes a and b"""
1395 1384 a, b = self.rev(a), self.rev(b)
1396 1385 ancs = self._commonancestorsheads(a, b)
1397 1386 return pycompat.maplist(self.node, ancs)
1398 1387
1399 1388 def _commonancestorsheads(self, *revs):
1400 1389 """calculate all the heads of the common ancestors of revs"""
1401 1390 try:
1402 1391 ancs = self.index.commonancestorsheads(*revs)
1403 1392 except (AttributeError, OverflowError): # C implementation failed
1404 1393 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1405 1394 return ancs
1406 1395
1407 1396 def isancestor(self, a, b):
1408 1397 """return True if node a is an ancestor of node b
1409 1398
1410 1399 A revision is considered an ancestor of itself."""
1411 1400 a, b = self.rev(a), self.rev(b)
1412 1401 return self.isancestorrev(a, b)
1413 1402
1414 1403 def isancestorrev(self, a, b):
1415 1404 """return True if revision a is an ancestor of revision b
1416 1405
1417 1406 A revision is considered an ancestor of itself.
1418 1407
1419 1408 The implementation of this is trivial but the use of
1420 1409 reachableroots is not."""
1421 1410 if a == nullrev:
1422 1411 return True
1423 1412 elif a == b:
1424 1413 return True
1425 1414 elif a > b:
1426 1415 return False
1427 1416 return bool(self.reachableroots(a, [b], [a], includepath=False))
1428 1417
1429 1418 def reachableroots(self, minroot, heads, roots, includepath=False):
1430 1419 """return (heads(::(<roots> and <roots>::<heads>)))
1431 1420
1432 1421 If includepath is True, return (<roots>::<heads>)."""
1433 1422 try:
1434 1423 return self.index.reachableroots2(
1435 1424 minroot, heads, roots, includepath
1436 1425 )
1437 1426 except AttributeError:
1438 1427 return dagop._reachablerootspure(
1439 1428 self.parentrevs, minroot, roots, heads, includepath
1440 1429 )
1441 1430
1442 1431 def ancestor(self, a, b):
1443 1432 """calculate the "best" common ancestor of nodes a and b"""
1444 1433
1445 1434 a, b = self.rev(a), self.rev(b)
1446 1435 try:
1447 1436 ancs = self.index.ancestors(a, b)
1448 1437 except (AttributeError, OverflowError):
1449 1438 ancs = ancestor.ancestors(self.parentrevs, a, b)
1450 1439 if ancs:
1451 1440 # choose a consistent winner when there's a tie
1452 1441 return min(map(self.node, ancs))
1453 1442 return nullid
1454 1443
1455 1444 def _match(self, id):
1456 1445 if isinstance(id, int):
1457 1446 # rev
1458 1447 return self.node(id)
1459 1448 if len(id) == 20:
1460 1449 # possibly a binary node
1461 1450 # odds of a binary node being all hex in ASCII are 1 in 10**25
1462 1451 try:
1463 1452 node = id
1464 1453 self.rev(node) # quick search the index
1465 1454 return node
1466 1455 except error.LookupError:
1467 1456 pass # may be partial hex id
1468 1457 try:
1469 1458 # str(rev)
1470 1459 rev = int(id)
1471 1460 if b"%d" % rev != id:
1472 1461 raise ValueError
1473 1462 if rev < 0:
1474 1463 rev = len(self) + rev
1475 1464 if rev < 0 or rev >= len(self):
1476 1465 raise ValueError
1477 1466 return self.node(rev)
1478 1467 except (ValueError, OverflowError):
1479 1468 pass
1480 1469 if len(id) == 40:
1481 1470 try:
1482 1471 # a full hex nodeid?
1483 1472 node = bin(id)
1484 1473 self.rev(node)
1485 1474 return node
1486 1475 except (TypeError, error.LookupError):
1487 1476 pass
1488 1477
1489 1478 def _partialmatch(self, id):
1490 1479 # we don't care wdirfilenodeids as they should be always full hash
1491 1480 maybewdir = wdirhex.startswith(id)
1492 1481 try:
1493 1482 partial = self.index.partialmatch(id)
1494 1483 if partial and self.hasnode(partial):
1495 1484 if maybewdir:
1496 1485 # single 'ff...' match in radix tree, ambiguous with wdir
1497 1486 raise error.RevlogError
1498 1487 return partial
1499 1488 if maybewdir:
1500 1489 # no 'ff...' match in radix tree, wdir identified
1501 1490 raise error.WdirUnsupported
1502 1491 return None
1503 1492 except error.RevlogError:
1504 1493 # parsers.c radix tree lookup gave multiple matches
1505 1494 # fast path: for unfiltered changelog, radix tree is accurate
1506 1495 if not getattr(self, 'filteredrevs', None):
1507 1496 raise error.AmbiguousPrefixLookupError(
1508 1497 id, self.indexfile, _(b'ambiguous identifier')
1509 1498 )
1510 1499 # fall through to slow path that filters hidden revisions
1511 1500 except (AttributeError, ValueError):
1512 1501 # we are pure python, or key was too short to search radix tree
1513 1502 pass
1514 1503
1515 1504 if id in self._pcache:
1516 1505 return self._pcache[id]
1517 1506
1518 1507 if len(id) <= 40:
1519 1508 try:
1520 1509 # hex(node)[:...]
1521 1510 l = len(id) // 2 # grab an even number of digits
1522 1511 prefix = bin(id[: l * 2])
1523 1512 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1524 1513 nl = [
1525 1514 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1526 1515 ]
1527 1516 if nullhex.startswith(id):
1528 1517 nl.append(nullid)
1529 1518 if len(nl) > 0:
1530 1519 if len(nl) == 1 and not maybewdir:
1531 1520 self._pcache[id] = nl[0]
1532 1521 return nl[0]
1533 1522 raise error.AmbiguousPrefixLookupError(
1534 1523 id, self.indexfile, _(b'ambiguous identifier')
1535 1524 )
1536 1525 if maybewdir:
1537 1526 raise error.WdirUnsupported
1538 1527 return None
1539 1528 except TypeError:
1540 1529 pass
1541 1530
1542 1531 def lookup(self, id):
1543 1532 """locate a node based on:
1544 1533 - revision number or str(revision number)
1545 1534 - nodeid or subset of hex nodeid
1546 1535 """
1547 1536 n = self._match(id)
1548 1537 if n is not None:
1549 1538 return n
1550 1539 n = self._partialmatch(id)
1551 1540 if n:
1552 1541 return n
1553 1542
1554 1543 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1555 1544
1556 1545 def shortest(self, node, minlength=1):
1557 1546 """Find the shortest unambiguous prefix that matches node."""
1558 1547
1559 1548 def isvalid(prefix):
1560 1549 try:
1561 1550 matchednode = self._partialmatch(prefix)
1562 1551 except error.AmbiguousPrefixLookupError:
1563 1552 return False
1564 1553 except error.WdirUnsupported:
1565 1554 # single 'ff...' match
1566 1555 return True
1567 1556 if matchednode is None:
1568 1557 raise error.LookupError(node, self.indexfile, _(b'no node'))
1569 1558 return True
1570 1559
1571 1560 def maybewdir(prefix):
1572 1561 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1573 1562
1574 1563 hexnode = hex(node)
1575 1564
1576 1565 def disambiguate(hexnode, minlength):
1577 1566 """Disambiguate against wdirid."""
1578 1567 for length in range(minlength, len(hexnode) + 1):
1579 1568 prefix = hexnode[:length]
1580 1569 if not maybewdir(prefix):
1581 1570 return prefix
1582 1571
1583 1572 if not getattr(self, 'filteredrevs', None):
1584 1573 try:
1585 1574 length = max(self.index.shortest(node), minlength)
1586 1575 return disambiguate(hexnode, length)
1587 1576 except error.RevlogError:
1588 1577 if node != wdirid:
1589 1578 raise error.LookupError(node, self.indexfile, _(b'no node'))
1590 1579 except AttributeError:
1591 1580 # Fall through to pure code
1592 1581 pass
1593 1582
1594 1583 if node == wdirid:
1595 1584 for length in range(minlength, len(hexnode) + 1):
1596 1585 prefix = hexnode[:length]
1597 1586 if isvalid(prefix):
1598 1587 return prefix
1599 1588
1600 1589 for length in range(minlength, len(hexnode) + 1):
1601 1590 prefix = hexnode[:length]
1602 1591 if isvalid(prefix):
1603 1592 return disambiguate(hexnode, length)
1604 1593
1605 1594 def cmp(self, node, text):
1606 1595 """compare text with a given file revision
1607 1596
1608 1597 returns True if text is different than what is stored.
1609 1598 """
1610 1599 p1, p2 = self.parents(node)
1611 1600 return storageutil.hashrevisionsha1(text, p1, p2) != node
1612 1601
1613 1602 def _cachesegment(self, offset, data):
1614 1603 """Add a segment to the revlog cache.
1615 1604
1616 1605 Accepts an absolute offset and the data that is at that location.
1617 1606 """
1618 1607 o, d = self._chunkcache
1619 1608 # try to add to existing cache
1620 1609 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1621 1610 self._chunkcache = o, d + data
1622 1611 else:
1623 1612 self._chunkcache = offset, data
1624 1613
1625 1614 def _readsegment(self, offset, length, df=None):
1626 1615 """Load a segment of raw data from the revlog.
1627 1616
1628 1617 Accepts an absolute offset, length to read, and an optional existing
1629 1618 file handle to read from.
1630 1619
1631 1620 If an existing file handle is passed, it will be seeked and the
1632 1621 original seek position will NOT be restored.
1633 1622
1634 1623 Returns a str or buffer of raw byte data.
1635 1624
1636 1625 Raises if the requested number of bytes could not be read.
1637 1626 """
1638 1627 # Cache data both forward and backward around the requested
1639 1628 # data, in a fixed size window. This helps speed up operations
1640 1629 # involving reading the revlog backwards.
1641 1630 cachesize = self._chunkcachesize
1642 1631 realoffset = offset & ~(cachesize - 1)
1643 1632 reallength = (
1644 1633 (offset + length + cachesize) & ~(cachesize - 1)
1645 1634 ) - realoffset
1646 1635 with self._datareadfp(df) as df:
1647 1636 df.seek(realoffset)
1648 1637 d = df.read(reallength)
1649 1638
1650 1639 self._cachesegment(realoffset, d)
1651 1640 if offset != realoffset or reallength != length:
1652 1641 startoffset = offset - realoffset
1653 1642 if len(d) - startoffset < length:
1654 1643 raise error.RevlogError(
1655 1644 _(
1656 1645 b'partial read of revlog %s; expected %d bytes from '
1657 1646 b'offset %d, got %d'
1658 1647 )
1659 1648 % (
1660 1649 self.indexfile if self._inline else self.datafile,
1661 1650 length,
1662 1651 realoffset,
1663 1652 len(d) - startoffset,
1664 1653 )
1665 1654 )
1666 1655
1667 1656 return util.buffer(d, startoffset, length)
1668 1657
1669 1658 if len(d) < length:
1670 1659 raise error.RevlogError(
1671 1660 _(
1672 1661 b'partial read of revlog %s; expected %d bytes from offset '
1673 1662 b'%d, got %d'
1674 1663 )
1675 1664 % (
1676 1665 self.indexfile if self._inline else self.datafile,
1677 1666 length,
1678 1667 offset,
1679 1668 len(d),
1680 1669 )
1681 1670 )
1682 1671
1683 1672 return d
1684 1673
1685 1674 def _getsegment(self, offset, length, df=None):
1686 1675 """Obtain a segment of raw data from the revlog.
1687 1676
1688 1677 Accepts an absolute offset, length of bytes to obtain, and an
1689 1678 optional file handle to the already-opened revlog. If the file
1690 1679 handle is used, it's original seek position will not be preserved.
1691 1680
1692 1681 Requests for data may be returned from a cache.
1693 1682
1694 1683 Returns a str or a buffer instance of raw byte data.
1695 1684 """
1696 1685 o, d = self._chunkcache
1697 1686 l = len(d)
1698 1687
1699 1688 # is it in the cache?
1700 1689 cachestart = offset - o
1701 1690 cacheend = cachestart + length
1702 1691 if cachestart >= 0 and cacheend <= l:
1703 1692 if cachestart == 0 and cacheend == l:
1704 1693 return d # avoid a copy
1705 1694 return util.buffer(d, cachestart, cacheend - cachestart)
1706 1695
1707 1696 return self._readsegment(offset, length, df=df)
1708 1697
1709 1698 def _getsegmentforrevs(self, startrev, endrev, df=None):
1710 1699 """Obtain a segment of raw data corresponding to a range of revisions.
1711 1700
1712 1701 Accepts the start and end revisions and an optional already-open
1713 1702 file handle to be used for reading. If the file handle is read, its
1714 1703 seek position will not be preserved.
1715 1704
1716 1705 Requests for data may be satisfied by a cache.
1717 1706
1718 1707 Returns a 2-tuple of (offset, data) for the requested range of
1719 1708 revisions. Offset is the integer offset from the beginning of the
1720 1709 revlog and data is a str or buffer of the raw byte data.
1721 1710
1722 1711 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1723 1712 to determine where each revision's data begins and ends.
1724 1713 """
1725 1714 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1726 1715 # (functions are expensive).
1727 1716 index = self.index
1728 1717 istart = index[startrev]
1729 1718 start = int(istart[0] >> 16)
1730 1719 if startrev == endrev:
1731 1720 end = start + istart[1]
1732 1721 else:
1733 1722 iend = index[endrev]
1734 1723 end = int(iend[0] >> 16) + iend[1]
1735 1724
1736 1725 if self._inline:
1737 1726 start += (startrev + 1) * self._io.size
1738 1727 end += (endrev + 1) * self._io.size
1739 1728 length = end - start
1740 1729
1741 1730 return start, self._getsegment(start, length, df=df)
1742 1731
1743 1732 def _chunk(self, rev, df=None):
1744 1733 """Obtain a single decompressed chunk for a revision.
1745 1734
1746 1735 Accepts an integer revision and an optional already-open file handle
1747 1736 to be used for reading. If used, the seek position of the file will not
1748 1737 be preserved.
1749 1738
1750 1739 Returns a str holding uncompressed data for the requested revision.
1751 1740 """
1752 1741 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1753 1742
1754 1743 def _chunks(self, revs, df=None, targetsize=None):
1755 1744 """Obtain decompressed chunks for the specified revisions.
1756 1745
1757 1746 Accepts an iterable of numeric revisions that are assumed to be in
1758 1747 ascending order. Also accepts an optional already-open file handle
1759 1748 to be used for reading. If used, the seek position of the file will
1760 1749 not be preserved.
1761 1750
1762 1751 This function is similar to calling ``self._chunk()`` multiple times,
1763 1752 but is faster.
1764 1753
1765 1754 Returns a list with decompressed data for each requested revision.
1766 1755 """
1767 1756 if not revs:
1768 1757 return []
1769 1758 start = self.start
1770 1759 length = self.length
1771 1760 inline = self._inline
1772 1761 iosize = self._io.size
1773 1762 buffer = util.buffer
1774 1763
1775 1764 l = []
1776 1765 ladd = l.append
1777 1766
1778 1767 if not self._withsparseread:
1779 1768 slicedchunks = (revs,)
1780 1769 else:
1781 1770 slicedchunks = deltautil.slicechunk(
1782 1771 self, revs, targetsize=targetsize
1783 1772 )
1784 1773
1785 1774 for revschunk in slicedchunks:
1786 1775 firstrev = revschunk[0]
1787 1776 # Skip trailing revisions with empty diff
1788 1777 for lastrev in revschunk[::-1]:
1789 1778 if length(lastrev) != 0:
1790 1779 break
1791 1780
1792 1781 try:
1793 1782 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1794 1783 except OverflowError:
1795 1784 # issue4215 - we can't cache a run of chunks greater than
1796 1785 # 2G on Windows
1797 1786 return [self._chunk(rev, df=df) for rev in revschunk]
1798 1787
1799 1788 decomp = self.decompress
1800 1789 for rev in revschunk:
1801 1790 chunkstart = start(rev)
1802 1791 if inline:
1803 1792 chunkstart += (rev + 1) * iosize
1804 1793 chunklength = length(rev)
1805 1794 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1806 1795
1807 1796 return l
1808 1797
1809 1798 def _chunkclear(self):
1810 1799 """Clear the raw chunk cache."""
1811 1800 self._chunkcache = (0, b'')
1812 1801
1813 1802 def deltaparent(self, rev):
1814 1803 """return deltaparent of the given revision"""
1815 1804 base = self.index[rev][3]
1816 1805 if base == rev:
1817 1806 return nullrev
1818 1807 elif self._generaldelta:
1819 1808 return base
1820 1809 else:
1821 1810 return rev - 1
1822 1811
1823 1812 def issnapshot(self, rev):
1824 1813 """tells whether rev is a snapshot"""
1825 1814 if not self._sparserevlog:
1826 1815 return self.deltaparent(rev) == nullrev
1827 1816 elif util.safehasattr(self.index, b'issnapshot'):
1828 1817 # directly assign the method to cache the testing and access
1829 1818 self.issnapshot = self.index.issnapshot
1830 1819 return self.issnapshot(rev)
1831 1820 if rev == nullrev:
1832 1821 return True
1833 1822 entry = self.index[rev]
1834 1823 base = entry[3]
1835 1824 if base == rev:
1836 1825 return True
1837 1826 if base == nullrev:
1838 1827 return True
1839 1828 p1 = entry[5]
1840 1829 p2 = entry[6]
1841 1830 if base == p1 or base == p2:
1842 1831 return False
1843 1832 return self.issnapshot(base)
1844 1833
1845 1834 def snapshotdepth(self, rev):
1846 1835 """number of snapshot in the chain before this one"""
1847 1836 if not self.issnapshot(rev):
1848 1837 raise error.ProgrammingError(b'revision %d not a snapshot')
1849 1838 return len(self._deltachain(rev)[0]) - 1
1850 1839
1851 1840 def revdiff(self, rev1, rev2):
1852 1841 """return or calculate a delta between two revisions
1853 1842
1854 1843 The delta calculated is in binary form and is intended to be written to
1855 1844 revlog data directly. So this function needs raw revision data.
1856 1845 """
1857 1846 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1858 1847 return bytes(self._chunk(rev2))
1859 1848
1860 1849 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1861 1850
1862 1851 def _processflags(self, text, flags, operation, raw=False):
1863 1852 """deprecated entry point to access flag processors"""
1864 1853 msg = b'_processflag(...) use the specialized variant'
1865 1854 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1866 1855 if raw:
1867 1856 return text, flagutil.processflagsraw(self, text, flags)
1868 1857 elif operation == b'read':
1869 1858 return flagutil.processflagsread(self, text, flags)
1870 1859 else: # write operation
1871 1860 return flagutil.processflagswrite(self, text, flags)
1872 1861
1873 1862 def revision(self, nodeorrev, _df=None, raw=False):
1874 1863 """return an uncompressed revision of a given node or revision
1875 1864 number.
1876 1865
1877 1866 _df - an existing file handle to read from. (internal-only)
1878 1867 raw - an optional argument specifying if the revision data is to be
1879 1868 treated as raw data when applying flag transforms. 'raw' should be set
1880 1869 to True when generating changegroups or in debug commands.
1881 1870 """
1882 1871 if raw:
1883 1872 msg = (
1884 1873 b'revlog.revision(..., raw=True) is deprecated, '
1885 1874 b'use revlog.rawdata(...)'
1886 1875 )
1887 1876 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1888 1877 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1889 1878
1890 1879 def sidedata(self, nodeorrev, _df=None):
1891 1880 """a map of extra data related to the changeset but not part of the hash
1892 1881
1893 1882 This function currently return a dictionary. However, more advanced
1894 1883 mapping object will likely be used in the future for a more
1895 1884 efficient/lazy code.
1896 1885 """
1897 1886 return self._revisiondata(nodeorrev, _df)[1]
1898 1887
1899 1888 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1900 1889 # deal with <nodeorrev> argument type
1901 1890 if isinstance(nodeorrev, int):
1902 1891 rev = nodeorrev
1903 1892 node = self.node(rev)
1904 1893 else:
1905 1894 node = nodeorrev
1906 1895 rev = None
1907 1896
1908 1897 # fast path the special `nullid` rev
1909 1898 if node == nullid:
1910 1899 return b"", {}
1911 1900
1912 1901 # ``rawtext`` is the text as stored inside the revlog. Might be the
1913 1902 # revision or might need to be processed to retrieve the revision.
1914 1903 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1915 1904
1916 1905 if self.version & 0xFFFF == REVLOGV2:
1917 1906 if rev is None:
1918 1907 rev = self.rev(node)
1919 1908 sidedata = self._sidedata(rev)
1920 1909 else:
1921 1910 sidedata = {}
1922 1911
1923 1912 if raw and validated:
1924 1913 # if we don't want to process the raw text and that raw
1925 1914 # text is cached, we can exit early.
1926 1915 return rawtext, sidedata
1927 1916 if rev is None:
1928 1917 rev = self.rev(node)
1929 1918 # the revlog's flag for this revision
1930 1919 # (usually alter its state or content)
1931 1920 flags = self.flags(rev)
1932 1921
1933 1922 if validated and flags == REVIDX_DEFAULT_FLAGS:
1934 1923 # no extra flags set, no flag processor runs, text = rawtext
1935 1924 return rawtext, sidedata
1936 1925
1937 1926 if raw:
1938 1927 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1939 1928 text = rawtext
1940 1929 else:
1941 1930 r = flagutil.processflagsread(self, rawtext, flags)
1942 1931 text, validatehash = r
1943 1932 if validatehash:
1944 1933 self.checkhash(text, node, rev=rev)
1945 1934 if not validated:
1946 1935 self._revisioncache = (node, rev, rawtext)
1947 1936
1948 1937 return text, sidedata
1949 1938
1950 1939 def _rawtext(self, node, rev, _df=None):
1951 1940 """return the possibly unvalidated rawtext for a revision
1952 1941
1953 1942 returns (rev, rawtext, validated)
1954 1943 """
1955 1944
1956 1945 # revision in the cache (could be useful to apply delta)
1957 1946 cachedrev = None
1958 1947 # An intermediate text to apply deltas to
1959 1948 basetext = None
1960 1949
1961 1950 # Check if we have the entry in cache
1962 1951 # The cache entry looks like (node, rev, rawtext)
1963 1952 if self._revisioncache:
1964 1953 if self._revisioncache[0] == node:
1965 1954 return (rev, self._revisioncache[2], True)
1966 1955 cachedrev = self._revisioncache[1]
1967 1956
1968 1957 if rev is None:
1969 1958 rev = self.rev(node)
1970 1959
1971 1960 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1972 1961 if stopped:
1973 1962 basetext = self._revisioncache[2]
1974 1963
1975 1964 # drop cache to save memory, the caller is expected to
1976 1965 # update self._revisioncache after validating the text
1977 1966 self._revisioncache = None
1978 1967
1979 1968 targetsize = None
1980 1969 rawsize = self.index[rev][2]
1981 1970 if 0 <= rawsize:
1982 1971 targetsize = 4 * rawsize
1983 1972
1984 1973 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1985 1974 if basetext is None:
1986 1975 basetext = bytes(bins[0])
1987 1976 bins = bins[1:]
1988 1977
1989 1978 rawtext = mdiff.patches(basetext, bins)
1990 1979 del basetext # let us have a chance to free memory early
1991 1980 return (rev, rawtext, False)
1992 1981
1993 1982 def _sidedata(self, rev):
1994 1983 """Return the sidedata for a given revision number."""
1995 1984 index_entry = self.index[rev]
1996 1985 sidedata_offset = index_entry[8]
1997 1986 sidedata_size = index_entry[9]
1998 1987
1999 1988 if self._inline:
2000 1989 sidedata_offset += self._io.size * (1 + rev)
2001 1990 if sidedata_size == 0:
2002 1991 return {}
2003 1992
2004 1993 segment = self._getsegment(sidedata_offset, sidedata_size)
2005 1994 sidedata = sidedatautil.deserialize_sidedata(segment)
2006 1995 return sidedata
2007 1996
2008 1997 def rawdata(self, nodeorrev, _df=None):
2009 1998 """return an uncompressed raw data of a given node or revision number.
2010 1999
2011 2000 _df - an existing file handle to read from. (internal-only)
2012 2001 """
2013 2002 return self._revisiondata(nodeorrev, _df, raw=True)[0]
2014 2003
2015 2004 def hash(self, text, p1, p2):
2016 2005 """Compute a node hash.
2017 2006
2018 2007 Available as a function so that subclasses can replace the hash
2019 2008 as needed.
2020 2009 """
2021 2010 return storageutil.hashrevisionsha1(text, p1, p2)
2022 2011
2023 2012 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2024 2013 """Check node hash integrity.
2025 2014
2026 2015 Available as a function so that subclasses can extend hash mismatch
2027 2016 behaviors as needed.
2028 2017 """
2029 2018 try:
2030 2019 if p1 is None and p2 is None:
2031 2020 p1, p2 = self.parents(node)
2032 2021 if node != self.hash(text, p1, p2):
2033 2022 # Clear the revision cache on hash failure. The revision cache
2034 2023 # only stores the raw revision and clearing the cache does have
2035 2024 # the side-effect that we won't have a cache hit when the raw
2036 2025 # revision data is accessed. But this case should be rare and
2037 2026 # it is extra work to teach the cache about the hash
2038 2027 # verification state.
2039 2028 if self._revisioncache and self._revisioncache[0] == node:
2040 2029 self._revisioncache = None
2041 2030
2042 2031 revornode = rev
2043 2032 if revornode is None:
2044 2033 revornode = templatefilters.short(hex(node))
2045 2034 raise error.RevlogError(
2046 2035 _(b"integrity check failed on %s:%s")
2047 2036 % (self.indexfile, pycompat.bytestr(revornode))
2048 2037 )
2049 2038 except error.RevlogError:
2050 2039 if self._censorable and storageutil.iscensoredtext(text):
2051 2040 raise error.CensoredNodeError(self.indexfile, node, text)
2052 2041 raise
2053 2042
2054 2043 def _enforceinlinesize(self, tr, fp=None):
2055 2044 """Check if the revlog is too big for inline and convert if so.
2056 2045
2057 2046 This should be called after revisions are added to the revlog. If the
2058 2047 revlog has grown too large to be an inline revlog, it will convert it
2059 2048 to use multiple index and data files.
2060 2049 """
2061 2050 tiprev = len(self) - 1
2062 2051 if (
2063 2052 not self._inline
2064 2053 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
2065 2054 ):
2066 2055 return
2067 2056
2068 2057 troffset = tr.findoffset(self.indexfile)
2069 2058 if troffset is None:
2070 2059 raise error.RevlogError(
2071 2060 _(b"%s not found in the transaction") % self.indexfile
2072 2061 )
2073 2062 trindex = 0
2074 2063 tr.add(self.datafile, 0)
2075 2064
2076 2065 if fp:
2077 2066 fp.flush()
2078 2067 fp.close()
2079 2068 # We can't use the cached file handle after close(). So prevent
2080 2069 # its usage.
2081 2070 self._writinghandles = None
2082 2071
2083 2072 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
2084 2073 for r in self:
2085 2074 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
2086 2075 if troffset <= self.start(r):
2087 2076 trindex = r
2088 2077
2089 2078 with self._indexfp(b'w') as fp:
2090 2079 self.version &= ~FLAG_INLINE_DATA
2091 2080 self._inline = False
2092 2081 io = self._io
2093 2082 for i in self:
2094 2083 e = io.packentry(self.index[i], self.node, self.version, i)
2095 2084 fp.write(e)
2096 2085
2097 2086 # the temp file replace the real index when we exit the context
2098 2087 # manager
2099 2088
2100 2089 tr.replace(self.indexfile, trindex * self._io.size)
2101 2090 nodemaputil.setup_persistent_nodemap(tr, self)
2102 2091 self._chunkclear()
2103 2092
2104 2093 def _nodeduplicatecallback(self, transaction, node):
2105 2094 """called when trying to add a node already stored."""
2106 2095
2107 2096 def addrevision(
2108 2097 self,
2109 2098 text,
2110 2099 transaction,
2111 2100 link,
2112 2101 p1,
2113 2102 p2,
2114 2103 cachedelta=None,
2115 2104 node=None,
2116 2105 flags=REVIDX_DEFAULT_FLAGS,
2117 2106 deltacomputer=None,
2118 2107 sidedata=None,
2119 2108 ):
2120 2109 """add a revision to the log
2121 2110
2122 2111 text - the revision data to add
2123 2112 transaction - the transaction object used for rollback
2124 2113 link - the linkrev data to add
2125 2114 p1, p2 - the parent nodeids of the revision
2126 2115 cachedelta - an optional precomputed delta
2127 2116 node - nodeid of revision; typically node is not specified, and it is
2128 2117 computed by default as hash(text, p1, p2), however subclasses might
2129 2118 use different hashing method (and override checkhash() in such case)
2130 2119 flags - the known flags to set on the revision
2131 2120 deltacomputer - an optional deltacomputer instance shared between
2132 2121 multiple calls
2133 2122 """
2134 2123 if link == nullrev:
2135 2124 raise error.RevlogError(
2136 2125 _(b"attempted to add linkrev -1 to %s") % self.indexfile
2137 2126 )
2138 2127
2139 2128 if sidedata is None:
2140 2129 sidedata = {}
2141 2130 elif not self.hassidedata:
2142 2131 raise error.ProgrammingError(
2143 2132 _(b"trying to add sidedata to a revlog who don't support them")
2144 2133 )
2145 2134
2146 2135 if flags:
2147 2136 node = node or self.hash(text, p1, p2)
2148 2137
2149 2138 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2150 2139
2151 2140 # If the flag processor modifies the revision data, ignore any provided
2152 2141 # cachedelta.
2153 2142 if rawtext != text:
2154 2143 cachedelta = None
2155 2144
2156 2145 if len(rawtext) > _maxentrysize:
2157 2146 raise error.RevlogError(
2158 2147 _(
2159 2148 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2160 2149 )
2161 2150 % (self.indexfile, len(rawtext))
2162 2151 )
2163 2152
2164 2153 node = node or self.hash(rawtext, p1, p2)
2165 2154 rev = self.index.get_rev(node)
2166 2155 if rev is not None:
2167 2156 return rev
2168 2157
2169 2158 if validatehash:
2170 2159 self.checkhash(rawtext, node, p1=p1, p2=p2)
2171 2160
2172 2161 return self.addrawrevision(
2173 2162 rawtext,
2174 2163 transaction,
2175 2164 link,
2176 2165 p1,
2177 2166 p2,
2178 2167 node,
2179 2168 flags,
2180 2169 cachedelta=cachedelta,
2181 2170 deltacomputer=deltacomputer,
2182 2171 sidedata=sidedata,
2183 2172 )
2184 2173
2185 2174 def addrawrevision(
2186 2175 self,
2187 2176 rawtext,
2188 2177 transaction,
2189 2178 link,
2190 2179 p1,
2191 2180 p2,
2192 2181 node,
2193 2182 flags,
2194 2183 cachedelta=None,
2195 2184 deltacomputer=None,
2196 2185 sidedata=None,
2197 2186 ):
2198 2187 """add a raw revision with known flags, node and parents
2199 2188 useful when reusing a revision not stored in this revlog (ex: received
2200 2189 over wire, or read from an external bundle).
2201 2190 """
2202 2191 dfh = None
2203 2192 if not self._inline:
2204 2193 dfh = self._datafp(b"a+")
2205 2194 ifh = self._indexfp(b"a+")
2206 2195 try:
2207 2196 return self._addrevision(
2208 2197 node,
2209 2198 rawtext,
2210 2199 transaction,
2211 2200 link,
2212 2201 p1,
2213 2202 p2,
2214 2203 flags,
2215 2204 cachedelta,
2216 2205 ifh,
2217 2206 dfh,
2218 2207 deltacomputer=deltacomputer,
2219 2208 sidedata=sidedata,
2220 2209 )
2221 2210 finally:
2222 2211 if dfh:
2223 2212 dfh.close()
2224 2213 ifh.close()
2225 2214
2226 2215 def compress(self, data):
2227 2216 """Generate a possibly-compressed representation of data."""
2228 2217 if not data:
2229 2218 return b'', data
2230 2219
2231 2220 compressed = self._compressor.compress(data)
2232 2221
2233 2222 if compressed:
2234 2223 # The revlog compressor added the header in the returned data.
2235 2224 return b'', compressed
2236 2225
2237 2226 if data[0:1] == b'\0':
2238 2227 return b'', data
2239 2228 return b'u', data
2240 2229
2241 2230 def decompress(self, data):
2242 2231 """Decompress a revlog chunk.
2243 2232
2244 2233 The chunk is expected to begin with a header identifying the
2245 2234 format type so it can be routed to an appropriate decompressor.
2246 2235 """
2247 2236 if not data:
2248 2237 return data
2249 2238
2250 2239 # Revlogs are read much more frequently than they are written and many
2251 2240 # chunks only take microseconds to decompress, so performance is
2252 2241 # important here.
2253 2242 #
2254 2243 # We can make a few assumptions about revlogs:
2255 2244 #
2256 2245 # 1) the majority of chunks will be compressed (as opposed to inline
2257 2246 # raw data).
2258 2247 # 2) decompressing *any* data will likely by at least 10x slower than
2259 2248 # returning raw inline data.
2260 2249 # 3) we want to prioritize common and officially supported compression
2261 2250 # engines
2262 2251 #
2263 2252 # It follows that we want to optimize for "decompress compressed data
2264 2253 # when encoded with common and officially supported compression engines"
2265 2254 # case over "raw data" and "data encoded by less common or non-official
2266 2255 # compression engines." That is why we have the inline lookup first
2267 2256 # followed by the compengines lookup.
2268 2257 #
2269 2258 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2270 2259 # compressed chunks. And this matters for changelog and manifest reads.
2271 2260 t = data[0:1]
2272 2261
2273 2262 if t == b'x':
2274 2263 try:
2275 2264 return _zlibdecompress(data)
2276 2265 except zlib.error as e:
2277 2266 raise error.RevlogError(
2278 2267 _(b'revlog decompress error: %s')
2279 2268 % stringutil.forcebytestr(e)
2280 2269 )
2281 2270 # '\0' is more common than 'u' so it goes first.
2282 2271 elif t == b'\0':
2283 2272 return data
2284 2273 elif t == b'u':
2285 2274 return util.buffer(data, 1)
2286 2275
2287 2276 try:
2288 2277 compressor = self._decompressors[t]
2289 2278 except KeyError:
2290 2279 try:
2291 2280 engine = util.compengines.forrevlogheader(t)
2292 2281 compressor = engine.revlogcompressor(self._compengineopts)
2293 2282 self._decompressors[t] = compressor
2294 2283 except KeyError:
2295 2284 raise error.RevlogError(
2296 2285 _(b'unknown compression type %s') % binascii.hexlify(t)
2297 2286 )
2298 2287
2299 2288 return compressor.decompress(data)
2300 2289
2301 2290 def _addrevision(
2302 2291 self,
2303 2292 node,
2304 2293 rawtext,
2305 2294 transaction,
2306 2295 link,
2307 2296 p1,
2308 2297 p2,
2309 2298 flags,
2310 2299 cachedelta,
2311 2300 ifh,
2312 2301 dfh,
2313 2302 alwayscache=False,
2314 2303 deltacomputer=None,
2315 2304 sidedata=None,
2316 2305 ):
2317 2306 """internal function to add revisions to the log
2318 2307
2319 2308 see addrevision for argument descriptions.
2320 2309
2321 2310 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2322 2311
2323 2312 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2324 2313 be used.
2325 2314
2326 2315 invariants:
2327 2316 - rawtext is optional (can be None); if not set, cachedelta must be set.
2328 2317 if both are set, they must correspond to each other.
2329 2318 """
2330 2319 if node == nullid:
2331 2320 raise error.RevlogError(
2332 2321 _(b"%s: attempt to add null revision") % self.indexfile
2333 2322 )
2334 2323 if node == wdirid or node in wdirfilenodeids:
2335 2324 raise error.RevlogError(
2336 2325 _(b"%s: attempt to add wdir revision") % self.indexfile
2337 2326 )
2338 2327
2339 2328 if self._inline:
2340 2329 fh = ifh
2341 2330 else:
2342 2331 fh = dfh
2343 2332
2344 2333 btext = [rawtext]
2345 2334
2346 2335 curr = len(self)
2347 2336 prev = curr - 1
2348 2337
2349 2338 offset = self._get_data_offset(prev)
2350 2339
2351 2340 if self._concurrencychecker:
2352 2341 if self._inline:
2353 2342 # offset is "as if" it were in the .d file, so we need to add on
2354 2343 # the size of the entry metadata.
2355 2344 self._concurrencychecker(
2356 2345 ifh, self.indexfile, offset + curr * self._io.size
2357 2346 )
2358 2347 else:
2359 2348 # Entries in the .i are a consistent size.
2360 2349 self._concurrencychecker(
2361 2350 ifh, self.indexfile, curr * self._io.size
2362 2351 )
2363 2352 self._concurrencychecker(dfh, self.datafile, offset)
2364 2353
2365 2354 p1r, p2r = self.rev(p1), self.rev(p2)
2366 2355
2367 2356 # full versions are inserted when the needed deltas
2368 2357 # become comparable to the uncompressed text
2369 2358 if rawtext is None:
2370 2359 # need rawtext size, before changed by flag processors, which is
2371 2360 # the non-raw size. use revlog explicitly to avoid filelog's extra
2372 2361 # logic that might remove metadata size.
2373 2362 textlen = mdiff.patchedsize(
2374 2363 revlog.size(self, cachedelta[0]), cachedelta[1]
2375 2364 )
2376 2365 else:
2377 2366 textlen = len(rawtext)
2378 2367
2379 2368 if deltacomputer is None:
2380 2369 deltacomputer = deltautil.deltacomputer(self)
2381 2370
2382 2371 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2383 2372
2384 2373 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2385 2374
2386 2375 if sidedata:
2387 2376 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2388 2377 sidedata_offset = offset + deltainfo.deltalen
2389 2378 else:
2390 2379 serialized_sidedata = b""
2391 2380 # Don't store the offset if the sidedata is empty, that way
2392 2381 # we can easily detect empty sidedata and they will be no different
2393 2382 # than ones we manually add.
2394 2383 sidedata_offset = 0
2395 2384
2396 2385 e = (
2397 2386 offset_type(offset, flags),
2398 2387 deltainfo.deltalen,
2399 2388 textlen,
2400 2389 deltainfo.base,
2401 2390 link,
2402 2391 p1r,
2403 2392 p2r,
2404 2393 node,
2405 2394 sidedata_offset,
2406 2395 len(serialized_sidedata),
2407 2396 )
2408 2397
2409 2398 if self.version & 0xFFFF != REVLOGV2:
2410 2399 e = e[:8]
2411 2400
2412 2401 self.index.append(e)
2413 2402 entry = self._io.packentry(e, self.node, self.version, curr)
2414 2403 self._writeentry(
2415 2404 transaction,
2416 2405 ifh,
2417 2406 dfh,
2418 2407 entry,
2419 2408 deltainfo.data,
2420 2409 link,
2421 2410 offset,
2422 2411 serialized_sidedata,
2423 2412 )
2424 2413
2425 2414 rawtext = btext[0]
2426 2415
2427 2416 if alwayscache and rawtext is None:
2428 2417 rawtext = deltacomputer.buildtext(revinfo, fh)
2429 2418
2430 2419 if type(rawtext) == bytes: # only accept immutable objects
2431 2420 self._revisioncache = (node, curr, rawtext)
2432 2421 self._chainbasecache[curr] = deltainfo.chainbase
2433 2422 return curr
2434 2423
2435 2424 def _get_data_offset(self, prev):
2436 2425 """Returns the current offset in the (in-transaction) data file.
2437 2426 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2438 2427 file to store that information: since sidedata can be rewritten to the
2439 2428 end of the data file within a transaction, you can have cases where, for
2440 2429 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2441 2430 to `n - 1`'s sidedata being written after `n`'s data.
2442 2431
2443 2432 TODO cache this in a docket file before getting out of experimental."""
2444 2433 if self.version & 0xFFFF != REVLOGV2:
2445 2434 return self.end(prev)
2446 2435
2447 2436 offset = 0
2448 2437 for rev, entry in enumerate(self.index):
2449 2438 sidedata_end = entry[8] + entry[9]
2450 2439 # Sidedata for a previous rev has potentially been written after
2451 2440 # this rev's end, so take the max.
2452 2441 offset = max(self.end(rev), offset, sidedata_end)
2453 2442 return offset
2454 2443
2455 2444 def _writeentry(
2456 2445 self, transaction, ifh, dfh, entry, data, link, offset, sidedata
2457 2446 ):
2458 2447 # Files opened in a+ mode have inconsistent behavior on various
2459 2448 # platforms. Windows requires that a file positioning call be made
2460 2449 # when the file handle transitions between reads and writes. See
2461 2450 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2462 2451 # platforms, Python or the platform itself can be buggy. Some versions
2463 2452 # of Solaris have been observed to not append at the end of the file
2464 2453 # if the file was seeked to before the end. See issue4943 for more.
2465 2454 #
2466 2455 # We work around this issue by inserting a seek() before writing.
2467 2456 # Note: This is likely not necessary on Python 3. However, because
2468 2457 # the file handle is reused for reads and may be seeked there, we need
2469 2458 # to be careful before changing this.
2470 2459 ifh.seek(0, os.SEEK_END)
2471 2460 if dfh:
2472 2461 dfh.seek(0, os.SEEK_END)
2473 2462
2474 2463 curr = len(self) - 1
2475 2464 if not self._inline:
2476 2465 transaction.add(self.datafile, offset)
2477 2466 transaction.add(self.indexfile, curr * len(entry))
2478 2467 if data[0]:
2479 2468 dfh.write(data[0])
2480 2469 dfh.write(data[1])
2481 2470 if sidedata:
2482 2471 dfh.write(sidedata)
2483 2472 ifh.write(entry)
2484 2473 else:
2485 2474 offset += curr * self._io.size
2486 2475 transaction.add(self.indexfile, offset)
2487 2476 ifh.write(entry)
2488 2477 ifh.write(data[0])
2489 2478 ifh.write(data[1])
2490 2479 if sidedata:
2491 2480 ifh.write(sidedata)
2492 2481 self._enforceinlinesize(transaction, ifh)
2493 2482 nodemaputil.setup_persistent_nodemap(transaction, self)
2494 2483
2495 2484 def addgroup(
2496 2485 self,
2497 2486 deltas,
2498 2487 linkmapper,
2499 2488 transaction,
2500 2489 alwayscache=False,
2501 2490 addrevisioncb=None,
2502 2491 duplicaterevisioncb=None,
2503 2492 ):
2504 2493 """
2505 2494 add a delta group
2506 2495
2507 2496 given a set of deltas, add them to the revision log. the
2508 2497 first delta is against its parent, which should be in our
2509 2498 log, the rest are against the previous delta.
2510 2499
2511 2500 If ``addrevisioncb`` is defined, it will be called with arguments of
2512 2501 this revlog and the node that was added.
2513 2502 """
2514 2503
2515 2504 if self._writinghandles:
2516 2505 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2517 2506
2518 2507 r = len(self)
2519 2508 end = 0
2520 2509 if r:
2521 2510 end = self.end(r - 1)
2522 2511 ifh = self._indexfp(b"a+")
2523 2512 isize = r * self._io.size
2524 2513 if self._inline:
2525 2514 transaction.add(self.indexfile, end + isize)
2526 2515 dfh = None
2527 2516 else:
2528 2517 transaction.add(self.indexfile, isize)
2529 2518 transaction.add(self.datafile, end)
2530 2519 dfh = self._datafp(b"a+")
2531 2520
2532 2521 def flush():
2533 2522 if dfh:
2534 2523 dfh.flush()
2535 2524 ifh.flush()
2536 2525
2537 2526 self._writinghandles = (ifh, dfh)
2538 2527 empty = True
2539 2528
2540 2529 try:
2541 2530 deltacomputer = deltautil.deltacomputer(self)
2542 2531 # loop through our set of deltas
2543 2532 for data in deltas:
2544 2533 node, p1, p2, linknode, deltabase, delta, flags, sidedata = data
2545 2534 link = linkmapper(linknode)
2546 2535 flags = flags or REVIDX_DEFAULT_FLAGS
2547 2536
2548 2537 rev = self.index.get_rev(node)
2549 2538 if rev is not None:
2550 2539 # this can happen if two branches make the same change
2551 2540 self._nodeduplicatecallback(transaction, rev)
2552 2541 if duplicaterevisioncb:
2553 2542 duplicaterevisioncb(self, rev)
2554 2543 empty = False
2555 2544 continue
2556 2545
2557 2546 for p in (p1, p2):
2558 2547 if not self.index.has_node(p):
2559 2548 raise error.LookupError(
2560 2549 p, self.indexfile, _(b'unknown parent')
2561 2550 )
2562 2551
2563 2552 if not self.index.has_node(deltabase):
2564 2553 raise error.LookupError(
2565 2554 deltabase, self.indexfile, _(b'unknown delta base')
2566 2555 )
2567 2556
2568 2557 baserev = self.rev(deltabase)
2569 2558
2570 2559 if baserev != nullrev and self.iscensored(baserev):
2571 2560 # if base is censored, delta must be full replacement in a
2572 2561 # single patch operation
2573 2562 hlen = struct.calcsize(b">lll")
2574 2563 oldlen = self.rawsize(baserev)
2575 2564 newlen = len(delta) - hlen
2576 2565 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2577 2566 raise error.CensoredBaseError(
2578 2567 self.indexfile, self.node(baserev)
2579 2568 )
2580 2569
2581 2570 if not flags and self._peek_iscensored(baserev, delta, flush):
2582 2571 flags |= REVIDX_ISCENSORED
2583 2572
2584 2573 # We assume consumers of addrevisioncb will want to retrieve
2585 2574 # the added revision, which will require a call to
2586 2575 # revision(). revision() will fast path if there is a cache
2587 2576 # hit. So, we tell _addrevision() to always cache in this case.
2588 2577 # We're only using addgroup() in the context of changegroup
2589 2578 # generation so the revision data can always be handled as raw
2590 2579 # by the flagprocessor.
2591 2580 rev = self._addrevision(
2592 2581 node,
2593 2582 None,
2594 2583 transaction,
2595 2584 link,
2596 2585 p1,
2597 2586 p2,
2598 2587 flags,
2599 2588 (baserev, delta),
2600 2589 ifh,
2601 2590 dfh,
2602 2591 alwayscache=alwayscache,
2603 2592 deltacomputer=deltacomputer,
2604 2593 sidedata=sidedata,
2605 2594 )
2606 2595
2607 2596 if addrevisioncb:
2608 2597 addrevisioncb(self, rev)
2609 2598 empty = False
2610 2599
2611 2600 if not dfh and not self._inline:
2612 2601 # addrevision switched from inline to conventional
2613 2602 # reopen the index
2614 2603 ifh.close()
2615 2604 dfh = self._datafp(b"a+")
2616 2605 ifh = self._indexfp(b"a+")
2617 2606 self._writinghandles = (ifh, dfh)
2618 2607 finally:
2619 2608 self._writinghandles = None
2620 2609
2621 2610 if dfh:
2622 2611 dfh.close()
2623 2612 ifh.close()
2624 2613 return not empty
2625 2614
2626 2615 def iscensored(self, rev):
2627 2616 """Check if a file revision is censored."""
2628 2617 if not self._censorable:
2629 2618 return False
2630 2619
2631 2620 return self.flags(rev) & REVIDX_ISCENSORED
2632 2621
2633 2622 def _peek_iscensored(self, baserev, delta, flush):
2634 2623 """Quickly check if a delta produces a censored revision."""
2635 2624 if not self._censorable:
2636 2625 return False
2637 2626
2638 2627 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2639 2628
2640 2629 def getstrippoint(self, minlink):
2641 2630 """find the minimum rev that must be stripped to strip the linkrev
2642 2631
2643 2632 Returns a tuple containing the minimum rev and a set of all revs that
2644 2633 have linkrevs that will be broken by this strip.
2645 2634 """
2646 2635 return storageutil.resolvestripinfo(
2647 2636 minlink,
2648 2637 len(self) - 1,
2649 2638 self.headrevs(),
2650 2639 self.linkrev,
2651 2640 self.parentrevs,
2652 2641 )
2653 2642
2654 2643 def strip(self, minlink, transaction):
2655 2644 """truncate the revlog on the first revision with a linkrev >= minlink
2656 2645
2657 2646 This function is called when we're stripping revision minlink and
2658 2647 its descendants from the repository.
2659 2648
2660 2649 We have to remove all revisions with linkrev >= minlink, because
2661 2650 the equivalent changelog revisions will be renumbered after the
2662 2651 strip.
2663 2652
2664 2653 So we truncate the revlog on the first of these revisions, and
2665 2654 trust that the caller has saved the revisions that shouldn't be
2666 2655 removed and that it'll re-add them after this truncation.
2667 2656 """
2668 2657 if len(self) == 0:
2669 2658 return
2670 2659
2671 2660 rev, _ = self.getstrippoint(minlink)
2672 2661 if rev == len(self):
2673 2662 return
2674 2663
2675 2664 # first truncate the files on disk
2676 2665 end = self.start(rev)
2677 2666 if not self._inline:
2678 2667 transaction.add(self.datafile, end)
2679 2668 end = rev * self._io.size
2680 2669 else:
2681 2670 end += rev * self._io.size
2682 2671
2683 2672 transaction.add(self.indexfile, end)
2684 2673
2685 2674 # then reset internal state in memory to forget those revisions
2686 2675 self._revisioncache = None
2687 2676 self._chaininfocache = util.lrucachedict(500)
2688 2677 self._chunkclear()
2689 2678
2690 2679 del self.index[rev:-1]
2691 2680
2692 2681 def checksize(self):
2693 2682 """Check size of index and data files
2694 2683
2695 2684 return a (dd, di) tuple.
2696 2685 - dd: extra bytes for the "data" file
2697 2686 - di: extra bytes for the "index" file
2698 2687
2699 2688 A healthy revlog will return (0, 0).
2700 2689 """
2701 2690 expected = 0
2702 2691 if len(self):
2703 2692 expected = max(0, self.end(len(self) - 1))
2704 2693
2705 2694 try:
2706 2695 with self._datafp() as f:
2707 2696 f.seek(0, io.SEEK_END)
2708 2697 actual = f.tell()
2709 2698 dd = actual - expected
2710 2699 except IOError as inst:
2711 2700 if inst.errno != errno.ENOENT:
2712 2701 raise
2713 2702 dd = 0
2714 2703
2715 2704 try:
2716 2705 f = self.opener(self.indexfile)
2717 2706 f.seek(0, io.SEEK_END)
2718 2707 actual = f.tell()
2719 2708 f.close()
2720 2709 s = self._io.size
2721 2710 i = max(0, actual // s)
2722 2711 di = actual - (i * s)
2723 2712 if self._inline:
2724 2713 databytes = 0
2725 2714 for r in self:
2726 2715 databytes += max(0, self.length(r))
2727 2716 dd = 0
2728 2717 di = actual - len(self) * s - databytes
2729 2718 except IOError as inst:
2730 2719 if inst.errno != errno.ENOENT:
2731 2720 raise
2732 2721 di = 0
2733 2722
2734 2723 return (dd, di)
2735 2724
2736 2725 def files(self):
2737 2726 res = [self.indexfile]
2738 2727 if not self._inline:
2739 2728 res.append(self.datafile)
2740 2729 return res
2741 2730
2742 2731 def emitrevisions(
2743 2732 self,
2744 2733 nodes,
2745 2734 nodesorder=None,
2746 2735 revisiondata=False,
2747 2736 assumehaveparentrevisions=False,
2748 2737 deltamode=repository.CG_DELTAMODE_STD,
2749 2738 sidedata_helpers=None,
2750 2739 ):
2751 2740 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2752 2741 raise error.ProgrammingError(
2753 2742 b'unhandled value for nodesorder: %s' % nodesorder
2754 2743 )
2755 2744
2756 2745 if nodesorder is None and not self._generaldelta:
2757 2746 nodesorder = b'storage'
2758 2747
2759 2748 if (
2760 2749 not self._storedeltachains
2761 2750 and deltamode != repository.CG_DELTAMODE_PREV
2762 2751 ):
2763 2752 deltamode = repository.CG_DELTAMODE_FULL
2764 2753
2765 2754 return storageutil.emitrevisions(
2766 2755 self,
2767 2756 nodes,
2768 2757 nodesorder,
2769 2758 revlogrevisiondelta,
2770 2759 deltaparentfn=self.deltaparent,
2771 2760 candeltafn=self.candelta,
2772 2761 rawsizefn=self.rawsize,
2773 2762 revdifffn=self.revdiff,
2774 2763 flagsfn=self.flags,
2775 2764 deltamode=deltamode,
2776 2765 revisiondata=revisiondata,
2777 2766 assumehaveparentrevisions=assumehaveparentrevisions,
2778 2767 sidedata_helpers=sidedata_helpers,
2779 2768 )
2780 2769
2781 2770 DELTAREUSEALWAYS = b'always'
2782 2771 DELTAREUSESAMEREVS = b'samerevs'
2783 2772 DELTAREUSENEVER = b'never'
2784 2773
2785 2774 DELTAREUSEFULLADD = b'fulladd'
2786 2775
2787 2776 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2788 2777
2789 2778 def clone(
2790 2779 self,
2791 2780 tr,
2792 2781 destrevlog,
2793 2782 addrevisioncb=None,
2794 2783 deltareuse=DELTAREUSESAMEREVS,
2795 2784 forcedeltabothparents=None,
2796 2785 sidedatacompanion=None,
2797 2786 ):
2798 2787 """Copy this revlog to another, possibly with format changes.
2799 2788
2800 2789 The destination revlog will contain the same revisions and nodes.
2801 2790 However, it may not be bit-for-bit identical due to e.g. delta encoding
2802 2791 differences.
2803 2792
2804 2793 The ``deltareuse`` argument control how deltas from the existing revlog
2805 2794 are preserved in the destination revlog. The argument can have the
2806 2795 following values:
2807 2796
2808 2797 DELTAREUSEALWAYS
2809 2798 Deltas will always be reused (if possible), even if the destination
2810 2799 revlog would not select the same revisions for the delta. This is the
2811 2800 fastest mode of operation.
2812 2801 DELTAREUSESAMEREVS
2813 2802 Deltas will be reused if the destination revlog would pick the same
2814 2803 revisions for the delta. This mode strikes a balance between speed
2815 2804 and optimization.
2816 2805 DELTAREUSENEVER
2817 2806 Deltas will never be reused. This is the slowest mode of execution.
2818 2807 This mode can be used to recompute deltas (e.g. if the diff/delta
2819 2808 algorithm changes).
2820 2809 DELTAREUSEFULLADD
2821 2810 Revision will be re-added as if their were new content. This is
2822 2811 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2823 2812 eg: large file detection and handling.
2824 2813
2825 2814 Delta computation can be slow, so the choice of delta reuse policy can
2826 2815 significantly affect run time.
2827 2816
2828 2817 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2829 2818 two extremes. Deltas will be reused if they are appropriate. But if the
2830 2819 delta could choose a better revision, it will do so. This means if you
2831 2820 are converting a non-generaldelta revlog to a generaldelta revlog,
2832 2821 deltas will be recomputed if the delta's parent isn't a parent of the
2833 2822 revision.
2834 2823
2835 2824 In addition to the delta policy, the ``forcedeltabothparents``
2836 2825 argument controls whether to force compute deltas against both parents
2837 2826 for merges. By default, the current default is used.
2838 2827
2839 2828 If not None, the `sidedatacompanion` is callable that accept two
2840 2829 arguments:
2841 2830
2842 2831 (srcrevlog, rev)
2843 2832
2844 2833 and return a quintet that control changes to sidedata content from the
2845 2834 old revision to the new clone result:
2846 2835
2847 2836 (dropall, filterout, update, new_flags, dropped_flags)
2848 2837
2849 2838 * if `dropall` is True, all sidedata should be dropped
2850 2839 * `filterout` is a set of sidedata keys that should be dropped
2851 2840 * `update` is a mapping of additionnal/new key -> value
2852 2841 * new_flags is a bitfields of new flags that the revision should get
2853 2842 * dropped_flags is a bitfields of new flags that the revision shoudl not longer have
2854 2843 """
2855 2844 if deltareuse not in self.DELTAREUSEALL:
2856 2845 raise ValueError(
2857 2846 _(b'value for deltareuse invalid: %s') % deltareuse
2858 2847 )
2859 2848
2860 2849 if len(destrevlog):
2861 2850 raise ValueError(_(b'destination revlog is not empty'))
2862 2851
2863 2852 if getattr(self, 'filteredrevs', None):
2864 2853 raise ValueError(_(b'source revlog has filtered revisions'))
2865 2854 if getattr(destrevlog, 'filteredrevs', None):
2866 2855 raise ValueError(_(b'destination revlog has filtered revisions'))
2867 2856
2868 2857 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2869 2858 # if possible.
2870 2859 oldlazydelta = destrevlog._lazydelta
2871 2860 oldlazydeltabase = destrevlog._lazydeltabase
2872 2861 oldamd = destrevlog._deltabothparents
2873 2862
2874 2863 try:
2875 2864 if deltareuse == self.DELTAREUSEALWAYS:
2876 2865 destrevlog._lazydeltabase = True
2877 2866 destrevlog._lazydelta = True
2878 2867 elif deltareuse == self.DELTAREUSESAMEREVS:
2879 2868 destrevlog._lazydeltabase = False
2880 2869 destrevlog._lazydelta = True
2881 2870 elif deltareuse == self.DELTAREUSENEVER:
2882 2871 destrevlog._lazydeltabase = False
2883 2872 destrevlog._lazydelta = False
2884 2873
2885 2874 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2886 2875
2887 2876 self._clone(
2888 2877 tr,
2889 2878 destrevlog,
2890 2879 addrevisioncb,
2891 2880 deltareuse,
2892 2881 forcedeltabothparents,
2893 2882 sidedatacompanion,
2894 2883 )
2895 2884
2896 2885 finally:
2897 2886 destrevlog._lazydelta = oldlazydelta
2898 2887 destrevlog._lazydeltabase = oldlazydeltabase
2899 2888 destrevlog._deltabothparents = oldamd
2900 2889
2901 2890 def _clone(
2902 2891 self,
2903 2892 tr,
2904 2893 destrevlog,
2905 2894 addrevisioncb,
2906 2895 deltareuse,
2907 2896 forcedeltabothparents,
2908 2897 sidedatacompanion,
2909 2898 ):
2910 2899 """perform the core duty of `revlog.clone` after parameter processing"""
2911 2900 deltacomputer = deltautil.deltacomputer(destrevlog)
2912 2901 index = self.index
2913 2902 for rev in self:
2914 2903 entry = index[rev]
2915 2904
2916 2905 # Some classes override linkrev to take filtered revs into
2917 2906 # account. Use raw entry from index.
2918 2907 flags = entry[0] & 0xFFFF
2919 2908 linkrev = entry[4]
2920 2909 p1 = index[entry[5]][7]
2921 2910 p2 = index[entry[6]][7]
2922 2911 node = entry[7]
2923 2912
2924 2913 sidedataactions = (False, [], {}, 0, 0)
2925 2914 if sidedatacompanion is not None:
2926 2915 sidedataactions = sidedatacompanion(self, rev)
2927 2916
2928 2917 # (Possibly) reuse the delta from the revlog if allowed and
2929 2918 # the revlog chunk is a delta.
2930 2919 cachedelta = None
2931 2920 rawtext = None
2932 2921 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2933 2922 dropall = sidedataactions[0]
2934 2923 filterout = sidedataactions[1]
2935 2924 update = sidedataactions[2]
2936 2925 new_flags = sidedataactions[3]
2937 2926 dropped_flags = sidedataactions[4]
2938 2927 text, sidedata = self._revisiondata(rev)
2939 2928 if dropall:
2940 2929 sidedata = {}
2941 2930 for key in filterout:
2942 2931 sidedata.pop(key, None)
2943 2932 sidedata.update(update)
2944 2933 if not sidedata:
2945 2934 sidedata = None
2946 2935
2947 2936 flags |= new_flags
2948 2937 flags &= ~dropped_flags
2949 2938
2950 2939 destrevlog.addrevision(
2951 2940 text,
2952 2941 tr,
2953 2942 linkrev,
2954 2943 p1,
2955 2944 p2,
2956 2945 cachedelta=cachedelta,
2957 2946 node=node,
2958 2947 flags=flags,
2959 2948 deltacomputer=deltacomputer,
2960 2949 sidedata=sidedata,
2961 2950 )
2962 2951 else:
2963 2952 if destrevlog._lazydelta:
2964 2953 dp = self.deltaparent(rev)
2965 2954 if dp != nullrev:
2966 2955 cachedelta = (dp, bytes(self._chunk(rev)))
2967 2956
2968 2957 if not cachedelta:
2969 2958 rawtext = self.rawdata(rev)
2970 2959
2971 2960 ifh = destrevlog.opener(
2972 2961 destrevlog.indexfile, b'a+', checkambig=False
2973 2962 )
2974 2963 dfh = None
2975 2964 if not destrevlog._inline:
2976 2965 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2977 2966 try:
2978 2967 destrevlog._addrevision(
2979 2968 node,
2980 2969 rawtext,
2981 2970 tr,
2982 2971 linkrev,
2983 2972 p1,
2984 2973 p2,
2985 2974 flags,
2986 2975 cachedelta,
2987 2976 ifh,
2988 2977 dfh,
2989 2978 deltacomputer=deltacomputer,
2990 2979 )
2991 2980 finally:
2992 2981 if dfh:
2993 2982 dfh.close()
2994 2983 ifh.close()
2995 2984
2996 2985 if addrevisioncb:
2997 2986 addrevisioncb(self, rev, node)
2998 2987
2999 2988 def censorrevision(self, tr, censornode, tombstone=b''):
3000 2989 if (self.version & 0xFFFF) == REVLOGV0:
3001 2990 raise error.RevlogError(
3002 2991 _(b'cannot censor with version %d revlogs') % self.version
3003 2992 )
3004 2993
3005 2994 censorrev = self.rev(censornode)
3006 2995 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
3007 2996
3008 2997 if len(tombstone) > self.rawsize(censorrev):
3009 2998 raise error.Abort(
3010 2999 _(b'censor tombstone must be no longer than censored data')
3011 3000 )
3012 3001
3013 3002 # Rewriting the revlog in place is hard. Our strategy for censoring is
3014 3003 # to create a new revlog, copy all revisions to it, then replace the
3015 3004 # revlogs on transaction close.
3016 3005
3017 3006 newindexfile = self.indexfile + b'.tmpcensored'
3018 3007 newdatafile = self.datafile + b'.tmpcensored'
3019 3008
3020 3009 # This is a bit dangerous. We could easily have a mismatch of state.
3021 3010 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
3022 3011 newrl.version = self.version
3023 3012 newrl._generaldelta = self._generaldelta
3024 3013 newrl._io = self._io
3025 3014
3026 3015 for rev in self.revs():
3027 3016 node = self.node(rev)
3028 3017 p1, p2 = self.parents(node)
3029 3018
3030 3019 if rev == censorrev:
3031 3020 newrl.addrawrevision(
3032 3021 tombstone,
3033 3022 tr,
3034 3023 self.linkrev(censorrev),
3035 3024 p1,
3036 3025 p2,
3037 3026 censornode,
3038 3027 REVIDX_ISCENSORED,
3039 3028 )
3040 3029
3041 3030 if newrl.deltaparent(rev) != nullrev:
3042 3031 raise error.Abort(
3043 3032 _(
3044 3033 b'censored revision stored as delta; '
3045 3034 b'cannot censor'
3046 3035 ),
3047 3036 hint=_(
3048 3037 b'censoring of revlogs is not '
3049 3038 b'fully implemented; please report '
3050 3039 b'this bug'
3051 3040 ),
3052 3041 )
3053 3042 continue
3054 3043
3055 3044 if self.iscensored(rev):
3056 3045 if self.deltaparent(rev) != nullrev:
3057 3046 raise error.Abort(
3058 3047 _(
3059 3048 b'cannot censor due to censored '
3060 3049 b'revision having delta stored'
3061 3050 )
3062 3051 )
3063 3052 rawtext = self._chunk(rev)
3064 3053 else:
3065 3054 rawtext = self.rawdata(rev)
3066 3055
3067 3056 newrl.addrawrevision(
3068 3057 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
3069 3058 )
3070 3059
3071 3060 tr.addbackup(self.indexfile, location=b'store')
3072 3061 if not self._inline:
3073 3062 tr.addbackup(self.datafile, location=b'store')
3074 3063
3075 3064 self.opener.rename(newrl.indexfile, self.indexfile)
3076 3065 if not self._inline:
3077 3066 self.opener.rename(newrl.datafile, self.datafile)
3078 3067
3079 3068 self.clearcaches()
3080 3069 self._loadindex()
3081 3070
3082 3071 def verifyintegrity(self, state):
3083 3072 """Verifies the integrity of the revlog.
3084 3073
3085 3074 Yields ``revlogproblem`` instances describing problems that are
3086 3075 found.
3087 3076 """
3088 3077 dd, di = self.checksize()
3089 3078 if dd:
3090 3079 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3091 3080 if di:
3092 3081 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3093 3082
3094 3083 version = self.version & 0xFFFF
3095 3084
3096 3085 # The verifier tells us what version revlog we should be.
3097 3086 if version != state[b'expectedversion']:
3098 3087 yield revlogproblem(
3099 3088 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3100 3089 % (self.indexfile, version, state[b'expectedversion'])
3101 3090 )
3102 3091
3103 3092 state[b'skipread'] = set()
3104 3093 state[b'safe_renamed'] = set()
3105 3094
3106 3095 for rev in self:
3107 3096 node = self.node(rev)
3108 3097
3109 3098 # Verify contents. 4 cases to care about:
3110 3099 #
3111 3100 # common: the most common case
3112 3101 # rename: with a rename
3113 3102 # meta: file content starts with b'\1\n', the metadata
3114 3103 # header defined in filelog.py, but without a rename
3115 3104 # ext: content stored externally
3116 3105 #
3117 3106 # More formally, their differences are shown below:
3118 3107 #
3119 3108 # | common | rename | meta | ext
3120 3109 # -------------------------------------------------------
3121 3110 # flags() | 0 | 0 | 0 | not 0
3122 3111 # renamed() | False | True | False | ?
3123 3112 # rawtext[0:2]=='\1\n'| False | True | True | ?
3124 3113 #
3125 3114 # "rawtext" means the raw text stored in revlog data, which
3126 3115 # could be retrieved by "rawdata(rev)". "text"
3127 3116 # mentioned below is "revision(rev)".
3128 3117 #
3129 3118 # There are 3 different lengths stored physically:
3130 3119 # 1. L1: rawsize, stored in revlog index
3131 3120 # 2. L2: len(rawtext), stored in revlog data
3132 3121 # 3. L3: len(text), stored in revlog data if flags==0, or
3133 3122 # possibly somewhere else if flags!=0
3134 3123 #
3135 3124 # L1 should be equal to L2. L3 could be different from them.
3136 3125 # "text" may or may not affect commit hash depending on flag
3137 3126 # processors (see flagutil.addflagprocessor).
3138 3127 #
3139 3128 # | common | rename | meta | ext
3140 3129 # -------------------------------------------------
3141 3130 # rawsize() | L1 | L1 | L1 | L1
3142 3131 # size() | L1 | L2-LM | L1(*) | L1 (?)
3143 3132 # len(rawtext) | L2 | L2 | L2 | L2
3144 3133 # len(text) | L2 | L2 | L2 | L3
3145 3134 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3146 3135 #
3147 3136 # LM: length of metadata, depending on rawtext
3148 3137 # (*): not ideal, see comment in filelog.size
3149 3138 # (?): could be "- len(meta)" if the resolved content has
3150 3139 # rename metadata
3151 3140 #
3152 3141 # Checks needed to be done:
3153 3142 # 1. length check: L1 == L2, in all cases.
3154 3143 # 2. hash check: depending on flag processor, we may need to
3155 3144 # use either "text" (external), or "rawtext" (in revlog).
3156 3145
3157 3146 try:
3158 3147 skipflags = state.get(b'skipflags', 0)
3159 3148 if skipflags:
3160 3149 skipflags &= self.flags(rev)
3161 3150
3162 3151 _verify_revision(self, skipflags, state, node)
3163 3152
3164 3153 l1 = self.rawsize(rev)
3165 3154 l2 = len(self.rawdata(node))
3166 3155
3167 3156 if l1 != l2:
3168 3157 yield revlogproblem(
3169 3158 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3170 3159 node=node,
3171 3160 )
3172 3161
3173 3162 except error.CensoredNodeError:
3174 3163 if state[b'erroroncensored']:
3175 3164 yield revlogproblem(
3176 3165 error=_(b'censored file data'), node=node
3177 3166 )
3178 3167 state[b'skipread'].add(node)
3179 3168 except Exception as e:
3180 3169 yield revlogproblem(
3181 3170 error=_(b'unpacking %s: %s')
3182 3171 % (short(node), stringutil.forcebytestr(e)),
3183 3172 node=node,
3184 3173 )
3185 3174 state[b'skipread'].add(node)
3186 3175
3187 3176 def storageinfo(
3188 3177 self,
3189 3178 exclusivefiles=False,
3190 3179 sharedfiles=False,
3191 3180 revisionscount=False,
3192 3181 trackedsize=False,
3193 3182 storedsize=False,
3194 3183 ):
3195 3184 d = {}
3196 3185
3197 3186 if exclusivefiles:
3198 3187 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3199 3188 if not self._inline:
3200 3189 d[b'exclusivefiles'].append((self.opener, self.datafile))
3201 3190
3202 3191 if sharedfiles:
3203 3192 d[b'sharedfiles'] = []
3204 3193
3205 3194 if revisionscount:
3206 3195 d[b'revisionscount'] = len(self)
3207 3196
3208 3197 if trackedsize:
3209 3198 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3210 3199
3211 3200 if storedsize:
3212 3201 d[b'storedsize'] = sum(
3213 3202 self.opener.stat(path).st_size for path in self.files()
3214 3203 )
3215 3204
3216 3205 return d
3217 3206
3218 3207 def rewrite_sidedata(self, helpers, startrev, endrev):
3219 3208 if self.version & 0xFFFF != REVLOGV2:
3220 3209 return
3221 3210 # inline are not yet supported because they suffer from an issue when
3222 3211 # rewriting them (since it's not an append-only operation).
3223 3212 # See issue6485.
3224 3213 assert not self._inline
3225 3214 if not helpers[1] and not helpers[2]:
3226 3215 # Nothing to generate or remove
3227 3216 return
3228 3217
3229 3218 new_entries = []
3230 3219 # append the new sidedata
3231 3220 with self._datafp(b'a+') as fp:
3232 3221 # Maybe this bug still exists, see revlog._writeentry
3233 3222 fp.seek(0, os.SEEK_END)
3234 3223 current_offset = fp.tell()
3235 3224 for rev in range(startrev, endrev + 1):
3236 3225 entry = self.index[rev]
3237 3226 new_sidedata = storageutil.run_sidedata_helpers(
3238 3227 store=self,
3239 3228 sidedata_helpers=helpers,
3240 3229 sidedata={},
3241 3230 rev=rev,
3242 3231 )
3243 3232
3244 3233 serialized_sidedata = sidedatautil.serialize_sidedata(
3245 3234 new_sidedata
3246 3235 )
3247 3236 if entry[8] != 0 or entry[9] != 0:
3248 3237 # rewriting entries that already have sidedata is not
3249 3238 # supported yet, because it introduces garbage data in the
3250 3239 # revlog.
3251 3240 msg = b"Rewriting existing sidedata is not supported yet"
3252 3241 raise error.Abort(msg)
3253 3242 entry = entry[:8]
3254 3243 entry += (current_offset, len(serialized_sidedata))
3255 3244
3256 3245 fp.write(serialized_sidedata)
3257 3246 new_entries.append(entry)
3258 3247 current_offset += len(serialized_sidedata)
3259 3248
3260 3249 # rewrite the new index entries
3261 3250 with self._indexfp(b'w+') as fp:
3262 3251 fp.seek(startrev * self._io.size)
3263 3252 for i, entry in enumerate(new_entries):
3264 3253 rev = startrev + i
3265 3254 self.index.replace_sidedata_info(rev, entry[8], entry[9])
3266 3255 packed = self._io.packentry(entry, self.node, self.version, rev)
3267 3256 fp.write(packed)
@@ -1,77 +1,90 b''
1 1 # revlogdeltas.py - constant used for revlog logic
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 4 # Copyright 2018 Octobus <contact@octobus.net>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8 """Helper class to compute deltas stored inside revlogs"""
9 9
10 10 from __future__ import absolute_import
11 11
12 12 import struct
13 13
14 14 from ..interfaces import repository
15 15
16 16 ### main revlog header
17 17
18 18 ## revlog version
19 19 REVLOGV0 = 0
20 20 REVLOGV1 = 1
21 21 # Dummy value until file format is finalized.
22 22 REVLOGV2 = 0xDEAD
23 23
24 24 ## global revlog header flags
25 25 # Shared across v1 and v2.
26 26 FLAG_INLINE_DATA = 1 << 16
27 27 # Only used by v1, implied by v2.
28 28 FLAG_GENERALDELTA = 1 << 17
29 29 REVLOG_DEFAULT_FLAGS = FLAG_INLINE_DATA
30 30 REVLOG_DEFAULT_FORMAT = REVLOGV1
31 31 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
32 32 REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA
33 33 REVLOGV2_FLAGS = FLAG_INLINE_DATA
34 34
35 35 ### individual entry
36 36
37 37 ## index v0:
38 38 # 4 bytes: offset
39 39 # 4 bytes: compressed length
40 40 # 4 bytes: base rev
41 41 # 4 bytes: link rev
42 42 # 20 bytes: parent 1 nodeid
43 43 # 20 bytes: parent 2 nodeid
44 44 # 20 bytes: nodeid
45 45 INDEX_ENTRY_V0 = struct.Struct(b">4l20s20s20s")
46 46
47 ## index v1
48 # 6 bytes: offset
49 # 2 bytes: flags
50 # 4 bytes: compressed length
51 # 4 bytes: uncompressed length
52 # 4 bytes: base rev
53 # 4 bytes: link rev
54 # 4 bytes: parent 1 rev
55 # 4 bytes: parent 2 rev
56 # 32 bytes: nodeid
57 INDEX_ENTRY_V1 = struct.Struct(b">Qiiiiii20s12x")
58 assert INDEX_ENTRY_V1.size == 32 * 2
59
47 60 # revlog index flags
48 61
49 62 # For historical reasons, revlog's internal flags were exposed via the
50 63 # wire protocol and are even exposed in parts of the storage APIs.
51 64
52 65 # revision has censor metadata, must be verified
53 66 REVIDX_ISCENSORED = repository.REVISION_FLAG_CENSORED
54 67 # revision hash does not match data (narrowhg)
55 68 REVIDX_ELLIPSIS = repository.REVISION_FLAG_ELLIPSIS
56 69 # revision data is stored externally
57 70 REVIDX_EXTSTORED = repository.REVISION_FLAG_EXTSTORED
58 71 # revision data contains extra metadata not part of the official digest
59 72 REVIDX_SIDEDATA = repository.REVISION_FLAG_SIDEDATA
60 73 # revision changes files in a way that could affect copy tracing.
61 74 REVIDX_HASCOPIESINFO = repository.REVISION_FLAG_HASCOPIESINFO
62 75 REVIDX_DEFAULT_FLAGS = 0
63 76 # stable order in which flags need to be processed and their processors applied
64 77 REVIDX_FLAGS_ORDER = [
65 78 REVIDX_ISCENSORED,
66 79 REVIDX_ELLIPSIS,
67 80 REVIDX_EXTSTORED,
68 81 REVIDX_SIDEDATA,
69 82 REVIDX_HASCOPIESINFO,
70 83 ]
71 84
72 85 # bitmark for flags that could cause rawdata content change
73 86 REVIDX_RAWTEXT_CHANGING_FLAGS = (
74 87 REVIDX_ISCENSORED | REVIDX_EXTSTORED | REVIDX_SIDEDATA
75 88 )
76 89
77 90 SPARSE_REVLOG_MAX_CHAIN_LENGTH = 1000
General Comments 0
You need to be logged in to leave comments. Login now