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