##// END OF EJS Templates
delta-find: move pre-filtering with other pre-filtering logic...
marmoute -
r50508:e706bb41 default
parent child Browse files
Show More
@@ -1,1396 +1,1398 b''
1 1 # revlogdeltas.py - Logic around delta computation for revlog
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 4 # Copyright 2018 Octobus <contact@octobus.net>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8 """Helper class to compute deltas stored inside revlogs"""
9 9
10 10
11 11 import collections
12 12 import struct
13 13
14 14 # import stuff from node for others to import from revlog
15 15 from ..node import nullrev
16 16 from ..i18n import _
17 17 from ..pycompat import getattr
18 18
19 19 from .constants import (
20 20 COMP_MODE_DEFAULT,
21 21 COMP_MODE_INLINE,
22 22 COMP_MODE_PLAIN,
23 23 KIND_CHANGELOG,
24 24 KIND_FILELOG,
25 25 KIND_MANIFESTLOG,
26 26 REVIDX_ISCENSORED,
27 27 REVIDX_RAWTEXT_CHANGING_FLAGS,
28 28 )
29 29
30 30 from ..thirdparty import attr
31 31
32 32 from .. import (
33 33 error,
34 34 mdiff,
35 35 util,
36 36 )
37 37
38 38 from . import flagutil
39 39
40 40 # maximum <delta-chain-data>/<revision-text-length> ratio
41 41 LIMIT_DELTA2TEXT = 2
42 42
43 43
44 44 class _testrevlog:
45 45 """minimalist fake revlog to use in doctests"""
46 46
47 47 def __init__(self, data, density=0.5, mingap=0, snapshot=()):
48 48 """data is an list of revision payload boundaries"""
49 49 self._data = data
50 50 self._srdensitythreshold = density
51 51 self._srmingapsize = mingap
52 52 self._snapshot = set(snapshot)
53 53 self.index = None
54 54
55 55 def start(self, rev):
56 56 if rev == nullrev:
57 57 return 0
58 58 if rev == 0:
59 59 return 0
60 60 return self._data[rev - 1]
61 61
62 62 def end(self, rev):
63 63 if rev == nullrev:
64 64 return 0
65 65 return self._data[rev]
66 66
67 67 def length(self, rev):
68 68 return self.end(rev) - self.start(rev)
69 69
70 70 def __len__(self):
71 71 return len(self._data)
72 72
73 73 def issnapshot(self, rev):
74 74 if rev == nullrev:
75 75 return True
76 76 return rev in self._snapshot
77 77
78 78
79 79 def slicechunk(revlog, revs, targetsize=None):
80 80 """slice revs to reduce the amount of unrelated data to be read from disk.
81 81
82 82 ``revs`` is sliced into groups that should be read in one time.
83 83 Assume that revs are sorted.
84 84
85 85 The initial chunk is sliced until the overall density (payload/chunks-span
86 86 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
87 87 `revlog._srmingapsize` is skipped.
88 88
89 89 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
90 90 For consistency with other slicing choice, this limit won't go lower than
91 91 `revlog._srmingapsize`.
92 92
93 93 If individual revisions chunk are larger than this limit, they will still
94 94 be raised individually.
95 95
96 96 >>> data = [
97 97 ... 5, #00 (5)
98 98 ... 10, #01 (5)
99 99 ... 12, #02 (2)
100 100 ... 12, #03 (empty)
101 101 ... 27, #04 (15)
102 102 ... 31, #05 (4)
103 103 ... 31, #06 (empty)
104 104 ... 42, #07 (11)
105 105 ... 47, #08 (5)
106 106 ... 47, #09 (empty)
107 107 ... 48, #10 (1)
108 108 ... 51, #11 (3)
109 109 ... 74, #12 (23)
110 110 ... 85, #13 (11)
111 111 ... 86, #14 (1)
112 112 ... 91, #15 (5)
113 113 ... ]
114 114 >>> revlog = _testrevlog(data, snapshot=range(16))
115 115
116 116 >>> list(slicechunk(revlog, list(range(16))))
117 117 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
118 118 >>> list(slicechunk(revlog, [0, 15]))
119 119 [[0], [15]]
120 120 >>> list(slicechunk(revlog, [0, 11, 15]))
121 121 [[0], [11], [15]]
122 122 >>> list(slicechunk(revlog, [0, 11, 13, 15]))
123 123 [[0], [11, 13, 15]]
124 124 >>> list(slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
125 125 [[1, 2], [5, 8, 10, 11], [14]]
126 126
127 127 Slicing with a maximum chunk size
128 128 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
129 129 [[0], [11], [13], [15]]
130 130 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
131 131 [[0], [11], [13, 15]]
132 132
133 133 Slicing involving nullrev
134 134 >>> list(slicechunk(revlog, [-1, 0, 11, 13, 15], targetsize=20))
135 135 [[-1, 0], [11], [13, 15]]
136 136 >>> list(slicechunk(revlog, [-1, 13, 15], targetsize=5))
137 137 [[-1], [13], [15]]
138 138 """
139 139 if targetsize is not None:
140 140 targetsize = max(targetsize, revlog._srmingapsize)
141 141 # targetsize should not be specified when evaluating delta candidates:
142 142 # * targetsize is used to ensure we stay within specification when reading,
143 143 densityslicing = getattr(revlog.index, 'slicechunktodensity', None)
144 144 if densityslicing is None:
145 145 densityslicing = lambda x, y, z: _slicechunktodensity(revlog, x, y, z)
146 146 for chunk in densityslicing(
147 147 revs, revlog._srdensitythreshold, revlog._srmingapsize
148 148 ):
149 149 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
150 150 yield subchunk
151 151
152 152
153 153 def _slicechunktosize(revlog, revs, targetsize=None):
154 154 """slice revs to match the target size
155 155
156 156 This is intended to be used on chunk that density slicing selected by that
157 157 are still too large compared to the read garantee of revlog. This might
158 158 happens when "minimal gap size" interrupted the slicing or when chain are
159 159 built in a way that create large blocks next to each other.
160 160
161 161 >>> data = [
162 162 ... 3, #0 (3)
163 163 ... 5, #1 (2)
164 164 ... 6, #2 (1)
165 165 ... 8, #3 (2)
166 166 ... 8, #4 (empty)
167 167 ... 11, #5 (3)
168 168 ... 12, #6 (1)
169 169 ... 13, #7 (1)
170 170 ... 14, #8 (1)
171 171 ... ]
172 172
173 173 == All snapshots cases ==
174 174 >>> revlog = _testrevlog(data, snapshot=range(9))
175 175
176 176 Cases where chunk is already small enough
177 177 >>> list(_slicechunktosize(revlog, [0], 3))
178 178 [[0]]
179 179 >>> list(_slicechunktosize(revlog, [6, 7], 3))
180 180 [[6, 7]]
181 181 >>> list(_slicechunktosize(revlog, [0], None))
182 182 [[0]]
183 183 >>> list(_slicechunktosize(revlog, [6, 7], None))
184 184 [[6, 7]]
185 185
186 186 cases where we need actual slicing
187 187 >>> list(_slicechunktosize(revlog, [0, 1], 3))
188 188 [[0], [1]]
189 189 >>> list(_slicechunktosize(revlog, [1, 3], 3))
190 190 [[1], [3]]
191 191 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
192 192 [[1, 2], [3]]
193 193 >>> list(_slicechunktosize(revlog, [3, 5], 3))
194 194 [[3], [5]]
195 195 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
196 196 [[3], [5]]
197 197 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
198 198 [[5], [6, 7, 8]]
199 199 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
200 200 [[0], [1, 2], [3], [5], [6, 7, 8]]
201 201
202 202 Case with too large individual chunk (must return valid chunk)
203 203 >>> list(_slicechunktosize(revlog, [0, 1], 2))
204 204 [[0], [1]]
205 205 >>> list(_slicechunktosize(revlog, [1, 3], 1))
206 206 [[1], [3]]
207 207 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
208 208 [[3], [5]]
209 209
210 210 == No Snapshot cases ==
211 211 >>> revlog = _testrevlog(data)
212 212
213 213 Cases where chunk is already small enough
214 214 >>> list(_slicechunktosize(revlog, [0], 3))
215 215 [[0]]
216 216 >>> list(_slicechunktosize(revlog, [6, 7], 3))
217 217 [[6, 7]]
218 218 >>> list(_slicechunktosize(revlog, [0], None))
219 219 [[0]]
220 220 >>> list(_slicechunktosize(revlog, [6, 7], None))
221 221 [[6, 7]]
222 222
223 223 cases where we need actual slicing
224 224 >>> list(_slicechunktosize(revlog, [0, 1], 3))
225 225 [[0], [1]]
226 226 >>> list(_slicechunktosize(revlog, [1, 3], 3))
227 227 [[1], [3]]
228 228 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
229 229 [[1], [2, 3]]
230 230 >>> list(_slicechunktosize(revlog, [3, 5], 3))
231 231 [[3], [5]]
232 232 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
233 233 [[3], [4, 5]]
234 234 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
235 235 [[5], [6, 7, 8]]
236 236 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
237 237 [[0], [1, 2], [3], [5], [6, 7, 8]]
238 238
239 239 Case with too large individual chunk (must return valid chunk)
240 240 >>> list(_slicechunktosize(revlog, [0, 1], 2))
241 241 [[0], [1]]
242 242 >>> list(_slicechunktosize(revlog, [1, 3], 1))
243 243 [[1], [3]]
244 244 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
245 245 [[3], [5]]
246 246
247 247 == mixed case ==
248 248 >>> revlog = _testrevlog(data, snapshot=[0, 1, 2])
249 249 >>> list(_slicechunktosize(revlog, list(range(9)), 5))
250 250 [[0, 1], [2], [3, 4, 5], [6, 7, 8]]
251 251 """
252 252 assert targetsize is None or 0 <= targetsize
253 253 startdata = revlog.start(revs[0])
254 254 enddata = revlog.end(revs[-1])
255 255 fullspan = enddata - startdata
256 256 if targetsize is None or fullspan <= targetsize:
257 257 yield revs
258 258 return
259 259
260 260 startrevidx = 0
261 261 endrevidx = 1
262 262 iterrevs = enumerate(revs)
263 263 next(iterrevs) # skip first rev.
264 264 # first step: get snapshots out of the way
265 265 for idx, r in iterrevs:
266 266 span = revlog.end(r) - startdata
267 267 snapshot = revlog.issnapshot(r)
268 268 if span <= targetsize and snapshot:
269 269 endrevidx = idx + 1
270 270 else:
271 271 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
272 272 if chunk:
273 273 yield chunk
274 274 startrevidx = idx
275 275 startdata = revlog.start(r)
276 276 endrevidx = idx + 1
277 277 if not snapshot:
278 278 break
279 279
280 280 # for the others, we use binary slicing to quickly converge toward valid
281 281 # chunks (otherwise, we might end up looking for start/end of many
282 282 # revisions). This logic is not looking for the perfect slicing point, it
283 283 # focuses on quickly converging toward valid chunks.
284 284 nbitem = len(revs)
285 285 while (enddata - startdata) > targetsize:
286 286 endrevidx = nbitem
287 287 if nbitem - startrevidx <= 1:
288 288 break # protect against individual chunk larger than limit
289 289 localenddata = revlog.end(revs[endrevidx - 1])
290 290 span = localenddata - startdata
291 291 while span > targetsize:
292 292 if endrevidx - startrevidx <= 1:
293 293 break # protect against individual chunk larger than limit
294 294 endrevidx -= (endrevidx - startrevidx) // 2
295 295 localenddata = revlog.end(revs[endrevidx - 1])
296 296 span = localenddata - startdata
297 297 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
298 298 if chunk:
299 299 yield chunk
300 300 startrevidx = endrevidx
301 301 startdata = revlog.start(revs[startrevidx])
302 302
303 303 chunk = _trimchunk(revlog, revs, startrevidx)
304 304 if chunk:
305 305 yield chunk
306 306
307 307
308 308 def _slicechunktodensity(revlog, revs, targetdensity=0.5, mingapsize=0):
309 309 """slice revs to reduce the amount of unrelated data to be read from disk.
310 310
311 311 ``revs`` is sliced into groups that should be read in one time.
312 312 Assume that revs are sorted.
313 313
314 314 The initial chunk is sliced until the overall density (payload/chunks-span
315 315 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
316 316 skipped.
317 317
318 318 >>> revlog = _testrevlog([
319 319 ... 5, #00 (5)
320 320 ... 10, #01 (5)
321 321 ... 12, #02 (2)
322 322 ... 12, #03 (empty)
323 323 ... 27, #04 (15)
324 324 ... 31, #05 (4)
325 325 ... 31, #06 (empty)
326 326 ... 42, #07 (11)
327 327 ... 47, #08 (5)
328 328 ... 47, #09 (empty)
329 329 ... 48, #10 (1)
330 330 ... 51, #11 (3)
331 331 ... 74, #12 (23)
332 332 ... 85, #13 (11)
333 333 ... 86, #14 (1)
334 334 ... 91, #15 (5)
335 335 ... ])
336 336
337 337 >>> list(_slicechunktodensity(revlog, list(range(16))))
338 338 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
339 339 >>> list(_slicechunktodensity(revlog, [0, 15]))
340 340 [[0], [15]]
341 341 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
342 342 [[0], [11], [15]]
343 343 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
344 344 [[0], [11, 13, 15]]
345 345 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
346 346 [[1, 2], [5, 8, 10, 11], [14]]
347 347 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
348 348 ... mingapsize=20))
349 349 [[1, 2, 3, 5, 8, 10, 11], [14]]
350 350 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
351 351 ... targetdensity=0.95))
352 352 [[1, 2], [5], [8, 10, 11], [14]]
353 353 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
354 354 ... targetdensity=0.95, mingapsize=12))
355 355 [[1, 2], [5, 8, 10, 11], [14]]
356 356 """
357 357 start = revlog.start
358 358 length = revlog.length
359 359
360 360 if len(revs) <= 1:
361 361 yield revs
362 362 return
363 363
364 364 deltachainspan = segmentspan(revlog, revs)
365 365
366 366 if deltachainspan < mingapsize:
367 367 yield revs
368 368 return
369 369
370 370 readdata = deltachainspan
371 371 chainpayload = sum(length(r) for r in revs)
372 372
373 373 if deltachainspan:
374 374 density = chainpayload / float(deltachainspan)
375 375 else:
376 376 density = 1.0
377 377
378 378 if density >= targetdensity:
379 379 yield revs
380 380 return
381 381
382 382 # Store the gaps in a heap to have them sorted by decreasing size
383 383 gaps = []
384 384 prevend = None
385 385 for i, rev in enumerate(revs):
386 386 revstart = start(rev)
387 387 revlen = length(rev)
388 388
389 389 # Skip empty revisions to form larger holes
390 390 if revlen == 0:
391 391 continue
392 392
393 393 if prevend is not None:
394 394 gapsize = revstart - prevend
395 395 # only consider holes that are large enough
396 396 if gapsize > mingapsize:
397 397 gaps.append((gapsize, i))
398 398
399 399 prevend = revstart + revlen
400 400 # sort the gaps to pop them from largest to small
401 401 gaps.sort()
402 402
403 403 # Collect the indices of the largest holes until the density is acceptable
404 404 selected = []
405 405 while gaps and density < targetdensity:
406 406 gapsize, gapidx = gaps.pop()
407 407
408 408 selected.append(gapidx)
409 409
410 410 # the gap sizes are stored as negatives to be sorted decreasingly
411 411 # by the heap
412 412 readdata -= gapsize
413 413 if readdata > 0:
414 414 density = chainpayload / float(readdata)
415 415 else:
416 416 density = 1.0
417 417 selected.sort()
418 418
419 419 # Cut the revs at collected indices
420 420 previdx = 0
421 421 for idx in selected:
422 422
423 423 chunk = _trimchunk(revlog, revs, previdx, idx)
424 424 if chunk:
425 425 yield chunk
426 426
427 427 previdx = idx
428 428
429 429 chunk = _trimchunk(revlog, revs, previdx)
430 430 if chunk:
431 431 yield chunk
432 432
433 433
434 434 def _trimchunk(revlog, revs, startidx, endidx=None):
435 435 """returns revs[startidx:endidx] without empty trailing revs
436 436
437 437 Doctest Setup
438 438 >>> revlog = _testrevlog([
439 439 ... 5, #0
440 440 ... 10, #1
441 441 ... 12, #2
442 442 ... 12, #3 (empty)
443 443 ... 17, #4
444 444 ... 21, #5
445 445 ... 21, #6 (empty)
446 446 ... ])
447 447
448 448 Contiguous cases:
449 449 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
450 450 [0, 1, 2, 3, 4, 5]
451 451 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
452 452 [0, 1, 2, 3, 4]
453 453 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
454 454 [0, 1, 2]
455 455 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
456 456 [2]
457 457 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
458 458 [3, 4, 5]
459 459 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
460 460 [3, 4]
461 461
462 462 Discontiguous cases:
463 463 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
464 464 [1, 3, 5]
465 465 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
466 466 [1]
467 467 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
468 468 [3, 5]
469 469 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
470 470 [3, 5]
471 471 """
472 472 length = revlog.length
473 473
474 474 if endidx is None:
475 475 endidx = len(revs)
476 476
477 477 # If we have a non-emtpy delta candidate, there are nothing to trim
478 478 if revs[endidx - 1] < len(revlog):
479 479 # Trim empty revs at the end, except the very first revision of a chain
480 480 while (
481 481 endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0
482 482 ):
483 483 endidx -= 1
484 484
485 485 return revs[startidx:endidx]
486 486
487 487
488 488 def segmentspan(revlog, revs):
489 489 """Get the byte span of a segment of revisions
490 490
491 491 revs is a sorted array of revision numbers
492 492
493 493 >>> revlog = _testrevlog([
494 494 ... 5, #0
495 495 ... 10, #1
496 496 ... 12, #2
497 497 ... 12, #3 (empty)
498 498 ... 17, #4
499 499 ... ])
500 500
501 501 >>> segmentspan(revlog, [0, 1, 2, 3, 4])
502 502 17
503 503 >>> segmentspan(revlog, [0, 4])
504 504 17
505 505 >>> segmentspan(revlog, [3, 4])
506 506 5
507 507 >>> segmentspan(revlog, [1, 2, 3,])
508 508 7
509 509 >>> segmentspan(revlog, [1, 3])
510 510 7
511 511 """
512 512 if not revs:
513 513 return 0
514 514 end = revlog.end(revs[-1])
515 515 return end - revlog.start(revs[0])
516 516
517 517
518 518 def _textfromdelta(fh, revlog, baserev, delta, p1, p2, flags, expectednode):
519 519 """build full text from a (base, delta) pair and other metadata"""
520 520 # special case deltas which replace entire base; no need to decode
521 521 # base revision. this neatly avoids censored bases, which throw when
522 522 # they're decoded.
523 523 hlen = struct.calcsize(b">lll")
524 524 if delta[:hlen] == mdiff.replacediffheader(
525 525 revlog.rawsize(baserev), len(delta) - hlen
526 526 ):
527 527 fulltext = delta[hlen:]
528 528 else:
529 529 # deltabase is rawtext before changed by flag processors, which is
530 530 # equivalent to non-raw text
531 531 basetext = revlog.revision(baserev, _df=fh)
532 532 fulltext = mdiff.patch(basetext, delta)
533 533
534 534 try:
535 535 validatehash = flagutil.processflagsraw(revlog, fulltext, flags)
536 536 if validatehash:
537 537 revlog.checkhash(fulltext, expectednode, p1=p1, p2=p2)
538 538 if flags & REVIDX_ISCENSORED:
539 539 raise error.StorageError(
540 540 _(b'node %s is not censored') % expectednode
541 541 )
542 542 except error.CensoredNodeError:
543 543 # must pass the censored index flag to add censored revisions
544 544 if not flags & REVIDX_ISCENSORED:
545 545 raise
546 546 return fulltext
547 547
548 548
549 549 @attr.s(slots=True, frozen=True)
550 550 class _deltainfo:
551 551 distance = attr.ib()
552 552 deltalen = attr.ib()
553 553 data = attr.ib()
554 554 base = attr.ib()
555 555 chainbase = attr.ib()
556 556 chainlen = attr.ib()
557 557 compresseddeltalen = attr.ib()
558 558 snapshotdepth = attr.ib()
559 559
560 560
561 561 def drop_u_compression(delta):
562 562 """turn into a "u" (no-compression) into no-compression without header
563 563
564 564 This is useful for revlog format that has better compression method.
565 565 """
566 566 assert delta.data[0] == b'u', delta.data[0]
567 567 return _deltainfo(
568 568 delta.distance,
569 569 delta.deltalen - 1,
570 570 (b'', delta.data[1]),
571 571 delta.base,
572 572 delta.chainbase,
573 573 delta.chainlen,
574 574 delta.compresseddeltalen,
575 575 delta.snapshotdepth,
576 576 )
577 577
578 578
579 579 def isgooddeltainfo(revlog, deltainfo, revinfo):
580 580 """Returns True if the given delta is good. Good means that it is within
581 581 the disk span, disk size, and chain length bounds that we know to be
582 582 performant."""
583 583 if deltainfo is None:
584 584 return False
585 585
586 586 # - 'deltainfo.distance' is the distance from the base revision --
587 587 # bounding it limits the amount of I/O we need to do.
588 588 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
589 589 # deltas we need to apply -- bounding it limits the amount of CPU
590 590 # we consume.
591 591
592 592 textlen = revinfo.textlen
593 593 defaultmax = textlen * 4
594 594 maxdist = revlog._maxdeltachainspan
595 595 if not maxdist:
596 596 maxdist = deltainfo.distance # ensure the conditional pass
597 597 maxdist = max(maxdist, defaultmax)
598 598
599 599 # Bad delta from read span:
600 600 #
601 601 # If the span of data read is larger than the maximum allowed.
602 602 #
603 603 # In the sparse-revlog case, we rely on the associated "sparse reading"
604 604 # to avoid issue related to the span of data. In theory, it would be
605 605 # possible to build pathological revlog where delta pattern would lead
606 606 # to too many reads. However, they do not happen in practice at all. So
607 607 # we skip the span check entirely.
608 608 if not revlog._sparserevlog and maxdist < deltainfo.distance:
609 609 return False
610 610
611 611 # Bad delta from new delta size:
612 612 #
613 613 # If the delta size is larger than the target text, storing the
614 614 # delta will be inefficient.
615 615 if textlen < deltainfo.deltalen:
616 616 return False
617 617
618 618 # Bad delta from cumulated payload size:
619 619 #
620 620 # If the sum of delta get larger than K * target text length.
621 621 if textlen * LIMIT_DELTA2TEXT < deltainfo.compresseddeltalen:
622 622 return False
623 623
624 624 # Bad delta from chain length:
625 625 #
626 626 # If the number of delta in the chain gets too high.
627 627 if revlog._maxchainlen and revlog._maxchainlen < deltainfo.chainlen:
628 628 return False
629 629
630 630 # bad delta from intermediate snapshot size limit
631 631 #
632 632 # If an intermediate snapshot size is higher than the limit. The
633 633 # limit exist to prevent endless chain of intermediate delta to be
634 634 # created.
635 635 if (
636 636 deltainfo.snapshotdepth is not None
637 637 and (textlen >> deltainfo.snapshotdepth) < deltainfo.deltalen
638 638 ):
639 639 return False
640 640
641 641 # bad delta if new intermediate snapshot is larger than the previous
642 642 # snapshot
643 643 if (
644 644 deltainfo.snapshotdepth
645 645 and revlog.length(deltainfo.base) < deltainfo.deltalen
646 646 ):
647 647 return False
648 648
649 649 return True
650 650
651 651
652 652 # If a revision's full text is that much bigger than a base candidate full
653 653 # text's, it is very unlikely that it will produce a valid delta. We no longer
654 654 # consider these candidates.
655 655 LIMIT_BASE2TEXT = 500
656 656
657 657
658 658 def _candidategroups(
659 659 revlog,
660 660 textlen,
661 661 p1,
662 662 p2,
663 663 cachedelta,
664 excluded_bases=None,
665 target_rev=None,
664 666 ):
665 667 """Provides group of revision to be tested as delta base
666 668
667 669 This top level function focus on emitting groups with unique and worthwhile
668 670 content. See _raw_candidate_groups for details about the group order.
669 671 """
670 672 # should we try to build a delta?
671 673 if not (len(revlog) and revlog._storedeltachains):
672 674 yield None
673 675 return
674 676
675 677 deltalength = revlog.length
676 678 deltaparent = revlog.deltaparent
677 679 sparse = revlog._sparserevlog
678 680 good = None
679 681
680 682 deltas_limit = textlen * LIMIT_DELTA2TEXT
681 683
682 684 tested = {nullrev}
683 685 candidates = _refinedgroups(
684 686 revlog,
685 687 p1,
686 688 p2,
687 689 cachedelta,
688 690 )
689 691 while True:
690 692 temptative = candidates.send(good)
691 693 if temptative is None:
692 694 break
693 695 group = []
694 696 for rev in temptative:
695 697 # skip over empty delta (no need to include them in a chain)
696 698 while revlog._generaldelta and not (
697 699 rev == nullrev or rev in tested or deltalength(rev)
698 700 ):
699 701 tested.add(rev)
700 702 rev = deltaparent(rev)
701 703 # no need to try a delta against nullrev, this will be done as a
702 704 # last resort.
703 705 if rev == nullrev:
704 706 continue
705 707 # filter out revision we tested already
706 708 if rev in tested:
707 709 continue
708 710 tested.add(rev)
711 # an higher authority deamed the base unworthy (e.g. censored)
712 if excluded_bases is not None and rev in excluded_bases:
713 continue
714 # We are in some recomputation cases and that rev is too high in
715 # the revlog
716 if target_rev is not None and rev >= target_rev:
717 continue
709 718 # filter out delta base that will never produce good delta
710 719 if deltas_limit < revlog.length(rev):
711 720 continue
712 721 if sparse and revlog.rawsize(rev) < (textlen // LIMIT_BASE2TEXT):
713 722 continue
714 723 # no delta for rawtext-changing revs (see "candelta" for why)
715 724 if revlog.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
716 725 continue
717 726
718 727 # If we reach here, we are about to build and test a delta.
719 728 # The delta building process will compute the chaininfo in all
720 729 # case, since that computation is cached, it is fine to access it
721 730 # here too.
722 731 chainlen, chainsize = revlog._chaininfo(rev)
723 732 # if chain will be too long, skip base
724 733 if revlog._maxchainlen and chainlen >= revlog._maxchainlen:
725 734 continue
726 735 # if chain already have too much data, skip base
727 736 if deltas_limit < chainsize:
728 737 continue
729 738 if sparse and revlog.upperboundcomp is not None:
730 739 maxcomp = revlog.upperboundcomp
731 740 basenotsnap = (p1, p2, nullrev)
732 741 if rev not in basenotsnap and revlog.issnapshot(rev):
733 742 snapshotdepth = revlog.snapshotdepth(rev)
734 743 # If text is significantly larger than the base, we can
735 744 # expect the resulting delta to be proportional to the size
736 745 # difference
737 746 revsize = revlog.rawsize(rev)
738 747 rawsizedistance = max(textlen - revsize, 0)
739 748 # use an estimate of the compression upper bound.
740 749 lowestrealisticdeltalen = rawsizedistance // maxcomp
741 750
742 751 # check the absolute constraint on the delta size
743 752 snapshotlimit = textlen >> snapshotdepth
744 753 if snapshotlimit < lowestrealisticdeltalen:
745 754 # delta lower bound is larger than accepted upper bound
746 755 continue
747 756
748 757 # check the relative constraint on the delta size
749 758 revlength = revlog.length(rev)
750 759 if revlength < lowestrealisticdeltalen:
751 760 # delta probable lower bound is larger than target base
752 761 continue
753 762
754 763 group.append(rev)
755 764 if group:
756 765 # XXX: in the sparse revlog case, group can become large,
757 766 # impacting performances. Some bounding or slicing mecanism
758 767 # would help to reduce this impact.
759 768 good = yield tuple(group)
760 769 yield None
761 770
762 771
763 772 def _findsnapshots(revlog, cache, start_rev):
764 773 """find snapshot from start_rev to tip"""
765 774 if util.safehasattr(revlog.index, b'findsnapshots'):
766 775 revlog.index.findsnapshots(cache, start_rev)
767 776 else:
768 777 deltaparent = revlog.deltaparent
769 778 issnapshot = revlog.issnapshot
770 779 for rev in revlog.revs(start_rev):
771 780 if issnapshot(rev):
772 781 cache[deltaparent(rev)].append(rev)
773 782
774 783
775 784 def _refinedgroups(revlog, p1, p2, cachedelta):
776 785 good = None
777 786 # First we try to reuse a the delta contained in the bundle.
778 787 # (or from the source revlog)
779 788 #
780 789 # This logic only applies to general delta repositories and can be disabled
781 790 # through configuration. Disabling reuse source delta is useful when
782 791 # we want to make sure we recomputed "optimal" deltas.
783 792 debug_info = None
784 793 if cachedelta and revlog._generaldelta and revlog._lazydeltabase:
785 794 # Assume what we received from the server is a good choice
786 795 # build delta will reuse the cache
787 796 if debug_info is not None:
788 797 debug_info['cached-delta.tested'] += 1
789 798 good = yield (cachedelta[0],)
790 799 if good is not None:
791 800 if debug_info is not None:
792 801 debug_info['cached-delta.accepted'] += 1
793 802 yield None
794 803 return
795 804 # XXX cache me higher
796 805 snapshots = collections.defaultdict(list)
797 806 groups = _rawgroups(
798 807 revlog,
799 808 p1,
800 809 p2,
801 810 cachedelta,
802 811 snapshots,
803 812 )
804 813 for candidates in groups:
805 814 good = yield candidates
806 815 if good is not None:
807 816 break
808 817
809 818 # If sparse revlog is enabled, we can try to refine the available deltas
810 819 if not revlog._sparserevlog:
811 820 yield None
812 821 return
813 822
814 823 # if we have a refinable value, try to refine it
815 824 if good is not None and good not in (p1, p2) and revlog.issnapshot(good):
816 825 # refine snapshot down
817 826 previous = None
818 827 while previous != good:
819 828 previous = good
820 829 base = revlog.deltaparent(good)
821 830 if base == nullrev:
822 831 break
823 832 good = yield (base,)
824 833 # refine snapshot up
825 834 if not snapshots:
826 835 _findsnapshots(revlog, snapshots, good + 1)
827 836 previous = None
828 837 while good != previous:
829 838 previous = good
830 839 children = tuple(sorted(c for c in snapshots[good]))
831 840 good = yield children
832 841
833 842 if debug_info is not None:
834 843 if good is None:
835 844 debug_info['no-solution'] += 1
836 845
837 846 yield None
838 847
839 848
840 849 def _rawgroups(revlog, p1, p2, cachedelta, snapshots=None):
841 850 """Provides group of revision to be tested as delta base
842 851
843 852 This lower level function focus on emitting delta theorically interresting
844 853 without looking it any practical details.
845 854
846 855 The group order aims at providing fast or small candidates first.
847 856 """
848 857 gdelta = revlog._generaldelta
849 858 # gate sparse behind general-delta because of issue6056
850 859 sparse = gdelta and revlog._sparserevlog
851 860 curr = len(revlog)
852 861 prev = curr - 1
853 862 deltachain = lambda rev: revlog._deltachain(rev)[0]
854 863
855 864 if gdelta:
856 865 # exclude already lazy tested base if any
857 866 parents = [p for p in (p1, p2) if p != nullrev]
858 867
859 868 if not revlog._deltabothparents and len(parents) == 2:
860 869 parents.sort()
861 870 # To minimize the chance of having to build a fulltext,
862 871 # pick first whichever parent is closest to us (max rev)
863 872 yield (parents[1],)
864 873 # then the other one (min rev) if the first did not fit
865 874 yield (parents[0],)
866 875 elif len(parents) > 0:
867 876 # Test all parents (1 or 2), and keep the best candidate
868 877 yield parents
869 878
870 879 if sparse and parents:
871 880 if snapshots is None:
872 881 # map: base-rev: snapshot-rev
873 882 snapshots = collections.defaultdict(list)
874 883 # See if we can use an existing snapshot in the parent chains to use as
875 884 # a base for a new intermediate-snapshot
876 885 #
877 886 # search for snapshot in parents delta chain
878 887 # map: snapshot-level: snapshot-rev
879 888 parents_snaps = collections.defaultdict(set)
880 889 candidate_chains = [deltachain(p) for p in parents]
881 890 for chain in candidate_chains:
882 891 for idx, s in enumerate(chain):
883 892 if not revlog.issnapshot(s):
884 893 break
885 894 parents_snaps[idx].add(s)
886 895 snapfloor = min(parents_snaps[0]) + 1
887 896 _findsnapshots(revlog, snapshots, snapfloor)
888 897 # search for the highest "unrelated" revision
889 898 #
890 899 # Adding snapshots used by "unrelated" revision increase the odd we
891 900 # reuse an independant, yet better snapshot chain.
892 901 #
893 902 # XXX instead of building a set of revisions, we could lazily enumerate
894 903 # over the chains. That would be more efficient, however we stick to
895 904 # simple code for now.
896 905 all_revs = set()
897 906 for chain in candidate_chains:
898 907 all_revs.update(chain)
899 908 other = None
900 909 for r in revlog.revs(prev, snapfloor):
901 910 if r not in all_revs:
902 911 other = r
903 912 break
904 913 if other is not None:
905 914 # To avoid unfair competition, we won't use unrelated intermediate
906 915 # snapshot that are deeper than the ones from the parent delta
907 916 # chain.
908 917 max_depth = max(parents_snaps.keys())
909 918 chain = deltachain(other)
910 919 for idx, s in enumerate(chain):
911 920 if s < snapfloor:
912 921 continue
913 922 if max_depth < idx:
914 923 break
915 924 if not revlog.issnapshot(s):
916 925 break
917 926 parents_snaps[idx].add(s)
918 927 # Test them as possible intermediate snapshot base
919 928 # We test them from highest to lowest level. High level one are more
920 929 # likely to result in small delta
921 930 floor = None
922 931 for idx, snaps in sorted(parents_snaps.items(), reverse=True):
923 932 siblings = set()
924 933 for s in snaps:
925 934 siblings.update(snapshots[s])
926 935 # Before considering making a new intermediate snapshot, we check
927 936 # if an existing snapshot, children of base we consider, would be
928 937 # suitable.
929 938 #
930 939 # It give a change to reuse a delta chain "unrelated" to the
931 940 # current revision instead of starting our own. Without such
932 941 # re-use, topological branches would keep reopening new chains.
933 942 # Creating more and more snapshot as the repository grow.
934 943
935 944 if floor is not None:
936 945 # We only do this for siblings created after the one in our
937 946 # parent's delta chain. Those created before has less chances
938 947 # to be valid base since our ancestors had to create a new
939 948 # snapshot.
940 949 siblings = [r for r in siblings if floor < r]
941 950 yield tuple(sorted(siblings))
942 951 # then test the base from our parent's delta chain.
943 952 yield tuple(sorted(snaps))
944 953 floor = min(snaps)
945 954 # No suitable base found in the parent chain, search if any full
946 955 # snapshots emitted since parent's base would be a suitable base for an
947 956 # intermediate snapshot.
948 957 #
949 958 # It give a chance to reuse a delta chain unrelated to the current
950 959 # revisions instead of starting our own. Without such re-use,
951 960 # topological branches would keep reopening new full chains. Creating
952 961 # more and more snapshot as the repository grow.
953 962 yield tuple(snapshots[nullrev])
954 963
955 964 if not sparse:
956 965 # other approach failed try against prev to hopefully save us a
957 966 # fulltext.
958 967 yield (prev,)
959 968
960 969
961 970 class deltacomputer:
962 971 def __init__(
963 972 self,
964 973 revlog,
965 974 write_debug=None,
966 975 debug_search=False,
967 976 debug_info=None,
968 977 ):
969 978 self.revlog = revlog
970 979 self._write_debug = write_debug
971 980 self._debug_search = debug_search
972 981 self._debug_info = debug_info
973 982
974 983 def buildtext(self, revinfo, fh):
975 984 """Builds a fulltext version of a revision
976 985
977 986 revinfo: revisioninfo instance that contains all needed info
978 987 fh: file handle to either the .i or the .d revlog file,
979 988 depending on whether it is inlined or not
980 989 """
981 990 btext = revinfo.btext
982 991 if btext[0] is not None:
983 992 return btext[0]
984 993
985 994 revlog = self.revlog
986 995 cachedelta = revinfo.cachedelta
987 996 baserev = cachedelta[0]
988 997 delta = cachedelta[1]
989 998
990 999 fulltext = btext[0] = _textfromdelta(
991 1000 fh,
992 1001 revlog,
993 1002 baserev,
994 1003 delta,
995 1004 revinfo.p1,
996 1005 revinfo.p2,
997 1006 revinfo.flags,
998 1007 revinfo.node,
999 1008 )
1000 1009 return fulltext
1001 1010
1002 1011 def _builddeltadiff(self, base, revinfo, fh):
1003 1012 revlog = self.revlog
1004 1013 t = self.buildtext(revinfo, fh)
1005 1014 if revlog.iscensored(base):
1006 1015 # deltas based on a censored revision must replace the
1007 1016 # full content in one patch, so delta works everywhere
1008 1017 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
1009 1018 delta = header + t
1010 1019 else:
1011 1020 ptext = revlog.rawdata(base, _df=fh)
1012 1021 delta = mdiff.textdiff(ptext, t)
1013 1022
1014 1023 return delta
1015 1024
1016 1025 def _builddeltainfo(self, revinfo, base, fh):
1017 1026 # can we use the cached delta?
1018 1027 revlog = self.revlog
1019 1028 debug_search = self._write_debug is not None and self._debug_search
1020 1029 chainbase = revlog.chainbase(base)
1021 1030 if revlog._generaldelta:
1022 1031 deltabase = base
1023 1032 else:
1024 1033 deltabase = chainbase
1025 1034 snapshotdepth = None
1026 1035 if revlog._sparserevlog and deltabase == nullrev:
1027 1036 snapshotdepth = 0
1028 1037 elif revlog._sparserevlog and revlog.issnapshot(deltabase):
1029 1038 # A delta chain should always be one full snapshot,
1030 1039 # zero or more semi-snapshots, and zero or more deltas
1031 1040 p1, p2 = revlog.rev(revinfo.p1), revlog.rev(revinfo.p2)
1032 1041 if deltabase not in (p1, p2) and revlog.issnapshot(deltabase):
1033 1042 snapshotdepth = len(revlog._deltachain(deltabase)[0])
1034 1043 delta = None
1035 1044 if revinfo.cachedelta:
1036 1045 cachebase, cachediff = revinfo.cachedelta
1037 1046 # check if the diff still apply
1038 1047 currentbase = cachebase
1039 1048 while (
1040 1049 currentbase != nullrev
1041 1050 and currentbase != base
1042 1051 and self.revlog.length(currentbase) == 0
1043 1052 ):
1044 1053 currentbase = self.revlog.deltaparent(currentbase)
1045 1054 if self.revlog._lazydelta and currentbase == base:
1046 1055 delta = revinfo.cachedelta[1]
1047 1056 if delta is None:
1048 1057 delta = self._builddeltadiff(base, revinfo, fh)
1049 1058 if debug_search:
1050 1059 msg = b"DBG-DELTAS-SEARCH: uncompressed-delta-size=%d\n"
1051 1060 msg %= len(delta)
1052 1061 self._write_debug(msg)
1053 1062 # snapshotdept need to be neither None nor 0 level snapshot
1054 1063 if revlog.upperboundcomp is not None and snapshotdepth:
1055 1064 lowestrealisticdeltalen = len(delta) // revlog.upperboundcomp
1056 1065 snapshotlimit = revinfo.textlen >> snapshotdepth
1057 1066 if debug_search:
1058 1067 msg = b"DBG-DELTAS-SEARCH: projected-lower-size=%d\n"
1059 1068 msg %= lowestrealisticdeltalen
1060 1069 self._write_debug(msg)
1061 1070 if snapshotlimit < lowestrealisticdeltalen:
1062 1071 if debug_search:
1063 1072 msg = b"DBG-DELTAS-SEARCH: DISCARDED (snapshot limit)\n"
1064 1073 self._write_debug(msg)
1065 1074 return None
1066 1075 if revlog.length(base) < lowestrealisticdeltalen:
1067 1076 if debug_search:
1068 1077 msg = b"DBG-DELTAS-SEARCH: DISCARDED (prev size)\n"
1069 1078 self._write_debug(msg)
1070 1079 return None
1071 1080 header, data = revlog.compress(delta)
1072 1081 deltalen = len(header) + len(data)
1073 1082 offset = revlog.end(len(revlog) - 1)
1074 1083 dist = deltalen + offset - revlog.start(chainbase)
1075 1084 chainlen, compresseddeltalen = revlog._chaininfo(base)
1076 1085 chainlen += 1
1077 1086 compresseddeltalen += deltalen
1078 1087
1079 1088 return _deltainfo(
1080 1089 dist,
1081 1090 deltalen,
1082 1091 (header, data),
1083 1092 deltabase,
1084 1093 chainbase,
1085 1094 chainlen,
1086 1095 compresseddeltalen,
1087 1096 snapshotdepth,
1088 1097 )
1089 1098
1090 1099 def _fullsnapshotinfo(self, fh, revinfo, curr):
1091 1100 rawtext = self.buildtext(revinfo, fh)
1092 1101 data = self.revlog.compress(rawtext)
1093 1102 compresseddeltalen = deltalen = dist = len(data[1]) + len(data[0])
1094 1103 deltabase = chainbase = curr
1095 1104 snapshotdepth = 0
1096 1105 chainlen = 1
1097 1106
1098 1107 return _deltainfo(
1099 1108 dist,
1100 1109 deltalen,
1101 1110 data,
1102 1111 deltabase,
1103 1112 chainbase,
1104 1113 chainlen,
1105 1114 compresseddeltalen,
1106 1115 snapshotdepth,
1107 1116 )
1108 1117
1109 1118 def finddeltainfo(self, revinfo, fh, excluded_bases=None, target_rev=None):
1110 1119 """Find an acceptable delta against a candidate revision
1111 1120
1112 1121 revinfo: information about the revision (instance of _revisioninfo)
1113 1122 fh: file handle to either the .i or the .d revlog file,
1114 1123 depending on whether it is inlined or not
1115 1124
1116 1125 Returns the first acceptable candidate revision, as ordered by
1117 1126 _candidategroups
1118 1127
1119 1128 If no suitable deltabase is found, we return delta info for a full
1120 1129 snapshot.
1121 1130
1122 1131 `excluded_bases` is an optional set of revision that cannot be used as
1123 1132 a delta base. Use this to recompute delta suitable in censor or strip
1124 1133 context.
1125 1134 """
1126 1135 if target_rev is None:
1127 1136 target_rev = len(self.revlog)
1128 1137
1129 1138 if not revinfo.textlen:
1130 1139 return self._fullsnapshotinfo(fh, revinfo, target_rev)
1131 1140
1132 1141 if excluded_bases is None:
1133 1142 excluded_bases = set()
1134 1143
1135 1144 # no delta for flag processor revision (see "candelta" for why)
1136 1145 # not calling candelta since only one revision needs test, also to
1137 1146 # avoid overhead fetching flags again.
1138 1147 if revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
1139 1148 return self._fullsnapshotinfo(fh, revinfo, target_rev)
1140 1149
1141 1150 gather_debug = (
1142 1151 self._write_debug is not None or self._debug_info is not None
1143 1152 )
1144 1153 debug_search = self._write_debug is not None and self._debug_search
1145 1154
1146 1155 if gather_debug:
1147 1156 start = util.timer()
1148 1157
1149 1158 # count the number of different delta we tried (for debug purpose)
1150 1159 dbg_try_count = 0
1151 1160 # count the number of "search round" we did. (for debug purpose)
1152 1161 dbg_try_rounds = 0
1153 1162 dbg_type = b'unknown'
1154 1163
1155 1164 cachedelta = revinfo.cachedelta
1156 1165 p1 = revinfo.p1
1157 1166 p2 = revinfo.p2
1158 1167 revlog = self.revlog
1159 1168
1160 1169 deltainfo = None
1161 1170 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
1162 1171
1163 1172 if gather_debug:
1164 1173 if p1r != nullrev:
1165 1174 p1_chain_len = revlog._chaininfo(p1r)[0]
1166 1175 else:
1167 1176 p1_chain_len = -1
1168 1177 if p2r != nullrev:
1169 1178 p2_chain_len = revlog._chaininfo(p2r)[0]
1170 1179 else:
1171 1180 p2_chain_len = -1
1172 1181 if debug_search:
1173 1182 msg = b"DBG-DELTAS-SEARCH: SEARCH rev=%d\n"
1174 1183 msg %= target_rev
1175 1184 self._write_debug(msg)
1176 1185
1177 1186 groups = _candidategroups(
1178 1187 self.revlog,
1179 1188 revinfo.textlen,
1180 1189 p1r,
1181 1190 p2r,
1182 1191 cachedelta,
1192 excluded_bases,
1193 target_rev,
1183 1194 )
1184 1195 candidaterevs = next(groups)
1185 1196 while candidaterevs is not None:
1186 1197 dbg_try_rounds += 1
1187 1198 if debug_search:
1188 1199 prev = None
1189 1200 if deltainfo is not None:
1190 1201 prev = deltainfo.base
1191 1202
1192 1203 if (
1193 1204 cachedelta is not None
1194 1205 and len(candidaterevs) == 1
1195 1206 and cachedelta[0] in candidaterevs
1196 1207 ):
1197 1208 round_type = b"cached-delta"
1198 1209 elif p1 in candidaterevs or p2 in candidaterevs:
1199 1210 round_type = b"parents"
1200 1211 elif prev is not None and all(c < prev for c in candidaterevs):
1201 1212 round_type = b"refine-down"
1202 1213 elif prev is not None and all(c > prev for c in candidaterevs):
1203 1214 round_type = b"refine-up"
1204 1215 else:
1205 1216 round_type = b"search-down"
1206 1217 msg = b"DBG-DELTAS-SEARCH: ROUND #%d - %d candidates - %s\n"
1207 1218 msg %= (dbg_try_rounds, len(candidaterevs), round_type)
1208 1219 self._write_debug(msg)
1209 1220 nominateddeltas = []
1210 1221 if deltainfo is not None:
1211 1222 if debug_search:
1212 1223 msg = (
1213 1224 b"DBG-DELTAS-SEARCH: CONTENDER: rev=%d - length=%d\n"
1214 1225 )
1215 1226 msg %= (deltainfo.base, deltainfo.deltalen)
1216 1227 self._write_debug(msg)
1217 1228 # if we already found a good delta,
1218 1229 # challenge it against refined candidates
1219 1230 nominateddeltas.append(deltainfo)
1220 1231 for candidaterev in candidaterevs:
1221 1232 if debug_search:
1222 1233 msg = b"DBG-DELTAS-SEARCH: CANDIDATE: rev=%d\n"
1223 1234 msg %= candidaterev
1224 1235 self._write_debug(msg)
1225 1236 candidate_type = None
1226 1237 if candidaterev == p1:
1227 1238 candidate_type = b"p1"
1228 1239 elif candidaterev == p2:
1229 1240 candidate_type = b"p2"
1230 1241 elif self.revlog.issnapshot(candidaterev):
1231 1242 candidate_type = b"snapshot-%d"
1232 1243 candidate_type %= self.revlog.snapshotdepth(
1233 1244 candidaterev
1234 1245 )
1235 1246
1236 1247 if candidate_type is not None:
1237 1248 msg = b"DBG-DELTAS-SEARCH: type=%s\n"
1238 1249 msg %= candidate_type
1239 1250 self._write_debug(msg)
1240 1251 msg = b"DBG-DELTAS-SEARCH: size=%d\n"
1241 1252 msg %= self.revlog.length(candidaterev)
1242 1253 self._write_debug(msg)
1243 1254 msg = b"DBG-DELTAS-SEARCH: base=%d\n"
1244 1255 msg %= self.revlog.deltaparent(candidaterev)
1245 1256 self._write_debug(msg)
1246 if candidaterev in excluded_bases:
1247 if debug_search:
1248 msg = b"DBG-DELTAS-SEARCH: EXCLUDED\n"
1249 self._write_debug(msg)
1250 continue
1251 if candidaterev >= target_rev:
1252 if debug_search:
1253 msg = b"DBG-DELTAS-SEARCH: TOO-HIGH\n"
1254 self._write_debug(msg)
1255 continue
1257
1256 1258 dbg_try_count += 1
1257 1259
1258 1260 if debug_search:
1259 1261 delta_start = util.timer()
1260 1262 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
1261 1263 if debug_search:
1262 1264 delta_end = util.timer()
1263 1265 msg = b"DBG-DELTAS-SEARCH: delta-search-time=%f\n"
1264 1266 msg %= delta_end - delta_start
1265 1267 self._write_debug(msg)
1266 1268 if candidatedelta is not None:
1267 1269 if isgooddeltainfo(self.revlog, candidatedelta, revinfo):
1268 1270 if debug_search:
1269 1271 msg = b"DBG-DELTAS-SEARCH: DELTA: length=%d (GOOD)\n"
1270 1272 msg %= candidatedelta.deltalen
1271 1273 self._write_debug(msg)
1272 1274 nominateddeltas.append(candidatedelta)
1273 1275 elif debug_search:
1274 1276 msg = b"DBG-DELTAS-SEARCH: DELTA: length=%d (BAD)\n"
1275 1277 msg %= candidatedelta.deltalen
1276 1278 self._write_debug(msg)
1277 1279 elif debug_search:
1278 1280 msg = b"DBG-DELTAS-SEARCH: NO-DELTA\n"
1279 1281 self._write_debug(msg)
1280 1282 if nominateddeltas:
1281 1283 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
1282 1284 if deltainfo is not None:
1283 1285 candidaterevs = groups.send(deltainfo.base)
1284 1286 else:
1285 1287 candidaterevs = next(groups)
1286 1288
1287 1289 if deltainfo is None:
1288 1290 dbg_type = b"full"
1289 1291 deltainfo = self._fullsnapshotinfo(fh, revinfo, target_rev)
1290 1292 elif deltainfo.snapshotdepth: # pytype: disable=attribute-error
1291 1293 dbg_type = b"snapshot"
1292 1294 else:
1293 1295 dbg_type = b"delta"
1294 1296
1295 1297 if gather_debug:
1296 1298 end = util.timer()
1297 1299 used_cached = (
1298 1300 cachedelta is not None
1299 1301 and dbg_try_rounds == 1
1300 1302 and dbg_try_count == 1
1301 1303 and deltainfo.base == cachedelta[0]
1302 1304 )
1303 1305 dbg = {
1304 1306 'duration': end - start,
1305 1307 'revision': target_rev,
1306 1308 'delta-base': deltainfo.base, # pytype: disable=attribute-error
1307 1309 'search_round_count': dbg_try_rounds,
1308 1310 'using-cached-base': used_cached,
1309 1311 'delta_try_count': dbg_try_count,
1310 1312 'type': dbg_type,
1311 1313 'p1-chain-len': p1_chain_len,
1312 1314 'p2-chain-len': p2_chain_len,
1313 1315 }
1314 1316 if (
1315 1317 deltainfo.snapshotdepth # pytype: disable=attribute-error
1316 1318 is not None
1317 1319 ):
1318 1320 dbg[
1319 1321 'snapshot-depth'
1320 1322 ] = deltainfo.snapshotdepth # pytype: disable=attribute-error
1321 1323 else:
1322 1324 dbg['snapshot-depth'] = 0
1323 1325 target_revlog = b"UNKNOWN"
1324 1326 target_type = self.revlog.target[0]
1325 1327 target_key = self.revlog.target[1]
1326 1328 if target_type == KIND_CHANGELOG:
1327 1329 target_revlog = b'CHANGELOG:'
1328 1330 elif target_type == KIND_MANIFESTLOG:
1329 1331 target_revlog = b'MANIFESTLOG:'
1330 1332 if target_key:
1331 1333 target_revlog += b'%s:' % target_key
1332 1334 elif target_type == KIND_FILELOG:
1333 1335 target_revlog = b'FILELOG:'
1334 1336 if target_key:
1335 1337 target_revlog += b'%s:' % target_key
1336 1338 dbg['target-revlog'] = target_revlog
1337 1339
1338 1340 if self._debug_info is not None:
1339 1341 self._debug_info.append(dbg)
1340 1342
1341 1343 if self._write_debug is not None:
1342 1344 msg = (
1343 1345 b"DBG-DELTAS:"
1344 1346 b" %-12s"
1345 1347 b" rev=%d:"
1346 1348 b" delta-base=%d"
1347 1349 b" is-cached=%d"
1348 1350 b" - search-rounds=%d"
1349 1351 b" try-count=%d"
1350 1352 b" - delta-type=%-6s"
1351 1353 b" snap-depth=%d"
1352 1354 b" - p1-chain-length=%d"
1353 1355 b" p2-chain-length=%d"
1354 1356 b" - duration=%f"
1355 1357 b"\n"
1356 1358 )
1357 1359 msg %= (
1358 1360 dbg["target-revlog"],
1359 1361 dbg["revision"],
1360 1362 dbg["delta-base"],
1361 1363 dbg["using-cached-base"],
1362 1364 dbg["search_round_count"],
1363 1365 dbg["delta_try_count"],
1364 1366 dbg["type"],
1365 1367 dbg["snapshot-depth"],
1366 1368 dbg["p1-chain-len"],
1367 1369 dbg["p2-chain-len"],
1368 1370 dbg["duration"],
1369 1371 )
1370 1372 self._write_debug(msg)
1371 1373 return deltainfo
1372 1374
1373 1375
1374 1376 def delta_compression(default_compression_header, deltainfo):
1375 1377 """return (COMPRESSION_MODE, deltainfo)
1376 1378
1377 1379 used by revlog v2+ format to dispatch between PLAIN and DEFAULT
1378 1380 compression.
1379 1381 """
1380 1382 h, d = deltainfo.data
1381 1383 compression_mode = COMP_MODE_INLINE
1382 1384 if not h and not d:
1383 1385 # not data to store at all... declare them uncompressed
1384 1386 compression_mode = COMP_MODE_PLAIN
1385 1387 elif not h:
1386 1388 t = d[0:1]
1387 1389 if t == b'\0':
1388 1390 compression_mode = COMP_MODE_PLAIN
1389 1391 elif t == default_compression_header:
1390 1392 compression_mode = COMP_MODE_DEFAULT
1391 1393 elif h == b'u':
1392 1394 # we have a more efficient way to declare uncompressed
1393 1395 h = b''
1394 1396 compression_mode = COMP_MODE_PLAIN
1395 1397 deltainfo = drop_u_compression(deltainfo)
1396 1398 return compression_mode, deltainfo
@@ -1,346 +1,341 b''
1 1 ====================================
2 2 Test delta choice with sparse revlog
3 3 ====================================
4 4
5 5 Sparse-revlog usually shows the most gain on Manifest. However, it is simpler
6 6 to general an appropriate file, so we test with a single file instead. The
7 7 goal is to observe intermediate snapshot being created.
8 8
9 9 We need a large enough file. Part of the content needs to be replaced
10 10 repeatedly while some of it changes rarely.
11 11
12 12 $ bundlepath="$TESTDIR/artifacts/cache/big-file-churn.hg"
13 13
14 14 $ expectedhash=`cat "$bundlepath".md5`
15 15
16 16 #if slow
17 17
18 18 $ if [ ! -f "$bundlepath" ]; then
19 19 > "$TESTDIR"/artifacts/scripts/generate-churning-bundle.py > /dev/null
20 20 > fi
21 21
22 22 #else
23 23
24 24 $ if [ ! -f "$bundlepath" ]; then
25 25 > echo 'skipped: missing artifact, run "'"$TESTDIR"'/artifacts/scripts/generate-churning-bundle.py"'
26 26 > exit 80
27 27 > fi
28 28
29 29 #endif
30 30
31 31 $ currenthash=`f -M "$bundlepath" | cut -d = -f 2`
32 32 $ if [ "$currenthash" != "$expectedhash" ]; then
33 33 > echo 'skipped: outdated artifact, md5 "'"$currenthash"'" expected "'"$expectedhash"'" run "'"$TESTDIR"'/artifacts/scripts/generate-churning-bundle.py"'
34 34 > exit 80
35 35 > fi
36 36
37 37 $ cat >> $HGRCPATH << EOF
38 38 > [format]
39 39 > sparse-revlog = yes
40 40 > maxchainlen = 15
41 41 > [storage]
42 42 > revlog.optimize-delta-parent-choice = yes
43 43 > revlog.reuse-external-delta = no
44 44 > EOF
45 45 $ hg init sparse-repo
46 46 $ cd sparse-repo
47 47 $ hg unbundle $bundlepath
48 48 adding changesets
49 49 adding manifests
50 50 adding file changes
51 51 added 5001 changesets with 5001 changes to 1 files (+89 heads)
52 52 new changesets 9706f5af64f4:d9032adc8114 (5001 drafts)
53 53 (run 'hg heads' to see heads, 'hg merge' to merge)
54 54 $ hg up
55 55 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 56 updated to "d9032adc8114: commit #5000"
57 57 89 other heads for branch "default"
58 58
59 59 $ hg log --stat -r 0:3
60 60 changeset: 0:9706f5af64f4
61 61 user: test
62 62 date: Thu Jan 01 00:00:00 1970 +0000
63 63 summary: initial commit
64 64
65 65 SPARSE-REVLOG-TEST-FILE | 10500 ++++++++++++++++++++++++++++++++++++++++++++++
66 66 1 files changed, 10500 insertions(+), 0 deletions(-)
67 67
68 68 changeset: 1:724907deaa5e
69 69 user: test
70 70 date: Thu Jan 01 00:00:00 1970 +0000
71 71 summary: commit #1
72 72
73 73 SPARSE-REVLOG-TEST-FILE | 1068 +++++++++++++++++++++++-----------------------
74 74 1 files changed, 534 insertions(+), 534 deletions(-)
75 75
76 76 changeset: 2:62c41bce3e5d
77 77 user: test
78 78 date: Thu Jan 01 00:00:00 1970 +0000
79 79 summary: commit #2
80 80
81 81 SPARSE-REVLOG-TEST-FILE | 1068 +++++++++++++++++++++++-----------------------
82 82 1 files changed, 534 insertions(+), 534 deletions(-)
83 83
84 84 changeset: 3:348a9cbd6959
85 85 user: test
86 86 date: Thu Jan 01 00:00:00 1970 +0000
87 87 summary: commit #3
88 88
89 89 SPARSE-REVLOG-TEST-FILE | 1068 +++++++++++++++++++++++-----------------------
90 90 1 files changed, 534 insertions(+), 534 deletions(-)
91 91
92 92
93 93 $ f -s .hg/store/data/*.d
94 94 .hg/store/data/_s_p_a_r_s_e-_r_e_v_l_o_g-_t_e_s_t-_f_i_l_e.d: size=58616973
95 95 $ hg debugrevlog *
96 96 format : 1
97 97 flags : generaldelta
98 98
99 99 revisions : 5001
100 100 merges : 625 (12.50%)
101 101 normal : 4376 (87.50%)
102 102 revisions : 5001
103 103 empty : 0 ( 0.00%)
104 104 text : 0 (100.00%)
105 105 delta : 0 (100.00%)
106 106 snapshot : 383 ( 7.66%)
107 107 lvl-0 : 3 ( 0.06%)
108 108 lvl-1 : 18 ( 0.36%)
109 109 lvl-2 : 62 ( 1.24%)
110 110 lvl-3 : 108 ( 2.16%)
111 111 lvl-4 : 191 ( 3.82%)
112 112 lvl-5 : 1 ( 0.02%)
113 113 deltas : 4618 (92.34%)
114 114 revision size : 58616973
115 115 snapshot : 9247844 (15.78%)
116 116 lvl-0 : 539532 ( 0.92%)
117 117 lvl-1 : 1467743 ( 2.50%)
118 118 lvl-2 : 1873820 ( 3.20%)
119 119 lvl-3 : 2326874 ( 3.97%)
120 120 lvl-4 : 3029118 ( 5.17%)
121 121 lvl-5 : 10757 ( 0.02%)
122 122 deltas : 49369129 (84.22%)
123 123
124 124 chunks : 5001
125 125 0x28 : 5001 (100.00%)
126 126 chunks size : 58616973
127 127 0x28 : 58616973 (100.00%)
128 128
129 129 avg chain length : 9
130 130 max chain length : 15
131 131 max chain reach : 27366701
132 132 compression ratio : 29
133 133
134 134 uncompressed data size (min/max/avg) : 346468 / 346472 / 346471
135 135 full revision size (min/max/avg) : 179288 / 180786 / 179844
136 136 inter-snapshot size (min/max/avg) : 10757 / 169507 / 22916
137 137 level-1 (min/max/avg) : 13905 / 169507 / 81541
138 138 level-2 (min/max/avg) : 10887 / 83873 / 30222
139 139 level-3 (min/max/avg) : 10911 / 43047 / 21545
140 140 level-4 (min/max/avg) : 10838 / 21390 / 15859
141 141 level-5 (min/max/avg) : 10757 / 10757 / 10757
142 142 delta size (min/max/avg) : 9672 / 108072 / 10690
143 143
144 144 deltas against prev : 3906 (84.58%)
145 145 where prev = p1 : 3906 (100.00%)
146 146 where prev = p2 : 0 ( 0.00%)
147 147 other : 0 ( 0.00%)
148 148 deltas against p1 : 649 (14.05%)
149 149 deltas against p2 : 63 ( 1.36%)
150 150 deltas against other : 0 ( 0.00%)
151 151
152 152
153 153 Test `debug-delta-find`
154 154 -----------------------
155 155
156 156 $ ls -1
157 157 SPARSE-REVLOG-TEST-FILE
158 158 $ hg debugdeltachain SPARSE-REVLOG-TEST-FILE | grep snap | tail -1
159 159 4971 4970 -1 3 5 4930 snap 19179 346472 427596 1.23414 15994877 15567281 36.40652 427596 179288 1.00000 5
160 160 $ hg debug-delta-find SPARSE-REVLOG-TEST-FILE 4971
161 161 DBG-DELTAS-SEARCH: SEARCH rev=4971
162 DBG-DELTAS-SEARCH: ROUND #1 - 2 candidates - search-down
162 DBG-DELTAS-SEARCH: ROUND #1 - 1 candidates - search-down
163 163 DBG-DELTAS-SEARCH: CANDIDATE: rev=4962
164 164 DBG-DELTAS-SEARCH: type=snapshot-4
165 165 DBG-DELTAS-SEARCH: size=18296
166 166 DBG-DELTAS-SEARCH: base=4930
167 167 DBG-DELTAS-SEARCH: uncompressed-delta-size=30377
168 168 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
169 169 DBG-DELTAS-SEARCH: DELTA: length=16872 (BAD)
170 DBG-DELTAS-SEARCH: CANDIDATE: rev=4971
171 DBG-DELTAS-SEARCH: type=snapshot-4
172 DBG-DELTAS-SEARCH: size=19179
173 DBG-DELTAS-SEARCH: base=4930
174 DBG-DELTAS-SEARCH: TOO-HIGH
175 170 DBG-DELTAS-SEARCH: ROUND #2 - 1 candidates - search-down
176 171 DBG-DELTAS-SEARCH: CANDIDATE: rev=4930
177 172 DBG-DELTAS-SEARCH: type=snapshot-3
178 173 DBG-DELTAS-SEARCH: size=39228
179 174 DBG-DELTAS-SEARCH: base=4799
180 175 DBG-DELTAS-SEARCH: uncompressed-delta-size=33050
181 176 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
182 177 DBG-DELTAS-SEARCH: DELTA: length=19179 (GOOD)
183 178 DBG-DELTAS-SEARCH: ROUND #3 - 1 candidates - refine-down
184 179 DBG-DELTAS-SEARCH: CONTENDER: rev=4930 - length=19179
185 180 DBG-DELTAS-SEARCH: CANDIDATE: rev=4799
186 181 DBG-DELTAS-SEARCH: type=snapshot-2
187 182 DBG-DELTAS-SEARCH: size=50213
188 183 DBG-DELTAS-SEARCH: base=4623
189 184 DBG-DELTAS-SEARCH: uncompressed-delta-size=82661
190 185 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
191 186 DBG-DELTAS-SEARCH: DELTA: length=49132 (BAD)
192 187 DBG-DELTAS: FILELOG:SPARSE-REVLOG-TEST-FILE: rev=4971: delta-base=4930 is-cached=0 - search-rounds=3 try-count=3 - delta-type=snapshot snap-depth=4 - p1-chain-length=15 p2-chain-length=-1 - duration=* (glob)
193 188
194 189 $ cat << EOF >>.hg/hgrc
195 190 > [storage]
196 191 > revlog.optimize-delta-parent-choice = no
197 192 > revlog.reuse-external-delta = yes
198 193 > EOF
199 194
200 195 $ hg debug-delta-find SPARSE-REVLOG-TEST-FILE 4971 --quiet
201 196 DBG-DELTAS: FILELOG:SPARSE-REVLOG-TEST-FILE: rev=4971: delta-base=4930 is-cached=0 - search-rounds=3 try-count=3 - delta-type=snapshot snap-depth=4 - p1-chain-length=15 p2-chain-length=-1 - duration=* (glob)
202 197 $ hg debug-delta-find SPARSE-REVLOG-TEST-FILE 4971 --source full
203 198 DBG-DELTAS-SEARCH: SEARCH rev=4971
204 199 DBG-DELTAS-SEARCH: ROUND #1 - 2 candidates - search-down
205 200 DBG-DELTAS-SEARCH: CANDIDATE: rev=4962
206 201 DBG-DELTAS-SEARCH: type=snapshot-4
207 202 DBG-DELTAS-SEARCH: size=18296
208 203 DBG-DELTAS-SEARCH: base=4930
209 204 DBG-DELTAS-SEARCH: uncompressed-delta-size=30377
210 205 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
211 206 DBG-DELTAS-SEARCH: DELTA: length=16872 (BAD)
212 207 DBG-DELTAS-SEARCH: CANDIDATE: rev=4971
213 208 DBG-DELTAS-SEARCH: type=snapshot-4
214 209 DBG-DELTAS-SEARCH: size=19179
215 210 DBG-DELTAS-SEARCH: base=4930
216 211 DBG-DELTAS-SEARCH: TOO-HIGH
217 212 DBG-DELTAS-SEARCH: ROUND #2 - 1 candidates - search-down
218 213 DBG-DELTAS-SEARCH: CANDIDATE: rev=4930
219 214 DBG-DELTAS-SEARCH: type=snapshot-3
220 215 DBG-DELTAS-SEARCH: size=39228
221 216 DBG-DELTAS-SEARCH: base=4799
222 217 DBG-DELTAS-SEARCH: uncompressed-delta-size=33050
223 218 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
224 219 DBG-DELTAS-SEARCH: DELTA: length=19179 (GOOD)
225 220 DBG-DELTAS-SEARCH: ROUND #3 - 1 candidates - refine-down
226 221 DBG-DELTAS-SEARCH: CONTENDER: rev=4930 - length=19179
227 222 DBG-DELTAS-SEARCH: CANDIDATE: rev=4799
228 223 DBG-DELTAS-SEARCH: type=snapshot-2
229 224 DBG-DELTAS-SEARCH: size=50213
230 225 DBG-DELTAS-SEARCH: base=4623
231 226 DBG-DELTAS-SEARCH: uncompressed-delta-size=82661
232 227 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
233 228 DBG-DELTAS-SEARCH: DELTA: length=49132 (BAD)
234 229 DBG-DELTAS: FILELOG:SPARSE-REVLOG-TEST-FILE: rev=4971: delta-base=4930 is-cached=0 - search-rounds=3 try-count=3 - delta-type=snapshot snap-depth=4 - p1-chain-length=15 p2-chain-length=-1 - duration=* (glob)
235 230 $ hg debug-delta-find SPARSE-REVLOG-TEST-FILE 4971 --source storage
236 231 DBG-DELTAS-SEARCH: SEARCH rev=4971
237 232 DBG-DELTAS-SEARCH: ROUND #1 - 1 candidates - cached-delta
238 233 DBG-DELTAS-SEARCH: CANDIDATE: rev=4930
239 234 DBG-DELTAS-SEARCH: type=snapshot-3
240 235 DBG-DELTAS-SEARCH: size=39228
241 236 DBG-DELTAS-SEARCH: base=4799
242 237 DBG-DELTAS-SEARCH: uncompressed-delta-size=33050
243 238 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
244 239 DBG-DELTAS-SEARCH: DELTA: length=19179 (GOOD)
245 240 DBG-DELTAS: FILELOG:SPARSE-REVLOG-TEST-FILE: rev=4971: delta-base=4930 is-cached=1 - search-rounds=1 try-count=1 - delta-type=snapshot snap-depth=4 - p1-chain-length=15 p2-chain-length=-1 - duration=* (glob)
246 241 $ hg debug-delta-find SPARSE-REVLOG-TEST-FILE 4971 --source p1
247 242 DBG-DELTAS-SEARCH: SEARCH rev=4971
248 243 DBG-DELTAS-SEARCH: ROUND #1 - 2 candidates - search-down
249 244 DBG-DELTAS-SEARCH: CANDIDATE: rev=4962
250 245 DBG-DELTAS-SEARCH: type=snapshot-4
251 246 DBG-DELTAS-SEARCH: size=18296
252 247 DBG-DELTAS-SEARCH: base=4930
253 248 DBG-DELTAS-SEARCH: uncompressed-delta-size=30377
254 249 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
255 250 DBG-DELTAS-SEARCH: DELTA: length=16872 (BAD)
256 251 DBG-DELTAS-SEARCH: CANDIDATE: rev=4971
257 252 DBG-DELTAS-SEARCH: type=snapshot-4
258 253 DBG-DELTAS-SEARCH: size=19179
259 254 DBG-DELTAS-SEARCH: base=4930
260 255 DBG-DELTAS-SEARCH: TOO-HIGH
261 256 DBG-DELTAS-SEARCH: ROUND #2 - 1 candidates - search-down
262 257 DBG-DELTAS-SEARCH: CANDIDATE: rev=4930
263 258 DBG-DELTAS-SEARCH: type=snapshot-3
264 259 DBG-DELTAS-SEARCH: size=39228
265 260 DBG-DELTAS-SEARCH: base=4799
266 261 DBG-DELTAS-SEARCH: uncompressed-delta-size=33050
267 262 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
268 263 DBG-DELTAS-SEARCH: DELTA: length=19179 (GOOD)
269 264 DBG-DELTAS-SEARCH: ROUND #3 - 1 candidates - refine-down
270 265 DBG-DELTAS-SEARCH: CONTENDER: rev=4930 - length=19179
271 266 DBG-DELTAS-SEARCH: CANDIDATE: rev=4799
272 267 DBG-DELTAS-SEARCH: type=snapshot-2
273 268 DBG-DELTAS-SEARCH: size=50213
274 269 DBG-DELTAS-SEARCH: base=4623
275 270 DBG-DELTAS-SEARCH: uncompressed-delta-size=82661
276 271 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
277 272 DBG-DELTAS-SEARCH: DELTA: length=49132 (BAD)
278 273 DBG-DELTAS: FILELOG:SPARSE-REVLOG-TEST-FILE: rev=4971: delta-base=4930 is-cached=0 - search-rounds=3 try-count=3 - delta-type=snapshot snap-depth=4 - p1-chain-length=15 p2-chain-length=-1 - duration=* (glob)
279 274 $ hg debug-delta-find SPARSE-REVLOG-TEST-FILE 4971 --source p2
280 275 DBG-DELTAS-SEARCH: SEARCH rev=4971
281 276 DBG-DELTAS-SEARCH: ROUND #1 - 2 candidates - search-down
282 277 DBG-DELTAS-SEARCH: CANDIDATE: rev=4962
283 278 DBG-DELTAS-SEARCH: type=snapshot-4
284 279 DBG-DELTAS-SEARCH: size=18296
285 280 DBG-DELTAS-SEARCH: base=4930
286 281 DBG-DELTAS-SEARCH: uncompressed-delta-size=30377
287 282 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
288 283 DBG-DELTAS-SEARCH: DELTA: length=16872 (BAD)
289 284 DBG-DELTAS-SEARCH: CANDIDATE: rev=4971
290 285 DBG-DELTAS-SEARCH: type=snapshot-4
291 286 DBG-DELTAS-SEARCH: size=19179
292 287 DBG-DELTAS-SEARCH: base=4930
293 288 DBG-DELTAS-SEARCH: TOO-HIGH
294 289 DBG-DELTAS-SEARCH: ROUND #2 - 1 candidates - search-down
295 290 DBG-DELTAS-SEARCH: CANDIDATE: rev=4930
296 291 DBG-DELTAS-SEARCH: type=snapshot-3
297 292 DBG-DELTAS-SEARCH: size=39228
298 293 DBG-DELTAS-SEARCH: base=4799
299 294 DBG-DELTAS-SEARCH: uncompressed-delta-size=33050
300 295 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
301 296 DBG-DELTAS-SEARCH: DELTA: length=19179 (GOOD)
302 297 DBG-DELTAS-SEARCH: ROUND #3 - 1 candidates - refine-down
303 298 DBG-DELTAS-SEARCH: CONTENDER: rev=4930 - length=19179
304 299 DBG-DELTAS-SEARCH: CANDIDATE: rev=4799
305 300 DBG-DELTAS-SEARCH: type=snapshot-2
306 301 DBG-DELTAS-SEARCH: size=50213
307 302 DBG-DELTAS-SEARCH: base=4623
308 303 DBG-DELTAS-SEARCH: uncompressed-delta-size=82661
309 304 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
310 305 DBG-DELTAS-SEARCH: DELTA: length=49132 (BAD)
311 306 DBG-DELTAS: FILELOG:SPARSE-REVLOG-TEST-FILE: rev=4971: delta-base=4930 is-cached=0 - search-rounds=3 try-count=3 - delta-type=snapshot snap-depth=4 - p1-chain-length=15 p2-chain-length=-1 - duration=* (glob)
312 307 $ hg debug-delta-find SPARSE-REVLOG-TEST-FILE 4971 --source prev
313 308 DBG-DELTAS-SEARCH: SEARCH rev=4971
314 309 DBG-DELTAS-SEARCH: ROUND #1 - 2 candidates - search-down
315 310 DBG-DELTAS-SEARCH: CANDIDATE: rev=4962
316 311 DBG-DELTAS-SEARCH: type=snapshot-4
317 312 DBG-DELTAS-SEARCH: size=18296
318 313 DBG-DELTAS-SEARCH: base=4930
319 314 DBG-DELTAS-SEARCH: uncompressed-delta-size=30377
320 315 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
321 316 DBG-DELTAS-SEARCH: DELTA: length=16872 (BAD)
322 317 DBG-DELTAS-SEARCH: CANDIDATE: rev=4971
323 318 DBG-DELTAS-SEARCH: type=snapshot-4
324 319 DBG-DELTAS-SEARCH: size=19179
325 320 DBG-DELTAS-SEARCH: base=4930
326 321 DBG-DELTAS-SEARCH: TOO-HIGH
327 322 DBG-DELTAS-SEARCH: ROUND #2 - 1 candidates - search-down
328 323 DBG-DELTAS-SEARCH: CANDIDATE: rev=4930
329 324 DBG-DELTAS-SEARCH: type=snapshot-3
330 325 DBG-DELTAS-SEARCH: size=39228
331 326 DBG-DELTAS-SEARCH: base=4799
332 327 DBG-DELTAS-SEARCH: uncompressed-delta-size=33050
333 328 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
334 329 DBG-DELTAS-SEARCH: DELTA: length=19179 (GOOD)
335 330 DBG-DELTAS-SEARCH: ROUND #3 - 1 candidates - refine-down
336 331 DBG-DELTAS-SEARCH: CONTENDER: rev=4930 - length=19179
337 332 DBG-DELTAS-SEARCH: CANDIDATE: rev=4799
338 333 DBG-DELTAS-SEARCH: type=snapshot-2
339 334 DBG-DELTAS-SEARCH: size=50213
340 335 DBG-DELTAS-SEARCH: base=4623
341 336 DBG-DELTAS-SEARCH: uncompressed-delta-size=82661
342 337 DBG-DELTAS-SEARCH: delta-search-time=* (glob)
343 338 DBG-DELTAS-SEARCH: DELTA: length=49132 (BAD)
344 339 DBG-DELTAS: FILELOG:SPARSE-REVLOG-TEST-FILE: rev=4971: delta-base=4930 is-cached=0 - search-rounds=3 try-count=3 - delta-type=snapshot snap-depth=4 - p1-chain-length=15 p2-chain-length=-1 - duration=* (glob)
345 340
346 341 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now