##// END OF EJS Templates
rust-index: improve phase computation speed...
rust-index: improve phase computation speed While less memory efficient, using an array is *much* faster than using a HashMap, especially with the default hasher. It even makes the code simpler, so I'm not really sure what I was thinking in the first place, maybe it's more obvious now. This fix a significant performance regression when using the rust version of the code. (however, the C code still outperform rust on this operation) hg perf::phases on mozilla-try-2023-03-22 - 6.6.3: 0.451239 seconds - before: 0.982495 seconds - after: 0.265347 seconds - C code: 0.183241 second

File last commit:

r52253:99869dcf default
r52318:7c6d0b9d default
Show More
deltas.py
1883 lines | 65.5 KiB | text/x-python | PythonLexer
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 # revlogdeltas.py - Logic around delta computation for revlog
#
Raphaël Gomès
contributor: change mentions of mpm to olivia...
r47575 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 # Copyright 2018 Octobus <contact@octobus.net>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""Helper class to compute deltas stored inside revlogs"""
delta-find: introduce a base class for _DeltaSearch...
r52239 import abc
Boris Feld
snapshot: search for unrelated but reusable full-snapshot...
r39529 import collections
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 import struct
# import stuff from node for others to import from revlog
Augie Fackler
formatting: blacken the codebase...
r43346 from ..node import nullrev
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 from ..i18n import _
from .constants import (
revlog: factor the logic to determine the delta compression out...
r48245 COMP_MODE_DEFAULT,
COMP_MODE_INLINE,
COMP_MODE_PLAIN,
delta-find: add a delta-reuse policy that blindly accepts incoming deltas...
r50662 DELTA_BASE_REUSE_FORCE,
find-delta: pass the cache-delta usage policy alongside the cache-delta...
r50572 DELTA_BASE_REUSE_NO,
deltas: add code to display information about the result of `finddeltainfo`...
r50121 KIND_CHANGELOG,
KIND_FILELOG,
KIND_MANIFESTLOG,
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 REVIDX_ISCENSORED,
REVIDX_RAWTEXT_CHANGING_FLAGS,
)
Augie Fackler
formatting: blacken the codebase...
r43346 from ..thirdparty import attr
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
from .. import (
error,
mdiff,
Boris Feld
delta: have a native implementation of _findsnapshot...
r41141 util,
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 )
Augie Fackler
formatting: blacken the codebase...
r43346 from . import flagutil
flagprocessors: make `processflagsraw` a module level function...
r43262
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 # maximum <delta-chain-data>/<revision-text-length> ratio
LIMIT_DELTA2TEXT = 2
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class _testrevlog:
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 """minimalist fake revlog to use in doctests"""
Boris Feld
doctest: add a `issnapshot` method to _testrevlog...
r40677 def __init__(self, data, density=0.5, mingap=0, snapshot=()):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 """data is an list of revision payload boundaries"""
revlog: use the new Config classes in _testrevlog...
r51938 from .. import revlog
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 self._data = data
revlog: use the new Config classes in _testrevlog...
r51938 self.data_config = revlog.DataConfig()
self.data_config.sr_density_threshold = density
self.data_config.sr_min_gap_size = mingap
self.delta_config = revlog.DeltaConfig()
self.feature_config = revlog.FeatureConfig()
Boris Feld
doctest: add a `issnapshot` method to _testrevlog...
r40677 self._snapshot = set(snapshot)
Boris Feld
sparse-revlog: put the native implementation of slicechunktodensity to use...
r40745 self.index = None
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
def start(self, rev):
Boris Feld
revlog: fix pure python slicing test when chain contains nullrev...
r41110 if rev == nullrev:
return 0
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 if rev == 0:
return 0
return self._data[rev - 1]
def end(self, rev):
Boris Feld
revlog: fix pure python slicing test when chain contains nullrev...
r41110 if rev == nullrev:
return 0
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 return self._data[rev]
def length(self, rev):
return self.end(rev) - self.start(rev)
def __len__(self):
return len(self._data)
Boris Feld
doctest: add a `issnapshot` method to _testrevlog...
r40677 def issnapshot(self, rev):
Boris Feld
revlog: fix pure python slicing test when chain contains nullrev...
r41110 if rev == nullrev:
return True
Boris Feld
doctest: add a `issnapshot` method to _testrevlog...
r40677 return rev in self._snapshot
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
sparse-revlog: drop unused deltainfo parameter from _slicechunktodensity...
r40640 def slicechunk(revlog, revs, targetsize=None):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 """slice revs to reduce the amount of unrelated data to be read from disk.
``revs`` is sliced into groups that should be read in one time.
Assume that revs are sorted.
The initial chunk is sliced until the overall density (payload/chunks-span
revlog: remove legacy usage of `_srdensitythreshold`...
r51955 ratio) is above `revlog.data_config.sr_density_threshold`. No gap smaller
revlog: remove legacy usage of `_srmingapsize`...
r51956 than `revlog.data_config.sr_min_gap_size` is skipped.
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
If `targetsize` is set, no chunk larger than `targetsize` will be yield.
For consistency with other slicing choice, this limit won't go lower than
revlog: remove legacy usage of `_srmingapsize`...
r51956 `revlog.data_config.sr_min_gap_size`.
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
If individual revisions chunk are larger than this limit, they will still
be raised individually.
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678 >>> data = [
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 ... 5, #00 (5)
... 10, #01 (5)
... 12, #02 (2)
... 12, #03 (empty)
... 27, #04 (15)
... 31, #05 (4)
... 31, #06 (empty)
... 42, #07 (11)
... 47, #08 (5)
... 47, #09 (empty)
... 48, #10 (1)
... 51, #11 (3)
... 74, #12 (23)
... 85, #13 (11)
... 86, #14 (1)
... 91, #15 (5)
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678 ... ]
>>> revlog = _testrevlog(data, snapshot=range(16))
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
>>> list(slicechunk(revlog, list(range(16))))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
>>> list(slicechunk(revlog, [0, 15]))
[[0], [15]]
>>> list(slicechunk(revlog, [0, 11, 15]))
[[0], [11], [15]]
>>> list(slicechunk(revlog, [0, 11, 13, 15]))
[[0], [11, 13, 15]]
>>> list(slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
[[1, 2], [5, 8, 10, 11], [14]]
Slicing with a maximum chunk size
>>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
[[0], [11], [13], [15]]
>>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
[[0], [11], [13, 15]]
Boris Feld
revlog: fix pure python slicing test when chain contains nullrev...
r41110
Slicing involving nullrev
>>> list(slicechunk(revlog, [-1, 0, 11, 13, 15], targetsize=20))
[[-1, 0], [11], [13, 15]]
>>> list(slicechunk(revlog, [-1, 13, 15], targetsize=5))
[[-1], [13], [15]]
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 """
if targetsize is not None:
revlog: remove legacy usage of `_srmingapsize`...
r51956 targetsize = max(targetsize, revlog.data_config.sr_min_gap_size)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 # targetsize should not be specified when evaluating delta candidates:
# * targetsize is used to ensure we stay within specification when reading,
Boris Feld
sparse-revlog: put the native implementation of slicechunktodensity to use...
r40745 densityslicing = getattr(revlog.index, 'slicechunktodensity', None)
if densityslicing is None:
densityslicing = lambda x, y, z: _slicechunktodensity(revlog, x, y, z)
Augie Fackler
formatting: blacken the codebase...
r43346 for chunk in densityslicing(
revlog: remove legacy usage of `_srmingapsize`...
r51956 revs,
revlog.data_config.sr_density_threshold,
revlog.data_config.sr_min_gap_size,
Augie Fackler
formatting: blacken the codebase...
r43346 ):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
yield subchunk
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 def _slicechunktosize(revlog, revs, targetsize=None):
"""slice revs to match the target size
This is intended to be used on chunk that density slicing selected by that
are still too large compared to the read garantee of revlog. This might
happens when "minimal gap size" interrupted the slicing or when chain are
built in a way that create large blocks next to each other.
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678 >>> data = [
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 ... 3, #0 (3)
... 5, #1 (2)
... 6, #2 (1)
... 8, #3 (2)
... 8, #4 (empty)
... 11, #5 (3)
... 12, #6 (1)
... 13, #7 (1)
... 14, #8 (1)
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678 ... ]
== All snapshots cases ==
>>> revlog = _testrevlog(data, snapshot=range(9))
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
Cases where chunk is already small enough
>>> list(_slicechunktosize(revlog, [0], 3))
[[0]]
>>> list(_slicechunktosize(revlog, [6, 7], 3))
[[6, 7]]
>>> list(_slicechunktosize(revlog, [0], None))
[[0]]
>>> list(_slicechunktosize(revlog, [6, 7], None))
[[6, 7]]
cases where we need actual slicing
>>> list(_slicechunktosize(revlog, [0, 1], 3))
[[0], [1]]
>>> list(_slicechunktosize(revlog, [1, 3], 3))
[[1], [3]]
>>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
[[1, 2], [3]]
>>> list(_slicechunktosize(revlog, [3, 5], 3))
[[3], [5]]
>>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
[[3], [5]]
>>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
[[5], [6, 7, 8]]
>>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
[[0], [1, 2], [3], [5], [6, 7, 8]]
Case with too large individual chunk (must return valid chunk)
>>> list(_slicechunktosize(revlog, [0, 1], 2))
[[0], [1]]
>>> list(_slicechunktosize(revlog, [1, 3], 1))
[[1], [3]]
>>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
[[3], [5]]
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678
== No Snapshot cases ==
>>> revlog = _testrevlog(data)
Cases where chunk is already small enough
>>> list(_slicechunktosize(revlog, [0], 3))
[[0]]
>>> list(_slicechunktosize(revlog, [6, 7], 3))
[[6, 7]]
>>> list(_slicechunktosize(revlog, [0], None))
[[0]]
>>> list(_slicechunktosize(revlog, [6, 7], None))
[[6, 7]]
cases where we need actual slicing
>>> list(_slicechunktosize(revlog, [0, 1], 3))
[[0], [1]]
>>> list(_slicechunktosize(revlog, [1, 3], 3))
[[1], [3]]
>>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
[[1], [2, 3]]
>>> list(_slicechunktosize(revlog, [3, 5], 3))
[[3], [5]]
>>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
[[3], [4, 5]]
>>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
[[5], [6, 7, 8]]
>>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
[[0], [1, 2], [3], [5], [6, 7, 8]]
Case with too large individual chunk (must return valid chunk)
>>> list(_slicechunktosize(revlog, [0, 1], 2))
[[0], [1]]
>>> list(_slicechunktosize(revlog, [1, 3], 1))
[[1], [3]]
>>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
[[3], [5]]
== mixed case ==
>>> revlog = _testrevlog(data, snapshot=[0, 1, 2])
>>> list(_slicechunktosize(revlog, list(range(9)), 5))
[[0, 1], [2], [3, 4, 5], [6, 7, 8]]
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 """
assert targetsize is None or 0 <= targetsize
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678 startdata = revlog.start(revs[0])
enddata = revlog.end(revs[-1])
fullspan = enddata - startdata
if targetsize is None or fullspan <= targetsize:
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 yield revs
return
startrevidx = 0
Boris Feld
sparse-revlog: align endrevidx usages in the _slicechunktosize...
r40693 endrevidx = 1
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 iterrevs = enumerate(revs)
Augie Fackler
formatting: blacken the codebase...
r43346 next(iterrevs) # skip first rev.
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678 # first step: get snapshots out of the way
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 for idx, r in iterrevs:
span = revlog.end(r) - startdata
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678 snapshot = revlog.issnapshot(r)
if span <= targetsize and snapshot:
Boris Feld
sparse-revlog: align endrevidx usages in the _slicechunktosize...
r40693 endrevidx = idx + 1
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 else:
Boris Feld
sparse-revlog: align endrevidx usages in the _slicechunktosize...
r40693 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 if chunk:
yield chunk
startrevidx = idx
startdata = revlog.start(r)
Boris Feld
sparse-revlog: align endrevidx usages in the _slicechunktosize...
r40693 endrevidx = idx + 1
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678 if not snapshot:
break
# for the others, we use binary slicing to quickly converge toward valid
# chunks (otherwise, we might end up looking for start/end of many
# revisions). This logic is not looking for the perfect slicing point, it
# focuses on quickly converging toward valid chunks.
nbitem = len(revs)
while (enddata - startdata) > targetsize:
endrevidx = nbitem
if nbitem - startrevidx <= 1:
Augie Fackler
formatting: blacken the codebase...
r43346 break # protect against individual chunk larger than limit
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678 localenddata = revlog.end(revs[endrevidx - 1])
span = localenddata - startdata
Boris Feld
sparse-revlog: use `span` variable as intended...
r40690 while span > targetsize:
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678 if endrevidx - startrevidx <= 1:
Augie Fackler
formatting: blacken the codebase...
r43346 break # protect against individual chunk larger than limit
Boris Feld
sparse-revlog: rework the way we enforce chunk size limit...
r40678 endrevidx -= (endrevidx - startrevidx) // 2
localenddata = revlog.end(revs[endrevidx - 1])
span = localenddata - startdata
chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
if chunk:
yield chunk
startrevidx = endrevidx
startdata = revlog.start(revs[startrevidx])
chunk = _trimchunk(revlog, revs, startrevidx)
if chunk:
yield chunk
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
Augie Fackler
formatting: blacken the codebase...
r43346
def _slicechunktodensity(revlog, revs, targetdensity=0.5, mingapsize=0):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 """slice revs to reduce the amount of unrelated data to be read from disk.
``revs`` is sliced into groups that should be read in one time.
Assume that revs are sorted.
The initial chunk is sliced until the overall density (payload/chunks-span
ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
skipped.
>>> revlog = _testrevlog([
... 5, #00 (5)
... 10, #01 (5)
... 12, #02 (2)
... 12, #03 (empty)
... 27, #04 (15)
... 31, #05 (4)
... 31, #06 (empty)
... 42, #07 (11)
... 47, #08 (5)
... 47, #09 (empty)
... 48, #10 (1)
... 51, #11 (3)
... 74, #12 (23)
... 85, #13 (11)
... 86, #14 (1)
... 91, #15 (5)
... ])
>>> list(_slicechunktodensity(revlog, list(range(16))))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
>>> list(_slicechunktodensity(revlog, [0, 15]))
[[0], [15]]
>>> list(_slicechunktodensity(revlog, [0, 11, 15]))
[[0], [11], [15]]
>>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
[[0], [11, 13, 15]]
>>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
[[1, 2], [5, 8, 10, 11], [14]]
>>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
... mingapsize=20))
[[1, 2, 3, 5, 8, 10, 11], [14]]
>>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
... targetdensity=0.95))
[[1, 2], [5], [8, 10, 11], [14]]
>>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
... targetdensity=0.95, mingapsize=12))
[[1, 2], [5, 8, 10, 11], [14]]
"""
start = revlog.start
length = revlog.length
if len(revs) <= 1:
yield revs
return
Boris Feld
sparse-revlog: drop unused deltainfo parameter from _slicechunktodensity...
r40640 deltachainspan = segmentspan(revlog, revs)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
if deltachainspan < mingapsize:
yield revs
return
readdata = deltachainspan
Boris Feld
sparse-revlog: fast-path before computing payload size...
r40642 chainpayload = sum(length(r) for r in revs)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
if deltachainspan:
density = chainpayload / float(deltachainspan)
else:
density = 1.0
if density >= targetdensity:
yield revs
return
# Store the gaps in a heap to have them sorted by decreasing size
Boris Feld
sparse-revlog: stop using a heap to track gaps...
r40643 gaps = []
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 prevend = None
for i, rev in enumerate(revs):
Boris Feld
sparse-revlog: drop unused deltainfo parameter from _slicechunktodensity...
r40640 revstart = start(rev)
revlen = length(rev)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
# Skip empty revisions to form larger holes
if revlen == 0:
continue
if prevend is not None:
gapsize = revstart - prevend
# only consider holes that are large enough
if gapsize > mingapsize:
Boris Feld
sparse-revlog: stop using a heap to track gaps...
r40643 gaps.append((gapsize, i))
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
prevend = revstart + revlen
Boris Feld
sparse-revlog: stop using a heap to track gaps...
r40643 # sort the gaps to pop them from largest to small
gaps.sort()
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
# Collect the indices of the largest holes until the density is acceptable
Boris Feld
sparse-revlog: stop using a heap to track selected gap...
r40644 selected = []
Boris Feld
sparse-revlog: stop using a heap to track gaps...
r40643 while gaps and density < targetdensity:
gapsize, gapidx = gaps.pop()
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
Boris Feld
sparse-revlog: stop using a heap to track selected gap...
r40644 selected.append(gapidx)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
# the gap sizes are stored as negatives to be sorted decreasingly
# by the heap
Boris Feld
sparse-revlog: stop using a heap to track gaps...
r40643 readdata -= gapsize
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 if readdata > 0:
density = chainpayload / float(readdata)
else:
density = 1.0
Boris Feld
sparse-revlog: stop using a heap to track selected gap...
r40644 selected.sort()
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
# Cut the revs at collected indices
previdx = 0
Boris Feld
sparse-revlog: stop using a heap to track selected gap...
r40644 for idx in selected:
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
chunk = _trimchunk(revlog, revs, previdx, idx)
if chunk:
yield chunk
previdx = idx
chunk = _trimchunk(revlog, revs, previdx)
if chunk:
yield chunk
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 def _trimchunk(revlog, revs, startidx, endidx=None):
"""returns revs[startidx:endidx] without empty trailing revs
Doctest Setup
>>> revlog = _testrevlog([
... 5, #0
... 10, #1
... 12, #2
... 12, #3 (empty)
... 17, #4
... 21, #5
... 21, #6 (empty)
... ])
Contiguous cases:
>>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
[0, 1, 2, 3, 4, 5]
>>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
[0, 1, 2, 3, 4]
>>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
[0, 1, 2]
>>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
[2]
>>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
[3, 4, 5]
>>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
[3, 4]
Discontiguous cases:
>>> _trimchunk(revlog, [1, 3, 5, 6], 0)
[1, 3, 5]
>>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
[1]
>>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
[3, 5]
>>> _trimchunk(revlog, [1, 3, 5, 6], 1)
[3, 5]
"""
length = revlog.length
if endidx is None:
endidx = len(revs)
# If we have a non-emtpy delta candidate, there are nothing to trim
if revs[endidx - 1] < len(revlog):
# Trim empty revs at the end, except the very first revision of a chain
Augie Fackler
formatting: blacken the codebase...
r43346 while (
endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0
):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 endidx -= 1
return revs[startidx:endidx]
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
sparse-revlog: drop unused deltainfo parameter from segmentspan...
r40641 def segmentspan(revlog, revs):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 """Get the byte span of a segment of revisions
revs is a sorted array of revision numbers
>>> revlog = _testrevlog([
... 5, #0
... 10, #1
... 12, #2
... 12, #3 (empty)
... 17, #4
... ])
>>> segmentspan(revlog, [0, 1, 2, 3, 4])
17
>>> segmentspan(revlog, [0, 4])
17
>>> segmentspan(revlog, [3, 4])
5
>>> segmentspan(revlog, [1, 2, 3,])
7
>>> segmentspan(revlog, [1, 3])
7
"""
if not revs:
return 0
Boris Feld
sparse-revlog: drop unused deltainfo parameter from segmentspan...
r40641 end = revlog.end(revs[-1])
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 return end - revlog.start(revs[0])
Augie Fackler
formatting: blacken the codebase...
r43346
delta-computer: stop explicitly taking file handle...
r51913 def _textfromdelta(revlog, baserev, delta, p1, p2, flags, expectednode):
Boris Feld
revlogdeltas: extra fulltext building in its own function...
r39367 """build full text from a (base, delta) pair and other metadata"""
# special case deltas which replace entire base; no need to decode
# base revision. this neatly avoids censored bases, which throw when
# they're decoded.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 hlen = struct.calcsize(b">lll")
Augie Fackler
formatting: blacken the codebase...
r43346 if delta[:hlen] == mdiff.replacediffheader(
revlog.rawsize(baserev), len(delta) - hlen
):
Boris Feld
revlogdeltas: extra fulltext building in its own function...
r39367 fulltext = delta[hlen:]
else:
# deltabase is rawtext before changed by flag processors, which is
# equivalent to non-raw text
delta-computer: stop explicitly taking file handle...
r51913 basetext = revlog.revision(baserev)
Boris Feld
revlogdeltas: extra fulltext building in its own function...
r39367 fulltext = mdiff.patch(basetext, delta)
try:
flagprocessors: make `processflagsraw` a module level function...
r43262 validatehash = flagutil.processflagsraw(revlog, fulltext, flags)
Boris Feld
revlogdeltas: extra fulltext building in its own function...
r39367 if validatehash:
revlog.checkhash(fulltext, expectednode, p1=p1, p2=p2)
if flags & REVIDX_ISCENSORED:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.StorageError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'node %s is not censored') % expectednode
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
revlog: drop some more error aliases (API)...
r39810 except error.CensoredNodeError:
Boris Feld
revlogdeltas: extra fulltext building in its own function...
r39367 # must pass the censored index flag to add censored revisions
if not flags & REVIDX_ISCENSORED:
raise
return fulltext
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 @attr.s(slots=True, frozen=True)
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class _deltainfo:
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 distance = attr.ib()
deltalen = attr.ib()
data = attr.ib()
base = attr.ib()
chainbase = attr.ib()
chainlen = attr.ib()
compresseddeltalen = attr.ib()
snapshotdepth = attr.ib()
Augie Fackler
formatting: blacken the codebase...
r43346
revlog: introduce a plain compression mode...
r48027 def drop_u_compression(delta):
"""turn into a "u" (no-compression) into no-compression without header
This is useful for revlog format that has better compression method.
"""
assert delta.data[0] == b'u', delta.data[0]
return _deltainfo(
delta.distance,
delta.deltalen - 1,
(b'', delta.data[1]),
delta.base,
delta.chainbase,
delta.chainlen,
delta.compresseddeltalen,
delta.snapshotdepth,
)
Boris Feld
delta: exclude base candidate much smaller than the target...
r41014 # If a revision's full text is that much bigger than a base candidate full
# text's, it is very unlikely that it will produce a valid delta. We no longer
# consider these candidates.
Boris Feld
revlog: limit base to rev size ratio to 500 instead of 50...
r41062 LIMIT_BASE2TEXT = 500
Boris Feld
delta: exclude base candidate much smaller than the target...
r41014
delta-find: explicitly track stage of the search...
r52242 ### stage of the search, used for debug and to select and to adjust some logic.
# initial stage, next step is unknown
_STAGE_UNSPECIFIED = "unspecified"
# trying the cached delta
_STAGE_CACHED = "cached"
# trying delta based on parents
_STAGE_PARENTS = "parents"
# trying to build a valid snapshot of any level
_STAGE_SNAPSHOT = "snapshot"
# trying to build a delta based of the previous revision
_STAGE_PREV = "prev"
# trying to build a full snapshot
_STAGE_FULL = "full"
Augie Fackler
formatting: blacken the codebase...
r43346
delta-find: introduce a base class for _DeltaSearch...
r52239 class _BaseDeltaSearch(abc.ABC):
delta-find: introduce a _DeltaSearch object...
r52213 """perform the search of a good delta for a single revlog revision
delta-find: assume the target-rev if not specified...
r51330
delta-find: introduce a _DeltaSearch object...
r52213 note: some of the deltacomputer.finddeltainfo logic should probably move
here.
"""
delta-find: never do anything fancy when general delta is off...
r51331
delta-find: introduce a _DeltaSearch object...
r52213 def __init__(
self,
debug: add an option to display statistic about a unbundling operation...
r50506 revlog,
delta-find: feed revinfo to _DeltaSearch...
r52223 revinfo,
debug: add an option to display statistic about a unbundling operation...
r50506 p1,
p2,
cachedelta,
delta-find: introduce a _DeltaSearch object...
r52213 excluded_bases=None,
target_rev=None,
snapshot_cache=None,
):
delta-find: check DELTA_BASE_REUSE_FORCE in the _DeltaSearch.__init__...
r52218 # the DELTA_BASE_REUSE_FORCE case should have been taken care of sooner
# so we should never end up asking such question. Adding the assert as
# a safe-guard to detect anything that would be fishy in this regard.
assert (
cachedelta is None
or cachedelta[2] != DELTA_BASE_REUSE_FORCE
or not revlog.delta_config.general_delta
)
delta-find: introduce a _DeltaSearch object...
r52213 self.revlog = revlog
delta-find: feed revinfo to _DeltaSearch...
r52223 self.revinfo = revinfo
self.textlen = revinfo.textlen
delta-find: introduce a _DeltaSearch object...
r52213 self.p1 = p1
self.p2 = p2
self.cachedelta = cachedelta
self.excluded_bases = excluded_bases
delta-find: move target_rev in the _DeltaSearch.__init__...
r52217 if target_rev is None:
self.target_rev = len(self.revlog)
delta-find: introduce a _DeltaSearch object...
r52213 self.target_rev = target_rev
delta-find: move snapshot_cache in the _DeltaSearch.__init__...
r52216 if snapshot_cache is None:
# map: base-rev: [snapshot-revs]
snapshot_cache = SnapshotCache()
delta-find: introduce a _DeltaSearch object...
r52213 self.snapshot_cache = snapshot_cache
delta-find: move tested in the _DeltaSearch.__init__...
r52219 self.tested = {nullrev}
delta-find: explicitly track stage of the search...
r52242 self.current_stage = _STAGE_UNSPECIFIED
delta-find: introduce a base class for _DeltaSearch...
r52239 self.current_group = None
self._init_group()
delta-find: move away from the generator API for _DeltaSearch...
r52227
delta-find: move good delta code earlier in the class...
r52237 def is_good_delta_info(self, deltainfo):
"""Returns True if the given delta is good.
Good means that it is within the disk span, disk size, and chain length
bounds that we know to be performant.
"""
if not self._is_good_delta_info_universal(deltainfo):
return False
if not self._is_good_delta_info_chain_quality(deltainfo):
return False
return True
def _is_good_delta_info_universal(self, deltainfo):
"""Returns True if the given delta is good.
This performs generic checks needed by all format variants.
This is used by is_good_delta_info.
"""
if deltainfo is None:
return False
# the DELTA_BASE_REUSE_FORCE case should have been taken care of sooner
# so we should never end up asking such question. Adding the assert as
# a safe-guard to detect anything that would be fishy in this regard.
assert (
self.revinfo.cachedelta is None
or self.revinfo.cachedelta[2] != DELTA_BASE_REUSE_FORCE
or not self.revlog.delta_config.general_delta
)
# Bad delta from new delta size:
#
# If the delta size is larger than the target text, storing the delta
# will be inefficient.
if self.revinfo.textlen < deltainfo.deltalen:
return False
return True
def _is_good_delta_info_chain_quality(self, deltainfo):
"""Returns True if the chain associated with the delta is good.
This performs checks for format that use delta chains.
This is used by is_good_delta_info.
"""
# - 'deltainfo.distance' is the distance from the base revision --
# bounding it limits the amount of I/O we need to do.
defaultmax = self.revinfo.textlen * 4
maxdist = self.revlog.delta_config.max_deltachain_span
if not maxdist:
maxdist = deltainfo.distance # ensure the conditional pass
maxdist = max(maxdist, defaultmax)
# Bad delta from read span:
#
# If the span of data read is larger than the maximum allowed.
#
# In the sparse-revlog case, we rely on the associated "sparse
# reading" to avoid issue related to the span of data. In theory, it
# would be possible to build pathological revlog where delta pattern
# would lead to too many reads. However, they do not happen in
# practice at all. So we skip the span check entirely.
if (
not self.revlog.delta_config.sparse_revlog
and maxdist < deltainfo.distance
):
return False
# Bad delta from cumulated payload size:
#
# - 'deltainfo.compresseddeltalen' is the sum of the total size of
# deltas we need to apply -- bounding it limits the amount of CPU
# we consume.
max_chain_data = self.revinfo.textlen * LIMIT_DELTA2TEXT
# If the sum of delta get larger than K * target text length.
if max_chain_data < deltainfo.compresseddeltalen:
return False
# Bad delta from chain length:
#
# If the number of delta in the chain gets too high.
if (
self.revlog.delta_config.max_chain_len
and self.revlog.delta_config.max_chain_len < deltainfo.chainlen
):
return False
return True
delta-find: move away from the generator API for _DeltaSearch...
r52227 @property
def done(self):
"""True when all possible candidate have been tested"""
return self.current_group is None
delta-find: introduce a base class for _DeltaSearch...
r52239 @abc.abstractmethod
delta-find: move away from the generator API for _DeltaSearch...
r52227 def next_group(self, good_delta=None):
"""move to the next group to test
The group of revision to test will be available in
`self.current_group`. If the previous group had any good delta, the
best one can be passed as the `good_delta` parameter to help selecting
the next group.
If not revision remains to be, `self.done` will be True and
`self.current_group` will be None.
"""
delta-find: introduce a base class for _DeltaSearch...
r52239 pass
@abc.abstractmethod
def _init_group(self):
pass
delta-find: introduce and use specialized _DeltaSearch class...
r52240 class _NoDeltaSearch(_BaseDeltaSearch):
"""Search for no delta.
This search variant is to be used in case where we should not store delta.
"""
def _init_group(self):
delta-find: explicitly track stage of the search...
r52242 self.current_stage = _STAGE_FULL
delta-find: introduce and use specialized _DeltaSearch class...
r52240
def next_group(self, good_delta=None):
pass
class _PrevDeltaSearch(_BaseDeltaSearch):
"""Search for delta against the previous revision only
This search variant is to be used when the format does not allow for delta
against arbitrary bases.
"""
def _init_group(self):
delta-find: explicitly track stage of the search...
r52242 self.current_stage = _STAGE_PREV
delta-find: introduce and use specialized _DeltaSearch class...
r52240 self.current_group = [self.target_rev - 1]
delta-find: remove the "candidate groups" layer...
r52244 self.tested.update(self.current_group)
delta-find: introduce and use specialized _DeltaSearch class...
r52240
def next_group(self, good_delta=None):
delta-find: explicitly track stage of the search...
r52242 self.current_stage = _STAGE_FULL
delta-find: introduce and use specialized _DeltaSearch class...
r52240 self.current_group = None
delta-find: split the _DeltaSearch class in two...
r52250 class _GeneralDeltaSearch(_BaseDeltaSearch):
"""Delta search variant for general-delta repository"""
delta-find: introduce and use specialized _DeltaSearch class...
r52240
delta-find: introduce a base class for _DeltaSearch...
r52239 def _init_group(self):
delta-find: introduce and use specialized _DeltaSearch class...
r52240 # Why search for delta base if we cannot use a delta base ?
# also see issue6056
assert self.revlog.delta_config.general_delta
delta-find: finish reworking the snapshot logic and drop more layer...
r52249 self._candidates_iterator = self._iter_groups()
delta-find: introduce a base class for _DeltaSearch...
r52239 self._last_good = None
delta-find: explicitly deal with usage of the cached revision...
r52245 if (
self.cachedelta is not None
and self.cachedelta[2] > DELTA_BASE_REUSE_NO
and self._pre_filter_rev(self.cachedelta[0])
):
# First we try to reuse a the delta contained in the bundle. (or from
# the source revlog)
#
# This logic only applies to general delta repositories and can be
# disabled through configuration. Disabling reuse source delta is
# useful when we want to make sure we recomputed "optimal" deltas.
self.current_stage = _STAGE_CACHED
self._internal_group = (self.cachedelta[0],)
self._internal_idx = 0
self.current_group = self._internal_group
self.tested.update(self.current_group)
else:
self._next_internal_group()
delta-find: remove the "candidate groups" layer...
r52244
def _next_internal_group(self):
# self._internal_group can be larger than self.current_group
self._internal_idx = 0
group = self._candidates_iterator.send(self._last_good)
if group is not None:
group = self._pre_filter_candidate_revs(group)
self._internal_group = group
if self._internal_group is None:
self.current_group = None
elif len(self._internal_group) == 0:
self.next_group()
else:
chunk_size = self.revlog.delta_config.candidate_group_chunk_size
if chunk_size > 0:
self.current_group = self._internal_group[:chunk_size]
self._internal_idx += chunk_size
else:
self.current_group = self._internal_group
self._internal_idx += len(self.current_group)
self.tested.update(self.current_group)
delta-find: introduce a base class for _DeltaSearch...
r52239
def next_group(self, good_delta=None):
delta-find: remove the "candidate groups" layer...
r52244 old_good = self._last_good
delta-find: move away from the generator API for _DeltaSearch...
r52227 if good_delta is not None:
delta-find: pass the full deltainfo to the _DeltaSearch class...
r52253 self._last_good = good_delta
delta-find: explicitly deal with usage of the cached revision...
r52245 if self.current_stage == _STAGE_CACHED and good_delta is not None:
# the cache is good, let us use the cache as requested
self._candidates_iterator = None
self._internal_group = None
self._internal_idx = None
self.current_group = None
return
delta-find: remove the "candidate groups" layer...
r52244 if (self._internal_idx < len(self._internal_group)) and (
old_good != good_delta
):
# When the size of the candidate group is big, it can result in
# a quite significant performance impact. To reduce this, we
# can send them in smaller batches until the new batch does not
# provide any improvements.
#
# This might reduce the overall efficiency of the compression
# in some corner cases, but that should also prevent very
# pathological cases from being an issue. (eg. 20 000
# candidates).
#
# XXX note that the ordering of the group becomes important as
# it now impacts the final result. The current order is
# unprocessed and can be improved.
next_idx = self._internal_idx + self._group_chunk_size
self.current_group = self._internal_group[
self._internal_idx : next_idx
]
self.tested.update(self.current_group)
self._internal_idx = next_idx
else:
self._next_internal_group()
Boris Feld
revlogdeltas: split candidate groups selection from the filtering logic...
r39372
delta-find: move pre-filtering of candidates in its own function...
r52228 def _pre_filter_candidate_revs(self, temptative):
"""filter possible candidate before computing a delta
This function use various criteria to pre-filter candidate delta base
before we compute a delta and evaluate its quality.
Such pre-filter limit the number of computed delta, an expensive operation.
return the updated list of revision to test
"""
deltalength = self.revlog.length
deltaparent = self.revlog.deltaparent
tested = self.tested
group = []
for rev in temptative:
# skip over empty delta (no need to include them in a chain)
while not (rev == nullrev or rev in tested or deltalength(rev)):
tested.add(rev)
rev = deltaparent(rev)
delta-find: move pre-filtering of individual revision in its own function...
r52229 if self._pre_filter_rev(rev):
group.append(rev)
else:
self.tested.add(rev)
return group
delta-find: split the generic part of `_pre_filter_rev` in a method...
r52231 def _pre_filter_rev_universal(self, rev):
"""pre filtering that is need in all cases.
return True if it seems okay to test a rev, False otherwise.
used by _pre_filter_rev.
"""
delta-find: drop the temporary indent...
r52230 # no need to try a delta against nullrev, this will be done as
# a last resort.
if rev == nullrev:
return False
# filter out revision we tested already
if rev in self.tested:
return False
delta-find: move pre-filtering of candidates in its own function...
r52228
delta-find: drop the temporary indent...
r52230 # an higher authority deamed the base unworthy (e.g. censored)
if self.excluded_bases is not None and rev in self.excluded_bases:
return False
# We are in some recomputation cases and that rev is too high
# in the revlog
if self.target_rev is not None and rev >= self.target_rev:
return False
delta-find: split the generic part of `_pre_filter_rev` in a method...
r52231 # no delta for rawtext-changing revs (see "candelta" for why)
if self.revlog.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
return False
return True
delta-find: split the delta-chain part of `_pre_filter_rev` in a method...
r52233 def _pre_filter_rev_delta_chain(self, rev):
"""pre filtering that is needed in sparse revlog cases
delta-find: split the "sparse" part of `_pre_filter_rev` in a method...
r52232
delta-find: split the delta-chain part of `_pre_filter_rev` in a method...
r52233 return True if it seems okay to test a rev, False otherwise.
used by _pre_filter_rev.
"""
delta-find: split the "sparse" part of `_pre_filter_rev` in a method...
r52232 deltas_limit = self.revinfo.textlen * LIMIT_DELTA2TEXT
# filter out delta base that will never produce good delta
#
# if the delta of that base is already bigger than the limit
# for the delta chain size, doing a delta is hopeless.
if deltas_limit < self.revlog.length(rev):
return False
# If we reach here, we are about to build and test a delta.
# The delta building process will compute the chaininfo in all
# case, since that computation is cached, it is fine to access
# it here too.
chainlen, chainsize = self.revlog._chaininfo(rev)
# if chain will be too long, skip base
if (
self.revlog.delta_config.max_chain_len
and chainlen >= self.revlog.delta_config.max_chain_len
):
return False
# if chain already have too much data, skip base
if deltas_limit < chainsize:
return False
delta-find: split the delta-chain part of `_pre_filter_rev` in a method...
r52233 return True
delta-find: split the "sparse" part of `_pre_filter_rev` in a method...
r52232
delta-find: split the delta-chain part of `_pre_filter_rev` in a method...
r52233 def _pre_filter_rev(self, rev):
"""return True if it seems okay to test a rev, False otherwise"""
if not self._pre_filter_rev_universal(rev):
return False
if not self._pre_filter_rev_delta_chain(rev):
return False
delta-find: split the "sparse" part of `_pre_filter_rev` in a method...
r52232 return True
delta-find: move the emotion of parents in a dedicated method...
r52246 def _iter_parents(self):
# exclude already lazy tested base if any
parents = [p for p in (self.p1, self.p2) if p != nullrev]
self.current_stage = _STAGE_PARENTS
if (
not self.revlog.delta_config.delta_both_parents
and len(parents) == 2
):
parents.sort()
# To minimize the chance of having to build a fulltext,
# pick first whichever parent is closest to us (max rev)
yield (parents[1],)
# then the other one (min rev) if the first did not fit
yield (parents[0],)
elif len(parents) > 0:
# Test all parents (1 or 2), and keep the best candidate
yield parents
delta-find: move the emotion of prev in a dedicated method...
r52247 def _iter_prev(self):
# other approach failed try against prev to hopefully save us a
# fulltext.
self.current_stage = _STAGE_PREV
yield (self.target_rev - 1,)
delta-find: split the _DeltaSearch class in two...
r52250 def _iter_groups(self):
good = None
for group in self._iter_parents():
good = yield group
if good is not None:
break
else:
assert good is None
yield from self._iter_prev()
yield None
class _SparseDeltaSearch(_GeneralDeltaSearch):
"""Delta search variants for sparse-revlog"""
delta-find: move sparse-revlog delta checks in the associated class...
r52251 def is_good_delta_info(self, deltainfo):
"""Returns True if the given delta is good.
Good means that it is within the disk span, disk size, and chain length
bounds that we know to be performant.
"""
if not self._is_good_delta_info_universal(deltainfo):
return False
if not self._is_good_delta_info_chain_quality(deltainfo):
return False
if not self._is_good_delta_info_snapshot_constraints(deltainfo):
return False
return True
def _is_good_delta_info_snapshot_constraints(self, deltainfo):
"""Returns True if the chain associated with snapshots
This performs checks for format that use sparse-revlog and intermediate
snapshots.
This is used by is_good_delta_info.
"""
# if not a snapshot, this method has no filtering to do
if deltainfo.snapshotdepth is None:
return True
# bad delta from intermediate snapshot size limit
#
# If an intermediate snapshot size is higher than the limit. The
# limit exist to prevent endless chain of intermediate delta to be
# created.
if (
self.revinfo.textlen >> deltainfo.snapshotdepth
) < deltainfo.deltalen:
return False
# bad delta if new intermediate snapshot is larger than the previous
# snapshot
if self.revlog.length(deltainfo.base) < deltainfo.deltalen:
return False
return True
delta-find: move sparse-revlog pre-filtering in the associated class...
r52252 def _pre_filter_rev(self, rev):
"""return True if it seems okay to test a rev, False otherwise"""
if not self._pre_filter_rev_universal(rev):
return False
if not self._pre_filter_rev_delta_chain(rev):
return False
if not self._pre_filter_rev_sparse(rev):
return False
return True
def _pre_filter_rev_sparse(self, rev):
"""pre filtering that is needed in sparse revlog cases
return True if it seems okay to test a rev, False otherwise.
used by _pre_filter_rev.
"""
assert self.revlog.delta_config.sparse_revlog
# if the revision we test again is too small, the resulting delta
# will be large anyway as that amount of data to be added is big
if self.revlog.rawsize(rev) < (self.textlen // LIMIT_BASE2TEXT):
return False
if self.revlog.delta_config.upper_bound_comp is not None:
maxcomp = self.revlog.delta_config.upper_bound_comp
basenotsnap = (self.p1, self.p2, nullrev)
if rev not in basenotsnap and self.revlog.issnapshot(rev):
snapshotdepth = self.revlog.snapshotdepth(rev)
# If text is significantly larger than the base, we can
# expect the resulting delta to be proportional to the size
# difference
revsize = self.revlog.rawsize(rev)
rawsizedistance = max(self.textlen - revsize, 0)
# use an estimate of the compression upper bound.
lowestrealisticdeltalen = rawsizedistance // maxcomp
# check the absolute constraint on the delta size
snapshotlimit = self.textlen >> snapshotdepth
if snapshotlimit < lowestrealisticdeltalen:
# delta lower bound is larger than accepted upper
# bound
return False
# check the relative constraint on the delta size
revlength = self.revlog.length(rev)
if revlength < lowestrealisticdeltalen:
# delta probable lower bound is larger than target
# base
return False
return True
delta-find: move the base of the delta search in its own function...
r52248 def _iter_snapshots_base(self):
assert self.revlog.delta_config.sparse_revlog
delta-find: finish reworking the snapshot logic and drop more layer...
r52249 assert self.current_stage == _STAGE_SNAPSHOT
delta-find: move the base of the delta search in its own function...
r52248 prev = self.target_rev - 1
deltachain = lambda rev: self.revlog._deltachain(rev)[0]
parents = [p for p in (self.p1, self.p2) if p != nullrev]
if not parents:
return
# See if we can use an existing snapshot in the parent chains to
# use as a base for a new intermediate-snapshot
#
# search for snapshot in parents delta chain map: snapshot-level:
# snapshot-rev
parents_snaps = collections.defaultdict(set)
candidate_chains = [deltachain(p) for p in parents]
for chain in candidate_chains:
for idx, s in enumerate(chain):
if not self.revlog.issnapshot(s):
break
parents_snaps[idx].add(s)
snapfloor = min(parents_snaps[0]) + 1
self.snapshot_cache.update(self.revlog, snapfloor)
# search for the highest "unrelated" revision
#
# Adding snapshots used by "unrelated" revision increase the odd we
# reuse an independant, yet better snapshot chain.
#
# XXX instead of building a set of revisions, we could lazily
# enumerate over the chains. That would be more efficient, however
# we stick to simple code for now.
all_revs = set()
for chain in candidate_chains:
all_revs.update(chain)
other = None
for r in self.revlog.revs(prev, snapfloor):
if r not in all_revs:
other = r
break
if other is not None:
# To avoid unfair competition, we won't use unrelated
# intermediate snapshot that are deeper than the ones from the
# parent delta chain.
max_depth = max(parents_snaps.keys())
chain = deltachain(other)
for depth, s in enumerate(chain):
if s < snapfloor:
continue
if max_depth < depth:
break
if not self.revlog.issnapshot(s):
break
parents_snaps[depth].add(s)
# Test them as possible intermediate snapshot base We test them
# from highest to lowest level. High level one are more likely to
# result in small delta
floor = None
for idx, snaps in sorted(parents_snaps.items(), reverse=True):
siblings = set()
for s in snaps:
siblings.update(self.snapshot_cache.snapshots[s])
# Before considering making a new intermediate snapshot, we
# check if an existing snapshot, children of base we consider,
# would be suitable.
#
# It give a change to reuse a delta chain "unrelated" to the
# current revision instead of starting our own. Without such
# re-use, topological branches would keep reopening new chains.
# Creating more and more snapshot as the repository grow.
if floor is not None:
# We only do this for siblings created after the one in our
# parent's delta chain. Those created before has less
# chances to be valid base since our ancestors had to
# create a new snapshot.
siblings = [r for r in siblings if floor < r]
yield tuple(sorted(siblings))
# then test the base from our parent's delta chain.
yield tuple(sorted(snaps))
floor = min(snaps)
# No suitable base found in the parent chain, search if any full
# snapshots emitted since parent's base would be a suitable base
# for an intermediate snapshot.
#
# It give a chance to reuse a delta chain unrelated to the current
# revisions instead of starting our own. Without such re-use,
# topological branches would keep reopening new full chains.
# Creating more and more snapshot as the repository grow.
full = [
r for r in self.snapshot_cache.snapshots[nullrev] if snapfloor <= r
]
yield tuple(sorted(full))
delta-find: finish reworking the snapshot logic and drop more layer...
r52249 def _iter_snapshots(self):
assert self.revlog.delta_config.sparse_revlog
self.current_stage = _STAGE_SNAPSHOT
delta-find: move `_refinedgroups` on the `_DeltaSearch` object...
r52214 good = None
delta-find: finish reworking the snapshot logic and drop more layer...
r52249 groups = self._iter_snapshots_base()
delta-find: move `_refinedgroups` on the `_DeltaSearch` object...
r52214 for candidates in groups:
good = yield candidates
if good is not None:
break
# if we have a refinable value, try to refine it
delta-find: pass the full deltainfo to the _DeltaSearch class...
r52253 if good is not None and good.snapshotdepth is not None:
delta-find: stop using heuristic to determine if we are creating a snapshot...
r52243 assert self.current_stage == _STAGE_SNAPSHOT
delta-find: move `_refinedgroups` on the `_DeltaSearch` object...
r52214 # refine snapshot down
previous = None
while previous != good:
previous = good
delta-find: pass the full deltainfo to the _DeltaSearch class...
r52253 base = self.revlog.deltaparent(good.base)
delta-find: move `_refinedgroups` on the `_DeltaSearch` object...
r52214 if base == nullrev:
break
good = yield (base,)
# refine snapshot up
if not self.snapshot_cache.snapshots:
delta-find: pass the full deltainfo to the _DeltaSearch class...
r52253 self.snapshot_cache.update(self.revlog, good.base + 1)
delta-find: move `_refinedgroups` on the `_DeltaSearch` object...
r52214 previous = None
while good != previous:
previous = good
children = tuple(
delta-find: pass the full deltainfo to the _DeltaSearch class...
r52253 sorted(c for c in self.snapshot_cache.snapshots[good.base])
delta-find: move `_refinedgroups` on the `_DeltaSearch` object...
r52214 )
good = yield children
yield None
Boris Feld
snapshot: introduce an intermediate `_refinedgroups` generator...
r39532
delta-find: finish reworking the snapshot logic and drop more layer...
r52249 def _iter_groups(self):
good = None
for group in self._iter_parents():
good = yield group
if good is not None:
break
delta-find: move the base of the delta search in its own function...
r52248 else:
delta-find: finish reworking the snapshot logic and drop more layer...
r52249 assert good is None
delta-find: split the _DeltaSearch class in two...
r52250 assert self.revlog.delta_config.sparse_revlog
# If sparse revlog is enabled, we can try to refine the
# available deltas
iter_snap = self._iter_snapshots()
group = iter_snap.send(None)
while group is not None:
good = yield group
group = iter_snap.send(good)
delta-find: finish reworking the snapshot logic and drop more layer...
r52249 yield None
Boris Feld
revlogdeltas: extract _getcandidaterevs in a function...
r39370
Augie Fackler
formatting: blacken the codebase...
r43346
delta-find: use a smarter object for snapshot caching...
r50573 class SnapshotCache:
__slots__ = ('snapshots', '_start_rev', '_end_rev')
def __init__(self):
delta-find: use sets instead of list in the snapshot cache...
r50574 self.snapshots = collections.defaultdict(set)
delta-find: use a smarter object for snapshot caching...
r50573 self._start_rev = None
self._end_rev = None
def update(self, revlog, start_rev=0):
"""find snapshots from start_rev to tip"""
nb_revs = len(revlog)
end_rev = nb_revs - 1
if start_rev > end_rev:
return # range is empty
if self._start_rev is None:
assert self._end_rev is None
self._update(revlog, start_rev, end_rev)
elif not (self._start_rev <= start_rev and end_rev <= self._end_rev):
if start_rev < self._start_rev:
self._update(revlog, start_rev, self._start_rev - 1)
if self._end_rev < end_rev:
self._update(revlog, self._end_rev + 1, end_rev)
if self._start_rev is None:
assert self._end_rev is None
self._end_rev = end_rev
self._start_rev = start_rev
else:
self._start_rev = min(self._start_rev, start_rev)
self._end_rev = max(self._end_rev, end_rev)
assert self._start_rev <= self._end_rev, (
self._start_rev,
self._end_rev,
)
def _update(self, revlog, start_rev, end_rev):
"""internal method that actually do update content"""
assert self._start_rev is None or (
start_rev < self._start_rev or start_rev > self._end_rev
), (self._start_rev, self._end_rev, start_rev, end_rev)
assert self._start_rev is None or (
end_rev < self._start_rev or end_rev > self._end_rev
), (self._start_rev, self._end_rev, start_rev, end_rev)
cache = self.snapshots
safehasattr: drop usage in favor of hasattr...
r51821 if hasattr(revlog.index, 'findsnapshots'):
delta-find: use a smarter object for snapshot caching...
r50573 revlog.index.findsnapshots(cache, start_rev, end_rev)
else:
deltaparent = revlog.deltaparent
issnapshot = revlog.issnapshot
for rev in revlog.revs(start_rev, end_rev):
if issnapshot(rev):
delta-find: use sets instead of list in the snapshot cache...
r50574 cache[deltaparent(rev)].add(rev)
delta-find: use a smarter object for snapshot caching...
r50573
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class deltacomputer:
delta-find: add a small docstring to deltacomputer...
r52212 """object capable of computing delta and finding delta for multiple revision
This object is meant to compute and find multiple delta applied to the same
revlog.
"""
debug: add an option to display statistic about a unbundling operation...
r50506 def __init__(
self,
revlog,
write_debug=None,
debug_search=False,
debug_info=None,
):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 self.revlog = revlog
deltas: add code to display information about the result of `finddeltainfo`...
r50121 self._write_debug = write_debug
delta-fine: use the `_debug_search` attribute directly...
r51542 if write_debug is None:
self._debug_search = False
else:
self._debug_search = debug_search
debug: add an option to display statistic about a unbundling operation...
r50506 self._debug_info = debug_info
delta-find: use a single snapshot cache when applying a group to an object...
r50576 self._snapshot_cache = SnapshotCache()
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
delta-find: move the `gather_debug` logic in a property...
r51541 @property
def _gather_debug(self):
return self._write_debug is not None or self._debug_info is not None
delta-computer: stop explicitly taking file handle...
r51913 def buildtext(self, revinfo):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 """Builds a fulltext version of a revision
revlog: move `revisioninfo` in `revlogutils`...
r48191 revinfo: revisioninfo instance that contains all needed info
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 """
btext = revinfo.btext
if btext[0] is not None:
return btext[0]
revlog = self.revlog
cachedelta = revinfo.cachedelta
baserev = cachedelta[0]
delta = cachedelta[1]
Augie Fackler
formatting: blacken the codebase...
r43346 fulltext = btext[0] = _textfromdelta(
revlog,
baserev,
delta,
revinfo.p1,
revinfo.p2,
revinfo.flags,
revinfo.node,
)
Boris Feld
revlogdeltas: extra fulltext building in its own function...
r39367 return fulltext
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
delta-computer: stop explicitly taking file handle...
r51913 def _builddeltadiff(self, base, revinfo):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 revlog = self.revlog
delta-computer: stop explicitly taking file handle...
r51913 t = self.buildtext(revinfo)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 if revlog.iscensored(base):
# deltas based on a censored revision must replace the
# full content in one patch, so delta works everywhere
header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
delta = header + t
else:
delta-computer: stop explicitly taking file handle...
r51913 ptext = revlog.rawdata(base)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 delta = mdiff.textdiff(ptext, t)
return delta
delta-find: stop using heuristic to determine if we are creating a snapshot...
r52243 def _builddeltainfo(
self, revinfo, base, target_rev=None, as_snapshot=False
):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 # can we use the cached delta?
Valentin Gatien-Baron
delta: move some delta chain related computation earlier in deltainfo...
r42665 revlog = self.revlog
chainbase = revlog.chainbase(base)
revlog: remove legacy usage of `_generaldelta`...
r51939 if revlog.delta_config.general_delta:
Valentin Gatien-Baron
delta: move some delta chain related computation earlier in deltainfo...
r42665 deltabase = base
else:
delta-find: add a simple safeguard to prevent bad non-general-delta...
r51332 if target_rev is not None and base != target_rev - 1:
msg = (
b'general delta cannot use delta for something else '
b'than `prev`: %d<-%d'
)
msg %= (base, target_rev)
raise error.ProgrammingError(msg)
Valentin Gatien-Baron
delta: move some delta chain related computation earlier in deltainfo...
r42665 deltabase = chainbase
snapshotdepth = None
revlog: remove legacy usage of `_sparserevlog`...
r51953 if revlog.delta_config.sparse_revlog and deltabase == nullrev:
Valentin Gatien-Baron
delta: move some delta chain related computation earlier in deltainfo...
r42665 snapshotdepth = 0
delta-find: stop using heuristic to determine if we are creating a snapshot...
r52243 elif revlog.delta_config.sparse_revlog and as_snapshot:
assert revlog.issnapshot(deltabase)
Valentin Gatien-Baron
delta: move some delta chain related computation earlier in deltainfo...
r42665 # A delta chain should always be one full snapshot,
# zero or more semi-snapshots, and zero or more deltas
p1, p2 = revlog.rev(revinfo.p1), revlog.rev(revinfo.p2)
if deltabase not in (p1, p2) and revlog.issnapshot(deltabase):
snapshotdepth = len(revlog._deltachain(deltabase)[0])
Boris Feld
revlog: reuse cached delta for identical base revision (issue5975)...
r39631 delta = None
if revinfo.cachedelta:
find-delta: minor preparatory change...
r50570 cachebase = revinfo.cachedelta[0]
Augie Fackler
formatting: blacken the codebase...
r43346 # check if the diff still apply
Boris Feld
revlog: reuse cached delta for identical base revision (issue5975)...
r39631 currentbase = cachebase
Augie Fackler
formatting: blacken the codebase...
r43346 while (
currentbase != nullrev
and currentbase != base
and self.revlog.length(currentbase) == 0
):
Boris Feld
revlog: reuse cached delta for identical base revision (issue5975)...
r39631 currentbase = self.revlog.deltaparent(currentbase)
revlog: remove legacy usage of `_lazydeltabase`...
r51960 if self.revlog.delta_config.lazy_delta and currentbase == base:
Boris Feld
revlog: reuse cached delta for identical base revision (issue5975)...
r39631 delta = revinfo.cachedelta[1]
if delta is None:
delta-computer: stop explicitly taking file handle...
r51913 delta = self._builddeltadiff(base, revinfo)
delta-fine: use the `_debug_search` attribute directly...
r51542 if self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 msg = b"DBG-DELTAS-SEARCH: uncompressed-delta-size=%d\n"
msg %= len(delta)
self._write_debug(msg)
Valentin Gatien-Baron
deltas: skip if projected compressed size does not match text size constraint...
r42667 # snapshotdept need to be neither None nor 0 level snapshot
revlog: also migrates `revlog.upperboundcomp` to ConfigClass...
r51976 if revlog.delta_config.upper_bound_comp is not None and snapshotdepth:
lowestrealisticdeltalen = (
len(delta) // revlog.delta_config.upper_bound_comp
)
Valentin Gatien-Baron
deltas: skip if projected compressed size does not match text size constraint...
r42667 snapshotlimit = revinfo.textlen >> snapshotdepth
delta-fine: use the `_debug_search` attribute directly...
r51542 if self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 msg = b"DBG-DELTAS-SEARCH: projected-lower-size=%d\n"
msg %= lowestrealisticdeltalen
self._write_debug(msg)
Valentin Gatien-Baron
deltas: skip if projected compressed size does not match text size constraint...
r42667 if snapshotlimit < lowestrealisticdeltalen:
delta-fine: use the `_debug_search` attribute directly...
r51542 if self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 msg = b"DBG-DELTAS-SEARCH: DISCARDED (snapshot limit)\n"
self._write_debug(msg)
Valentin Gatien-Baron
deltas: skip if projected compressed size does not match text size constraint...
r42667 return None
Valentin Gatien-Baron
deltas: skip if projected compressed size is bigger than previous snapshot...
r42668 if revlog.length(base) < lowestrealisticdeltalen:
delta-fine: use the `_debug_search` attribute directly...
r51542 if self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 msg = b"DBG-DELTAS-SEARCH: DISCARDED (prev size)\n"
self._write_debug(msg)
Valentin Gatien-Baron
deltas: skip if projected compressed size is bigger than previous snapshot...
r42668 return None
revlog: move the compression/decompression logic on the inner object...
r51984 header, data = revlog._inner.compress(delta)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 deltalen = len(header) + len(data)
offset = revlog.end(len(revlog) - 1)
dist = deltalen + offset - revlog.start(chainbase)
chainlen, compresseddeltalen = revlog._chaininfo(base)
chainlen += 1
compresseddeltalen += deltalen
Augie Fackler
formatting: blacken the codebase...
r43346 return _deltainfo(
dist,
deltalen,
(header, data),
deltabase,
chainbase,
chainlen,
compresseddeltalen,
snapshotdepth,
)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
delta-computer: stop explicitly taking file handle...
r51913 def _fullsnapshotinfo(self, revinfo, curr):
rawtext = self.buildtext(revinfo)
revlog: move the compression/decompression logic on the inner object...
r51984 data = self.revlog._inner.compress(rawtext)
Boris Feld
revlogdeltas: always return a delta info object in finddeltainfo...
r39369 compresseddeltalen = deltalen = dist = len(data[1]) + len(data[0])
deltabase = chainbase = curr
snapshotdepth = 0
chainlen = 1
Augie Fackler
formatting: blacken the codebase...
r43346 return _deltainfo(
dist,
deltalen,
data,
deltabase,
chainbase,
chainlen,
compresseddeltalen,
snapshotdepth,
)
Boris Feld
revlogdeltas: always return a delta info object in finddeltainfo...
r39369
delta-computer: stop explicitly taking file handle...
r51913 def finddeltainfo(self, revinfo, excluded_bases=None, target_rev=None):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 """Find an acceptable delta against a candidate revision
revinfo: information about the revision (instance of _revisioninfo)
Returns the first acceptable candidate revision, as ordered by
Boris Feld
revlogdeltas: extract _getcandidaterevs in a function...
r39370 _candidategroups
Boris Feld
revlogdeltas: always return a delta info object in finddeltainfo...
r39369
If no suitable deltabase is found, we return delta info for a full
snapshot.
revlog: add a ways to blacklist some revision when searching for a delta...
r48193
`excluded_bases` is an optional set of revision that cannot be used as
a delta base. Use this to recompute delta suitable in censor or strip
context.
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 """
deltas: at a `target_rev` parameter to finddeltainfo...
r48249 if target_rev is None:
censor: implement censoring for revlogv2...
r48250 target_rev = len(self.revlog)
deltas: at a `target_rev` parameter to finddeltainfo...
r48249
delta-find: initialize the debug information much sooner (when possible)...
r51546 gather_debug = self._gather_debug
cachedelta = revinfo.cachedelta
revlog = self.revlog
delta-find: fix pulled-delta-reuse-policy=forced behavior...
r51550 p1r = p2r = None
delta-find: initialize the debug information much sooner (when possible)...
r51546
delta-find: fix pulled-delta-reuse-policy=forced behavior...
r51550 if excluded_bases is None:
excluded_bases = set()
delta-find: initialize the debug information much sooner (when possible)...
r51546
if gather_debug:
start = util.timer()
dbg = self._one_dbg_data()
dbg['revision'] = target_rev
p1r = revlog.rev(revinfo.p1)
p2r = revlog.rev(revinfo.p2)
if p1r != nullrev:
p1_chain_len = revlog._chaininfo(p1r)[0]
else:
p1_chain_len = -1
if p2r != nullrev:
p2_chain_len = revlog._chaininfo(p2r)[0]
else:
p2_chain_len = -1
dbg['p1-chain-len'] = p1_chain_len
dbg['p2-chain-len'] = p2_chain_len
delta-find: gather the condition to blindly use a full snapshot together...
r51547 # 1) if the revision is empty, no amount of delta can beat it
#
# 2) no delta for flag processor revision (see "candelta" for why)
# not calling candelta since only one revision needs test, also to
# avoid overhead fetching flags again.
if not revinfo.textlen or revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
delta-computer: stop explicitly taking file handle...
r51913 deltainfo = self._fullsnapshotinfo(revinfo, target_rev)
deltafind: issue debug information when we fast-path rivial case too...
r51548 if gather_debug:
end = util.timer()
dbg['duration'] = end - start
dbg[
'delta-base'
] = deltainfo.base # pytype: disable=attribute-error
dbg['search_round_count'] = 0
delta-find: fix pulled-delta-reuse-policy=forced behavior...
r51550 dbg['using-cached-base'] = False
deltafind: issue debug information when we fast-path rivial case too...
r51548 dbg['delta_try_count'] = 0
dbg['type'] = b"full"
dbg['snapshot-depth'] = 0
self._dbg_process_data(dbg)
return deltainfo
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
delta-find: fix pulled-delta-reuse-policy=forced behavior...
r51550 deltainfo = None
# If this source delta are to be forcibly reuse, let us comply early.
if (
revlog: remove legacy usage of `_generaldelta`...
r51939 revlog.delta_config.general_delta
delta-find: fix pulled-delta-reuse-policy=forced behavior...
r51550 and revinfo.cachedelta is not None
and revinfo.cachedelta[2] == DELTA_BASE_REUSE_FORCE
):
base = revinfo.cachedelta[0]
if base == nullrev:
dbg_type = b"full"
delta-computer: stop explicitly taking file handle...
r51913 deltainfo = self._fullsnapshotinfo(revinfo, target_rev)
delta-find: fix pulled-delta-reuse-policy=forced behavior...
r51550 if gather_debug:
snapshotdepth = 0
elif base not in excluded_bases:
delta = revinfo.cachedelta[1]
header, data = revlog.compress(delta)
deltalen = len(header) + len(data)
if gather_debug:
offset = revlog.end(len(revlog) - 1)
chainbase = revlog.chainbase(base)
distance = deltalen + offset - revlog.start(chainbase)
chainlen, compresseddeltalen = revlog._chaininfo(base)
chainlen += 1
compresseddeltalen += deltalen
if base == p1r or base == p2r:
dbg_type = b"delta"
snapshotdepth = None
elif not revlog.issnapshot(base):
snapshotdepth = None
else:
dbg_type = b"snapshot"
snapshotdepth = revlog.snapshotdepth(base) + 1
else:
distance = None
chainbase = None
chainlen = None
compresseddeltalen = None
snapshotdepth = None
deltainfo = _deltainfo(
distance=distance,
deltalen=deltalen,
data=(header, data),
base=base,
chainbase=chainbase,
chainlen=chainlen,
compresseddeltalen=compresseddeltalen,
snapshotdepth=snapshotdepth,
)
if deltainfo is not None:
if gather_debug:
end = util.timer()
dbg['duration'] = end - start
dbg[
'delta-base'
] = deltainfo.base # pytype: disable=attribute-error
dbg['search_round_count'] = 0
dbg['using-cached-base'] = True
dbg['delta_try_count'] = 0
dbg['type'] = b"full"
if snapshotdepth is None:
delta-find: use "-1" as depth snapshot-dept for non snapshot in debug...
r52226 dbg['snapshot-depth'] = -1
delta-find: fix pulled-delta-reuse-policy=forced behavior...
r51550 else:
dbg['snapshot-depth'] = snapshotdepth
self._dbg_process_data(dbg)
return deltainfo
revlog: add a ways to blacklist some revision when searching for a delta...
r48193
deltas: add code to display information about the result of `finddeltainfo`...
r50121 # count the number of different delta we tried (for debug purpose)
dbg_try_count = 0
# count the number of "search round" we did. (for debug purpose)
dbg_try_rounds = 0
dbg_type = b'unknown'
delta-find: initialize the debug information much sooner (when possible)...
r51546 if p1r is None:
p1r = revlog.rev(revinfo.p1)
p2r = revlog.rev(revinfo.p2)
deltas: add code to display information about the result of `finddeltainfo`...
r50121
delta-fine: use the `_debug_search` attribute directly...
r51542 if self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 msg = b"DBG-DELTAS-SEARCH: SEARCH rev=%d\n"
msg %= target_rev
self._write_debug(msg)
deltas: add code to display information about the result of `finddeltainfo`...
r50121
delta-find: introduce and use specialized _DeltaSearch class...
r52240 # should we try to build a delta?
if not (len(self.revlog) and self.revlog._storedeltachains):
search_cls = _NoDeltaSearch
delta-find: split the _DeltaSearch class in two...
r52250 elif self.revlog.delta_config.sparse_revlog:
search_cls = _SparseDeltaSearch
elif self.revlog.delta_config.general_delta:
search_cls = _GeneralDeltaSearch
else:
delta-find: introduce and use specialized _DeltaSearch class...
r52240 # before general delta, there is only one possible delta base
search_cls = _PrevDeltaSearch
search = search_cls(
delta-find: expand a function definition and call before extendin it...
r50507 self.revlog,
delta-find: feed revinfo to _DeltaSearch...
r52223 revinfo,
delta-find: expand a function definition and call before extendin it...
r50507 p1r,
p2r,
cachedelta,
delta-find: move pre-filtering with other pre-filtering logic...
r50508 excluded_bases,
target_rev,
delta-find: use a single snapshot cache when applying a group to an object...
r50576 snapshot_cache=self._snapshot_cache,
Augie Fackler
formatting: blacken the codebase...
r43346 )
delta-find: introduce a _DeltaSearch object...
r52213
delta-find: move away from the generator API for _DeltaSearch...
r52227 while not search.done:
current_group = search.current_group
# current_group can be `None`, but not is search.done is False
# We add this assert to help pytype
assert current_group is not None
candidaterevs = current_group
deltas: add code to display information about the result of `finddeltainfo`...
r50121 dbg_try_rounds += 1
delta-fine: use the `_debug_search` attribute directly...
r51542 if self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 prev = None
if deltainfo is not None:
prev = deltainfo.base
delta-find: add debug information about reuse of cached data...
r50504 if (
cachedelta is not None
and len(candidaterevs) == 1
and cachedelta[0] in candidaterevs
):
round_type = b"cached-delta"
delta-find: fix `parents` round detection...
r51545 elif p1r in candidaterevs or p2r in candidaterevs:
deltas: add a debug-delta-find command to analyse delta search...
r50123 round_type = b"parents"
elif prev is not None and all(c < prev for c in candidaterevs):
round_type = b"refine-down"
elif prev is not None and all(c > prev for c in candidaterevs):
round_type = b"refine-up"
else:
round_type = b"search-down"
msg = b"DBG-DELTAS-SEARCH: ROUND #%d - %d candidates - %s\n"
msg %= (dbg_try_rounds, len(candidaterevs), round_type)
self._write_debug(msg)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 nominateddeltas = []
Boris Feld
snapshot: add refining logic at the findeltainfo level...
r39534 if deltainfo is not None:
delta-fine: use the `_debug_search` attribute directly...
r51542 if self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 msg = (
b"DBG-DELTAS-SEARCH: CONTENDER: rev=%d - length=%d\n"
)
msg %= (deltainfo.base, deltainfo.deltalen)
self._write_debug(msg)
Boris Feld
snapshot: add refining logic at the findeltainfo level...
r39534 # if we already found a good delta,
# challenge it against refined candidates
nominateddeltas.append(deltainfo)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 for candidaterev in candidaterevs:
delta-fine: use the `_debug_search` attribute directly...
r51542 if self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 msg = b"DBG-DELTAS-SEARCH: CANDIDATE: rev=%d\n"
msg %= candidaterev
self._write_debug(msg)
candidate_type = None
delta-find: fix `parents` round detection...
r51545 if candidaterev == p1r:
deltas: add a debug-delta-find command to analyse delta search...
r50123 candidate_type = b"p1"
delta-find: fix `parents` round detection...
r51545 elif candidaterev == p2r:
deltas: add a debug-delta-find command to analyse delta search...
r50123 candidate_type = b"p2"
elif self.revlog.issnapshot(candidaterev):
candidate_type = b"snapshot-%d"
candidate_type %= self.revlog.snapshotdepth(
candidaterev
)
if candidate_type is not None:
msg = b"DBG-DELTAS-SEARCH: type=%s\n"
msg %= candidate_type
self._write_debug(msg)
msg = b"DBG-DELTAS-SEARCH: size=%d\n"
msg %= self.revlog.length(candidaterev)
self._write_debug(msg)
msg = b"DBG-DELTAS-SEARCH: base=%d\n"
msg %= self.revlog.deltaparent(candidaterev)
self._write_debug(msg)
delta-find: move pre-filtering with other pre-filtering logic...
r50508
deltas: add code to display information about the result of `finddeltainfo`...
r50121 dbg_try_count += 1
deltas: add a debug-delta-find command to analyse delta search...
r50123
delta-fine: use the `_debug_search` attribute directly...
r51542 if self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 delta_start = util.timer()
delta-find: add a simple safeguard to prevent bad non-general-delta...
r51332 candidatedelta = self._builddeltainfo(
revinfo,
candidaterev,
target_rev=target_rev,
delta-find: stop using heuristic to determine if we are creating a snapshot...
r52243 as_snapshot=search.current_stage == _STAGE_SNAPSHOT,
delta-find: add a simple safeguard to prevent bad non-general-delta...
r51332 )
delta-fine: use the `_debug_search` attribute directly...
r51542 if self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 delta_end = util.timer()
msg = b"DBG-DELTAS-SEARCH: delta-search-time=%f\n"
msg %= delta_end - delta_start
self._write_debug(msg)
Valentin Gatien-Baron
deltas: accept and skip None return for delta info...
r42666 if candidatedelta is not None:
delta-find: move is_good_delta_info on the _DeltaSearch class...
r52224 if search.is_good_delta_info(candidatedelta):
delta-fine: use the `_debug_search` attribute directly...
r51542 if self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 msg = b"DBG-DELTAS-SEARCH: DELTA: length=%d (GOOD)\n"
msg %= candidatedelta.deltalen
self._write_debug(msg)
Valentin Gatien-Baron
deltas: accept and skip None return for delta info...
r42666 nominateddeltas.append(candidatedelta)
delta-fine: use the `_debug_search` attribute directly...
r51542 elif self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 msg = b"DBG-DELTAS-SEARCH: DELTA: length=%d (BAD)\n"
msg %= candidatedelta.deltalen
self._write_debug(msg)
delta-fine: use the `_debug_search` attribute directly...
r51542 elif self._debug_search:
deltas: add a debug-delta-find command to analyse delta search...
r50123 msg = b"DBG-DELTAS-SEARCH: NO-DELTA\n"
self._write_debug(msg)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 if nominateddeltas:
deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
delta-find: move away from the generator API for _DeltaSearch...
r52227 search.next_group(deltainfo)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
Boris Feld
revlogdeltas: always return a delta info object in finddeltainfo...
r39369 if deltainfo is None:
deltas: add code to display information about the result of `finddeltainfo`...
r50121 dbg_type = b"full"
delta-computer: stop explicitly taking file handle...
r51913 deltainfo = self._fullsnapshotinfo(revinfo, target_rev)
deltas: add a debug-delta-find command to analyse delta search...
r50123 elif deltainfo.snapshotdepth: # pytype: disable=attribute-error
deltas: add code to display information about the result of `finddeltainfo`...
r50121 dbg_type = b"snapshot"
else:
dbg_type = b"delta"
debug: add an option to display statistic about a unbundling operation...
r50506 if gather_debug:
deltas: add code to display information about the result of `finddeltainfo`...
r50121 end = util.timer()
delta-find: properly report full snapshot used from cache as such...
r50676 if dbg_type == b'full':
used_cached = (
cachedelta is not None
and dbg_try_rounds == 0
and dbg_try_count == 0
and cachedelta[0] == nullrev
)
else:
used_cached = (
cachedelta is not None
and dbg_try_rounds == 1
and dbg_try_count == 1
and deltainfo.base == cachedelta[0]
)
delta-find: intrduce a `_one_dbg_data` method...
r51544 dbg['duration'] = end - start
dbg[
'delta-base'
] = deltainfo.base # pytype: disable=attribute-error
dbg['search_round_count'] = dbg_try_rounds
dbg['using-cached-base'] = used_cached
dbg['delta_try_count'] = dbg_try_count
dbg['type'] = dbg_type
deltas: add a debug-delta-find command to analyse delta search...
r50123 if (
deltainfo.snapshotdepth # pytype: disable=attribute-error
is not None
):
dbg[
'snapshot-depth'
] = deltainfo.snapshotdepth # pytype: disable=attribute-error
deltas: add code to display information about the result of `finddeltainfo`...
r50121 else:
delta-find: use "-1" as depth snapshot-dept for non snapshot in debug...
r52226 dbg['snapshot-depth'] = -1
delta-find: move final debug processing in a `_dbg_process_data` method...
r51543 self._dbg_process_data(dbg)
return deltainfo
deltas: add code to display information about the result of `finddeltainfo`...
r50121
delta-find: intrduce a `_one_dbg_data` method...
r51544 def _one_dbg_data(self):
delta-find: move filing of some debug data in `_one_dbg_data`...
r52221 dbg = {
delta-find: intrduce a `_one_dbg_data` method...
r51544 'duration': None,
'revision': None,
'delta-base': None,
'search_round_count': None,
'using-cached-base': None,
'delta_try_count': None,
'type': None,
'p1-chain-len': None,
'p2-chain-len': None,
'snapshot-depth': None,
'target-revlog': None,
}
delta-find: move filing of some debug data in `_one_dbg_data`...
r52221 target_revlog = b"UNKNOWN"
target_type = self.revlog.target[0]
target_key = self.revlog.target[1]
if target_type == KIND_CHANGELOG:
target_revlog = b'CHANGELOG:'
elif target_type == KIND_MANIFESTLOG:
target_revlog = b'MANIFESTLOG:'
if target_key:
target_revlog += b'%s:' % target_key
elif target_type == KIND_FILELOG:
target_revlog = b'FILELOG:'
if target_key:
target_revlog += b'%s:' % target_key
dbg['target-revlog'] = target_revlog
return dbg
delta-find: intrduce a `_one_dbg_data` method...
r51544
delta-find: move final debug processing in a `_dbg_process_data` method...
r51543 def _dbg_process_data(self, dbg):
if self._debug_info is not None:
self._debug_info.append(dbg)
debug: add an option to display statistic about a unbundling operation...
r50506
delta-find: move final debug processing in a `_dbg_process_data` method...
r51543 if self._write_debug is not None:
msg = (
b"DBG-DELTAS:"
b" %-12s"
b" rev=%d:"
b" delta-base=%d"
b" is-cached=%d"
b" - search-rounds=%d"
b" try-count=%d"
b" - delta-type=%-6s"
b" snap-depth=%d"
b" - p1-chain-length=%d"
b" p2-chain-length=%d"
b" - duration=%f"
b"\n"
)
msg %= (
dbg["target-revlog"],
dbg["revision"],
dbg["delta-base"],
dbg["using-cached-base"],
dbg["search_round_count"],
dbg["delta_try_count"],
dbg["type"],
dbg["snapshot-depth"],
dbg["p1-chain-len"],
dbg["p2-chain-len"],
dbg["duration"],
)
self._write_debug(msg)
revlog: factor the logic to determine the delta compression out...
r48245
def delta_compression(default_compression_header, deltainfo):
"""return (COMPRESSION_MODE, deltainfo)
used by revlog v2+ format to dispatch between PLAIN and DEFAULT
compression.
"""
h, d = deltainfo.data
compression_mode = COMP_MODE_INLINE
if not h and not d:
# not data to store at all... declare them uncompressed
compression_mode = COMP_MODE_PLAIN
elif not h:
t = d[0:1]
if t == b'\0':
compression_mode = COMP_MODE_PLAIN
elif t == default_compression_header:
compression_mode = COMP_MODE_DEFAULT
elif h == b'u':
# we have a more efficient way to declare uncompressed
h = b''
compression_mode = COMP_MODE_PLAIN
deltainfo = drop_u_compression(deltainfo)
return compression_mode, deltainfo