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