##// END OF EJS Templates
revlogdeltas: move finddeltainfo filtering inside _candidategroups...
Boris Feld -
r39614:37957e07 default
parent child Browse files
Show More
@@ -1,787 +1,795 b''
1 1 # revlogdeltas.py - Logic around delta computation for revlog
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2018 Octobus <contact@octobus.net>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8 """Helper class to compute deltas stored inside revlogs"""
9 9
10 10 from __future__ import absolute_import
11 11
12 12 import heapq
13 13 import struct
14 14
15 15 # import stuff from node for others to import from revlog
16 16 from ..node import (
17 17 nullrev,
18 18 )
19 19 from ..i18n import _
20 20
21 21 from .constants import (
22 22 REVIDX_ISCENSORED,
23 23 REVIDX_RAWTEXT_CHANGING_FLAGS,
24 24 )
25 25
26 26 from ..thirdparty import (
27 27 attr,
28 28 )
29 29
30 30 from .. import (
31 31 error,
32 32 mdiff,
33 33 )
34 34
35 35 RevlogError = error.RevlogError
36 36 CensoredNodeError = error.CensoredNodeError
37 37
38 38 # maximum <delta-chain-data>/<revision-text-length> ratio
39 39 LIMIT_DELTA2TEXT = 2
40 40
41 41 class _testrevlog(object):
42 42 """minimalist fake revlog to use in doctests"""
43 43
44 44 def __init__(self, data, density=0.5, mingap=0):
45 45 """data is an list of revision payload boundaries"""
46 46 self._data = data
47 47 self._srdensitythreshold = density
48 48 self._srmingapsize = mingap
49 49
50 50 def start(self, rev):
51 51 if rev == 0:
52 52 return 0
53 53 return self._data[rev - 1]
54 54
55 55 def end(self, rev):
56 56 return self._data[rev]
57 57
58 58 def length(self, rev):
59 59 return self.end(rev) - self.start(rev)
60 60
61 61 def __len__(self):
62 62 return len(self._data)
63 63
64 64 def slicechunk(revlog, revs, deltainfo=None, targetsize=None):
65 65 """slice revs to reduce the amount of unrelated data to be read from disk.
66 66
67 67 ``revs`` is sliced into groups that should be read in one time.
68 68 Assume that revs are sorted.
69 69
70 70 The initial chunk is sliced until the overall density (payload/chunks-span
71 71 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
72 72 `revlog._srmingapsize` is skipped.
73 73
74 74 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
75 75 For consistency with other slicing choice, this limit won't go lower than
76 76 `revlog._srmingapsize`.
77 77
78 78 If individual revisions chunk are larger than this limit, they will still
79 79 be raised individually.
80 80
81 81 >>> revlog = _testrevlog([
82 82 ... 5, #00 (5)
83 83 ... 10, #01 (5)
84 84 ... 12, #02 (2)
85 85 ... 12, #03 (empty)
86 86 ... 27, #04 (15)
87 87 ... 31, #05 (4)
88 88 ... 31, #06 (empty)
89 89 ... 42, #07 (11)
90 90 ... 47, #08 (5)
91 91 ... 47, #09 (empty)
92 92 ... 48, #10 (1)
93 93 ... 51, #11 (3)
94 94 ... 74, #12 (23)
95 95 ... 85, #13 (11)
96 96 ... 86, #14 (1)
97 97 ... 91, #15 (5)
98 98 ... ])
99 99
100 100 >>> list(slicechunk(revlog, list(range(16))))
101 101 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
102 102 >>> list(slicechunk(revlog, [0, 15]))
103 103 [[0], [15]]
104 104 >>> list(slicechunk(revlog, [0, 11, 15]))
105 105 [[0], [11], [15]]
106 106 >>> list(slicechunk(revlog, [0, 11, 13, 15]))
107 107 [[0], [11, 13, 15]]
108 108 >>> list(slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
109 109 [[1, 2], [5, 8, 10, 11], [14]]
110 110
111 111 Slicing with a maximum chunk size
112 112 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
113 113 [[0], [11], [13], [15]]
114 114 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
115 115 [[0], [11], [13, 15]]
116 116 """
117 117 if targetsize is not None:
118 118 targetsize = max(targetsize, revlog._srmingapsize)
119 119 # targetsize should not be specified when evaluating delta candidates:
120 120 # * targetsize is used to ensure we stay within specification when reading,
121 121 # * deltainfo is used to pick are good delta chain when writing.
122 122 if not (deltainfo is None or targetsize is None):
123 123 msg = 'cannot use `targetsize` with a `deltainfo`'
124 124 raise error.ProgrammingError(msg)
125 125 for chunk in _slicechunktodensity(revlog, revs,
126 126 deltainfo,
127 127 revlog._srdensitythreshold,
128 128 revlog._srmingapsize):
129 129 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
130 130 yield subchunk
131 131
132 132 def _slicechunktosize(revlog, revs, targetsize=None):
133 133 """slice revs to match the target size
134 134
135 135 This is intended to be used on chunk that density slicing selected by that
136 136 are still too large compared to the read garantee of revlog. This might
137 137 happens when "minimal gap size" interrupted the slicing or when chain are
138 138 built in a way that create large blocks next to each other.
139 139
140 140 >>> revlog = _testrevlog([
141 141 ... 3, #0 (3)
142 142 ... 5, #1 (2)
143 143 ... 6, #2 (1)
144 144 ... 8, #3 (2)
145 145 ... 8, #4 (empty)
146 146 ... 11, #5 (3)
147 147 ... 12, #6 (1)
148 148 ... 13, #7 (1)
149 149 ... 14, #8 (1)
150 150 ... ])
151 151
152 152 Cases where chunk is already small enough
153 153 >>> list(_slicechunktosize(revlog, [0], 3))
154 154 [[0]]
155 155 >>> list(_slicechunktosize(revlog, [6, 7], 3))
156 156 [[6, 7]]
157 157 >>> list(_slicechunktosize(revlog, [0], None))
158 158 [[0]]
159 159 >>> list(_slicechunktosize(revlog, [6, 7], None))
160 160 [[6, 7]]
161 161
162 162 cases where we need actual slicing
163 163 >>> list(_slicechunktosize(revlog, [0, 1], 3))
164 164 [[0], [1]]
165 165 >>> list(_slicechunktosize(revlog, [1, 3], 3))
166 166 [[1], [3]]
167 167 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
168 168 [[1, 2], [3]]
169 169 >>> list(_slicechunktosize(revlog, [3, 5], 3))
170 170 [[3], [5]]
171 171 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
172 172 [[3], [5]]
173 173 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
174 174 [[5], [6, 7, 8]]
175 175 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
176 176 [[0], [1, 2], [3], [5], [6, 7, 8]]
177 177
178 178 Case with too large individual chunk (must return valid chunk)
179 179 >>> list(_slicechunktosize(revlog, [0, 1], 2))
180 180 [[0], [1]]
181 181 >>> list(_slicechunktosize(revlog, [1, 3], 1))
182 182 [[1], [3]]
183 183 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
184 184 [[3], [5]]
185 185 """
186 186 assert targetsize is None or 0 <= targetsize
187 187 if targetsize is None or segmentspan(revlog, revs) <= targetsize:
188 188 yield revs
189 189 return
190 190
191 191 startrevidx = 0
192 192 startdata = revlog.start(revs[0])
193 193 endrevidx = 0
194 194 iterrevs = enumerate(revs)
195 195 next(iterrevs) # skip first rev.
196 196 for idx, r in iterrevs:
197 197 span = revlog.end(r) - startdata
198 198 if span <= targetsize:
199 199 endrevidx = idx
200 200 else:
201 201 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx + 1)
202 202 if chunk:
203 203 yield chunk
204 204 startrevidx = idx
205 205 startdata = revlog.start(r)
206 206 endrevidx = idx
207 207 yield _trimchunk(revlog, revs, startrevidx)
208 208
209 209 def _slicechunktodensity(revlog, revs, deltainfo=None, targetdensity=0.5,
210 210 mingapsize=0):
211 211 """slice revs to reduce the amount of unrelated data to be read from disk.
212 212
213 213 ``revs`` is sliced into groups that should be read in one time.
214 214 Assume that revs are sorted.
215 215
216 216 ``deltainfo`` is a _deltainfo instance of a revision that we would append
217 217 to the top of the revlog.
218 218
219 219 The initial chunk is sliced until the overall density (payload/chunks-span
220 220 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
221 221 skipped.
222 222
223 223 >>> revlog = _testrevlog([
224 224 ... 5, #00 (5)
225 225 ... 10, #01 (5)
226 226 ... 12, #02 (2)
227 227 ... 12, #03 (empty)
228 228 ... 27, #04 (15)
229 229 ... 31, #05 (4)
230 230 ... 31, #06 (empty)
231 231 ... 42, #07 (11)
232 232 ... 47, #08 (5)
233 233 ... 47, #09 (empty)
234 234 ... 48, #10 (1)
235 235 ... 51, #11 (3)
236 236 ... 74, #12 (23)
237 237 ... 85, #13 (11)
238 238 ... 86, #14 (1)
239 239 ... 91, #15 (5)
240 240 ... ])
241 241
242 242 >>> list(_slicechunktodensity(revlog, list(range(16))))
243 243 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
244 244 >>> list(_slicechunktodensity(revlog, [0, 15]))
245 245 [[0], [15]]
246 246 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
247 247 [[0], [11], [15]]
248 248 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
249 249 [[0], [11, 13, 15]]
250 250 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
251 251 [[1, 2], [5, 8, 10, 11], [14]]
252 252 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
253 253 ... mingapsize=20))
254 254 [[1, 2, 3, 5, 8, 10, 11], [14]]
255 255 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
256 256 ... targetdensity=0.95))
257 257 [[1, 2], [5], [8, 10, 11], [14]]
258 258 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
259 259 ... targetdensity=0.95, mingapsize=12))
260 260 [[1, 2], [5, 8, 10, 11], [14]]
261 261 """
262 262 start = revlog.start
263 263 length = revlog.length
264 264
265 265 if len(revs) <= 1:
266 266 yield revs
267 267 return
268 268
269 269 nextrev = len(revlog)
270 270 nextoffset = revlog.end(nextrev - 1)
271 271
272 272 if deltainfo is None:
273 273 deltachainspan = segmentspan(revlog, revs)
274 274 chainpayload = sum(length(r) for r in revs)
275 275 else:
276 276 deltachainspan = deltainfo.distance
277 277 chainpayload = deltainfo.compresseddeltalen
278 278
279 279 if deltachainspan < mingapsize:
280 280 yield revs
281 281 return
282 282
283 283 readdata = deltachainspan
284 284
285 285 if deltachainspan:
286 286 density = chainpayload / float(deltachainspan)
287 287 else:
288 288 density = 1.0
289 289
290 290 if density >= targetdensity:
291 291 yield revs
292 292 return
293 293
294 294 if deltainfo is not None and deltainfo.deltalen:
295 295 revs = list(revs)
296 296 revs.append(nextrev)
297 297
298 298 # Store the gaps in a heap to have them sorted by decreasing size
299 299 gapsheap = []
300 300 heapq.heapify(gapsheap)
301 301 prevend = None
302 302 for i, rev in enumerate(revs):
303 303 if rev < nextrev:
304 304 revstart = start(rev)
305 305 revlen = length(rev)
306 306 else:
307 307 revstart = nextoffset
308 308 revlen = deltainfo.deltalen
309 309
310 310 # Skip empty revisions to form larger holes
311 311 if revlen == 0:
312 312 continue
313 313
314 314 if prevend is not None:
315 315 gapsize = revstart - prevend
316 316 # only consider holes that are large enough
317 317 if gapsize > mingapsize:
318 318 heapq.heappush(gapsheap, (-gapsize, i))
319 319
320 320 prevend = revstart + revlen
321 321
322 322 # Collect the indices of the largest holes until the density is acceptable
323 323 indicesheap = []
324 324 heapq.heapify(indicesheap)
325 325 while gapsheap and density < targetdensity:
326 326 oppgapsize, gapidx = heapq.heappop(gapsheap)
327 327
328 328 heapq.heappush(indicesheap, gapidx)
329 329
330 330 # the gap sizes are stored as negatives to be sorted decreasingly
331 331 # by the heap
332 332 readdata -= (-oppgapsize)
333 333 if readdata > 0:
334 334 density = chainpayload / float(readdata)
335 335 else:
336 336 density = 1.0
337 337
338 338 # Cut the revs at collected indices
339 339 previdx = 0
340 340 while indicesheap:
341 341 idx = heapq.heappop(indicesheap)
342 342
343 343 chunk = _trimchunk(revlog, revs, previdx, idx)
344 344 if chunk:
345 345 yield chunk
346 346
347 347 previdx = idx
348 348
349 349 chunk = _trimchunk(revlog, revs, previdx)
350 350 if chunk:
351 351 yield chunk
352 352
353 353 def _trimchunk(revlog, revs, startidx, endidx=None):
354 354 """returns revs[startidx:endidx] without empty trailing revs
355 355
356 356 Doctest Setup
357 357 >>> revlog = _testrevlog([
358 358 ... 5, #0
359 359 ... 10, #1
360 360 ... 12, #2
361 361 ... 12, #3 (empty)
362 362 ... 17, #4
363 363 ... 21, #5
364 364 ... 21, #6 (empty)
365 365 ... ])
366 366
367 367 Contiguous cases:
368 368 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
369 369 [0, 1, 2, 3, 4, 5]
370 370 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
371 371 [0, 1, 2, 3, 4]
372 372 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
373 373 [0, 1, 2]
374 374 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
375 375 [2]
376 376 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
377 377 [3, 4, 5]
378 378 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
379 379 [3, 4]
380 380
381 381 Discontiguous cases:
382 382 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
383 383 [1, 3, 5]
384 384 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
385 385 [1]
386 386 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
387 387 [3, 5]
388 388 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
389 389 [3, 5]
390 390 """
391 391 length = revlog.length
392 392
393 393 if endidx is None:
394 394 endidx = len(revs)
395 395
396 396 # If we have a non-emtpy delta candidate, there are nothing to trim
397 397 if revs[endidx - 1] < len(revlog):
398 398 # Trim empty revs at the end, except the very first revision of a chain
399 399 while (endidx > 1
400 400 and endidx > startidx
401 401 and length(revs[endidx - 1]) == 0):
402 402 endidx -= 1
403 403
404 404 return revs[startidx:endidx]
405 405
406 406 def segmentspan(revlog, revs, deltainfo=None):
407 407 """Get the byte span of a segment of revisions
408 408
409 409 revs is a sorted array of revision numbers
410 410
411 411 >>> revlog = _testrevlog([
412 412 ... 5, #0
413 413 ... 10, #1
414 414 ... 12, #2
415 415 ... 12, #3 (empty)
416 416 ... 17, #4
417 417 ... ])
418 418
419 419 >>> segmentspan(revlog, [0, 1, 2, 3, 4])
420 420 17
421 421 >>> segmentspan(revlog, [0, 4])
422 422 17
423 423 >>> segmentspan(revlog, [3, 4])
424 424 5
425 425 >>> segmentspan(revlog, [1, 2, 3,])
426 426 7
427 427 >>> segmentspan(revlog, [1, 3])
428 428 7
429 429 """
430 430 if not revs:
431 431 return 0
432 432 if deltainfo is not None and len(revlog) <= revs[-1]:
433 433 if len(revs) == 1:
434 434 return deltainfo.deltalen
435 435 offset = revlog.end(len(revlog) - 1)
436 436 end = deltainfo.deltalen + offset
437 437 else:
438 438 end = revlog.end(revs[-1])
439 439 return end - revlog.start(revs[0])
440 440
441 441 def _textfromdelta(fh, revlog, baserev, delta, p1, p2, flags, expectednode):
442 442 """build full text from a (base, delta) pair and other metadata"""
443 443 # special case deltas which replace entire base; no need to decode
444 444 # base revision. this neatly avoids censored bases, which throw when
445 445 # they're decoded.
446 446 hlen = struct.calcsize(">lll")
447 447 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
448 448 len(delta) - hlen):
449 449 fulltext = delta[hlen:]
450 450 else:
451 451 # deltabase is rawtext before changed by flag processors, which is
452 452 # equivalent to non-raw text
453 453 basetext = revlog.revision(baserev, _df=fh, raw=False)
454 454 fulltext = mdiff.patch(basetext, delta)
455 455
456 456 try:
457 457 res = revlog._processflags(fulltext, flags, 'read', raw=True)
458 458 fulltext, validatehash = res
459 459 if validatehash:
460 460 revlog.checkhash(fulltext, expectednode, p1=p1, p2=p2)
461 461 if flags & REVIDX_ISCENSORED:
462 462 raise RevlogError(_('node %s is not censored') % expectednode)
463 463 except CensoredNodeError:
464 464 # must pass the censored index flag to add censored revisions
465 465 if not flags & REVIDX_ISCENSORED:
466 466 raise
467 467 return fulltext
468 468
469 469 @attr.s(slots=True, frozen=True)
470 470 class _deltainfo(object):
471 471 distance = attr.ib()
472 472 deltalen = attr.ib()
473 473 data = attr.ib()
474 474 base = attr.ib()
475 475 chainbase = attr.ib()
476 476 chainlen = attr.ib()
477 477 compresseddeltalen = attr.ib()
478 478 snapshotdepth = attr.ib()
479 479
480 480 def isgooddeltainfo(revlog, deltainfo, revinfo):
481 481 """Returns True if the given delta is good. Good means that it is within
482 482 the disk span, disk size, and chain length bounds that we know to be
483 483 performant."""
484 484 if deltainfo is None:
485 485 return False
486 486
487 487 # - 'deltainfo.distance' is the distance from the base revision --
488 488 # bounding it limits the amount of I/O we need to do.
489 489 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
490 490 # deltas we need to apply -- bounding it limits the amount of CPU
491 491 # we consume.
492 492
493 493 if revlog._sparserevlog:
494 494 # As sparse-read will be used, we can consider that the distance,
495 495 # instead of being the span of the whole chunk,
496 496 # is the span of the largest read chunk
497 497 base = deltainfo.base
498 498
499 499 if base != nullrev:
500 500 deltachain = revlog._deltachain(base)[0]
501 501 else:
502 502 deltachain = []
503 503
504 504 # search for the first non-snapshot revision
505 505 for idx, r in enumerate(deltachain):
506 506 if not revlog.issnapshot(r):
507 507 break
508 508 deltachain = deltachain[idx:]
509 509 chunks = slicechunk(revlog, deltachain, deltainfo)
510 510 all_span = [segmentspan(revlog, revs, deltainfo)
511 511 for revs in chunks]
512 512 distance = max(all_span)
513 513 else:
514 514 distance = deltainfo.distance
515 515
516 516 textlen = revinfo.textlen
517 517 defaultmax = textlen * 4
518 518 maxdist = revlog._maxdeltachainspan
519 519 if not maxdist:
520 520 maxdist = distance # ensure the conditional pass
521 521 maxdist = max(maxdist, defaultmax)
522 522 if revlog._sparserevlog and maxdist < revlog._srmingapsize:
523 523 # In multiple place, we are ignoring irrelevant data range below a
524 524 # certain size. Be also apply this tradeoff here and relax span
525 525 # constraint for small enought content.
526 526 maxdist = revlog._srmingapsize
527 527
528 528 # Bad delta from read span:
529 529 #
530 530 # If the span of data read is larger than the maximum allowed.
531 531 if maxdist < distance:
532 532 return False
533 533
534 534 # Bad delta from new delta size:
535 535 #
536 536 # If the delta size is larger than the target text, storing the
537 537 # delta will be inefficient.
538 538 if textlen < deltainfo.deltalen:
539 539 return False
540 540
541 541 # Bad delta from cumulated payload size:
542 542 #
543 543 # If the sum of delta get larger than K * target text length.
544 544 if textlen * LIMIT_DELTA2TEXT < deltainfo.compresseddeltalen:
545 545 return False
546 546
547 547 # Bad delta from chain length:
548 548 #
549 549 # If the number of delta in the chain gets too high.
550 550 if (revlog._maxchainlen
551 551 and revlog._maxchainlen < deltainfo.chainlen):
552 552 return False
553 553
554 554 # bad delta from intermediate snapshot size limit
555 555 #
556 556 # If an intermediate snapshot size is higher than the limit. The
557 557 # limit exist to prevent endless chain of intermediate delta to be
558 558 # created.
559 559 if (deltainfo.snapshotdepth is not None and
560 560 (textlen >> deltainfo.snapshotdepth) < deltainfo.deltalen):
561 561 return False
562 562
563 563 # bad delta if new intermediate snapshot is larger than the previous
564 564 # snapshot
565 565 if (deltainfo.snapshotdepth
566 566 and revlog.length(deltainfo.base) < deltainfo.deltalen):
567 567 return False
568 568
569 569 return True
570 570
571 def _candidategroups(revlog, p1, p2, cachedelta):
571 def _candidategroups(revlog, textlen, p1, p2, cachedelta):
572 572 """Provides group of revision to be tested as delta base
573 573
574 574 This top level function focus on emitting groups with unique and worthwhile
575 575 content. See _raw_candidate_groups for details about the group order.
576 576 """
577 577 # should we try to build a delta?
578 578 if not (len(revlog) and revlog._storedeltachains):
579 579 return
580 580
581 deltalength = revlog.length
582 deltaparent = revlog.deltaparent
583
584 deltas_limit = textlen * LIMIT_DELTA2TEXT
585
581 586 tested = set([nullrev])
582 for group in _rawgroups(revlog, p1, p2, cachedelta):
583 group = tuple(r for r in group if r not in tested)
584 tested.update(group)
587 for temptative in _rawgroups(revlog, p1, p2, cachedelta):
588 group = []
589 for rev in temptative:
590 # skip over empty delta (no need to include them in a chain)
591 while not (rev == nullrev or rev in tested or deltalength(rev)):
592 rev = deltaparent(rev)
593 tested.add(rev)
594 # filter out revision we tested already
595 if rev in tested:
596 continue
597 tested.add(rev)
598 # filter out delta base that will never produce good delta
599 if deltas_limit < revlog.length(rev):
600 continue
601 # no need to try a delta against nullrev, this will be done as a
602 # last resort.
603 if rev == nullrev:
604 continue
605 # no delta for rawtext-changing revs (see "candelta" for why)
606 if revlog.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
607 continue
608 group.append(rev)
585 609 if group:
586 yield group
610 yield tuple(group)
587 611
588 612 def _rawgroups(revlog, p1, p2, cachedelta):
589 613 """Provides group of revision to be tested as delta base
590 614
591 615 This lower level function focus on emitting delta theorically interresting
592 616 without looking it any practical details.
593 617
594 618 The group order aims at providing fast or small candidates first.
595 619 """
596 620 gdelta = revlog._generaldelta
597 621 curr = len(revlog)
598 622 prev = curr - 1
599 623
600 624 # should we try to build a delta?
601 625 if prev != nullrev and revlog._storedeltachains:
602 626 tested = set()
603 627 # This condition is true most of the time when processing
604 628 # changegroup data into a generaldelta repo. The only time it
605 629 # isn't true is if this is the first revision in a delta chain
606 630 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
607 631 if cachedelta and gdelta and revlog._lazydeltabase:
608 632 # Assume what we received from the server is a good choice
609 633 # build delta will reuse the cache
610 634 yield (cachedelta[0],)
611 635 tested.add(cachedelta[0])
612 636
613 637 # This condition is true most of the time when processing
614 638 # changegroup data into a generaldelta repo. The only time it
615 639 # isn't true is if this is the first revision in a delta chain
616 640 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
617 641 if cachedelta and gdelta and revlog._lazydeltabase:
618 642 # Assume what we received from the server is a good choice
619 643 # build delta will reuse the cache
620 644 yield (cachedelta[0],)
621 645
622 646 if gdelta:
623 647 # exclude already lazy tested base if any
624 648 parents = [p for p in (p1, p2) if p != nullrev]
625 649
626 650 if not revlog._deltabothparents and len(parents) == 2:
627 651 parents.sort()
628 652 # To minimize the chance of having to build a fulltext,
629 653 # pick first whichever parent is closest to us (max rev)
630 654 yield (parents[1],)
631 655 # then the other one (min rev) if the first did not fit
632 656 yield (parents[0],)
633 657 elif len(parents) > 0:
634 658 # Test all parents (1 or 2), and keep the best candidate
635 659 yield parents
636 660
637 661 # other approach failed try against prev to hopefully save us a
638 662 # fulltext.
639 663 yield (prev,)
640 664
641 665 class deltacomputer(object):
642 666 def __init__(self, revlog):
643 667 self.revlog = revlog
644 668
645 669 def buildtext(self, revinfo, fh):
646 670 """Builds a fulltext version of a revision
647 671
648 672 revinfo: _revisioninfo instance that contains all needed info
649 673 fh: file handle to either the .i or the .d revlog file,
650 674 depending on whether it is inlined or not
651 675 """
652 676 btext = revinfo.btext
653 677 if btext[0] is not None:
654 678 return btext[0]
655 679
656 680 revlog = self.revlog
657 681 cachedelta = revinfo.cachedelta
658 682 baserev = cachedelta[0]
659 683 delta = cachedelta[1]
660 684
661 685 fulltext = btext[0] = _textfromdelta(fh, revlog, baserev, delta,
662 686 revinfo.p1, revinfo.p2,
663 687 revinfo.flags, revinfo.node)
664 688 return fulltext
665 689
666 690 def _builddeltadiff(self, base, revinfo, fh):
667 691 revlog = self.revlog
668 692 t = self.buildtext(revinfo, fh)
669 693 if revlog.iscensored(base):
670 694 # deltas based on a censored revision must replace the
671 695 # full content in one patch, so delta works everywhere
672 696 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
673 697 delta = header + t
674 698 else:
675 699 ptext = revlog.revision(base, _df=fh, raw=True)
676 700 delta = mdiff.textdiff(ptext, t)
677 701
678 702 return delta
679 703
680 704 def _builddeltainfo(self, revinfo, base, fh):
681 705 # can we use the cached delta?
682 706 if revinfo.cachedelta and revinfo.cachedelta[0] == base:
683 707 delta = revinfo.cachedelta[1]
684 708 else:
685 709 delta = self._builddeltadiff(base, revinfo, fh)
686 710 revlog = self.revlog
687 711 header, data = revlog.compress(delta)
688 712 deltalen = len(header) + len(data)
689 713 chainbase = revlog.chainbase(base)
690 714 offset = revlog.end(len(revlog) - 1)
691 715 dist = deltalen + offset - revlog.start(chainbase)
692 716 if revlog._generaldelta:
693 717 deltabase = base
694 718 else:
695 719 deltabase = chainbase
696 720 chainlen, compresseddeltalen = revlog._chaininfo(base)
697 721 chainlen += 1
698 722 compresseddeltalen += deltalen
699 723
700 724 revlog = self.revlog
701 725 snapshotdepth = None
702 726 if deltabase == nullrev:
703 727 snapshotdepth = 0
704 728 elif revlog._sparserevlog and revlog.issnapshot(deltabase):
705 729 # A delta chain should always be one full snapshot,
706 730 # zero or more semi-snapshots, and zero or more deltas
707 731 p1, p2 = revlog.rev(revinfo.p1), revlog.rev(revinfo.p2)
708 732 if deltabase not in (p1, p2) and revlog.issnapshot(deltabase):
709 733 snapshotdepth = len(revlog._deltachain(deltabase)[0])
710 734
711 735 return _deltainfo(dist, deltalen, (header, data), deltabase,
712 736 chainbase, chainlen, compresseddeltalen,
713 737 snapshotdepth)
714 738
715 739 def _fullsnapshotinfo(self, fh, revinfo):
716 740 curr = len(self.revlog)
717 741 rawtext = self.buildtext(revinfo, fh)
718 742 data = self.revlog.compress(rawtext)
719 743 compresseddeltalen = deltalen = dist = len(data[1]) + len(data[0])
720 744 deltabase = chainbase = curr
721 745 snapshotdepth = 0
722 746 chainlen = 1
723 747
724 748 return _deltainfo(dist, deltalen, data, deltabase,
725 749 chainbase, chainlen, compresseddeltalen,
726 750 snapshotdepth)
727 751
728 752 def finddeltainfo(self, revinfo, fh):
729 753 """Find an acceptable delta against a candidate revision
730 754
731 755 revinfo: information about the revision (instance of _revisioninfo)
732 756 fh: file handle to either the .i or the .d revlog file,
733 757 depending on whether it is inlined or not
734 758
735 759 Returns the first acceptable candidate revision, as ordered by
736 760 _candidategroups
737 761
738 762 If no suitable deltabase is found, we return delta info for a full
739 763 snapshot.
740 764 """
741 765 if not revinfo.textlen:
742 766 return self._fullsnapshotinfo(fh, revinfo)
743 767
744 768 # no delta for flag processor revision (see "candelta" for why)
745 769 # not calling candelta since only one revision needs test, also to
746 770 # avoid overhead fetching flags again.
747 771 if revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
748 772 return self._fullsnapshotinfo(fh, revinfo)
749 773
750 774 cachedelta = revinfo.cachedelta
751 775 p1 = revinfo.p1
752 776 p2 = revinfo.p2
753 777 revlog = self.revlog
754 778
755 deltalength = self.revlog.length
756 deltaparent = self.revlog.deltaparent
757
758 779 deltainfo = None
759 deltas_limit = revinfo.textlen * LIMIT_DELTA2TEXT
760 780 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
761 groups = _candidategroups(self.revlog, p1r, p2r, cachedelta)
781 groups = _candidategroups(self.revlog, revinfo.textlen,
782 p1r, p2r, cachedelta)
762 783 for candidaterevs in groups:
763 # filter out delta base that will never produce good delta
764 candidaterevs = [r for r in candidaterevs
765 if self.revlog.length(r) <= deltas_limit]
766 784 nominateddeltas = []
767 785 for candidaterev in candidaterevs:
768 # skip over empty delta (no need to include them in a chain)
769 while candidaterev != nullrev and not deltalength(candidaterev):
770 candidaterev = deltaparent(candidaterev)
771 # no need to try a delta against nullid, this will be handled
772 # by fulltext later.
773 if candidaterev == nullrev:
774 continue
775 # no delta for rawtext-changing revs (see "candelta" for why)
776 if revlog.flags(candidaterev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
777 continue
778 786 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
779 787 if isgooddeltainfo(self.revlog, candidatedelta, revinfo):
780 788 nominateddeltas.append(candidatedelta)
781 789 if nominateddeltas:
782 790 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
783 791 break
784 792
785 793 if deltainfo is None:
786 794 deltainfo = self._fullsnapshotinfo(fh, revinfo)
787 795 return deltainfo
General Comments 0
You need to be logged in to leave comments. Login now