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