##// END OF EJS Templates
share: make it possible to control the working copy format variant...
share: make it possible to control the working copy format variant A share will use the same format as its source for the store, but there are no reason to not lets it control the working copy variant at creation time. So we make it so. Differential Revision: https://phab.mercurial-scm.org/D11892

File last commit:

r48250:f8330a3f default
r49297:bf2738e0 default
Show More
deltas.py
1147 lines | 38.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"""
from __future__ import absolute_import
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 _
Gregory Szorc
py3: manually import getattr where it is needed...
r43359 from ..pycompat import getattr
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
from .constants import (
revlog: factor the logic to determine the delta compression out...
r48245 COMP_MODE_DEFAULT,
COMP_MODE_INLINE,
COMP_MODE_PLAIN,
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
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 class _testrevlog(object):
"""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"""
self._data = data
self._srdensitythreshold = density
self._srmingapsize = mingap
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
ratio) is above `revlog._srdensitythreshold`. No gap smaller than
`revlog._srmingapsize` is skipped.
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._srmingapsize`.
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:
targetsize = max(targetsize, revlog._srmingapsize)
# 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(
revs, revlog._srdensitythreshold, revlog._srmingapsize
):
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
Boris Feld
revlogdeltas: extra fulltext building in its own function...
r39367 def _textfromdelta(fh, revlog, baserev, delta, p1, p2, flags, expectednode):
"""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
basetext = revlog.revision(baserev, _df=fh, raw=False)
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)
class _deltainfo(object):
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
revlog: split functionality related to deltas computation in a new module...
r39366 def isgooddeltainfo(revlog, deltainfo, revinfo):
"""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 deltainfo is None:
return False
# - 'deltainfo.distance' is the distance from the base revision --
# bounding it limits the amount of I/O we need to do.
# - 'deltainfo.compresseddeltalen' is the sum of the total size of
# deltas we need to apply -- bounding it limits the amount of CPU
# we consume.
textlen = revinfo.textlen
defaultmax = textlen * 4
maxdist = revlog._maxdeltachainspan
if not maxdist:
Augie Fackler
formatting: blacken the codebase...
r43346 maxdist = deltainfo.distance # ensure the conditional pass
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 maxdist = max(maxdist, defaultmax)
# Bad delta from read span:
#
# If the span of data read is larger than the maximum allowed.
Boris Feld
sparse-revlog: skip the span check in the sparse-revlog case...
r40639 #
# 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 revlog._sparserevlog and maxdist < deltainfo.distance:
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 return False
# Bad delta from new delta size:
#
# If the delta size is larger than the target text, storing the
# delta will be inefficient.
if textlen < deltainfo.deltalen:
return False
# Bad delta from cumulated payload size:
#
# If the sum of delta get larger than K * target text length.
if textlen * LIMIT_DELTA2TEXT < deltainfo.compresseddeltalen:
return False
# Bad delta from chain length:
#
# If the number of delta in the chain gets too high.
Augie Fackler
formatting: blacken the codebase...
r43346 if revlog._maxchainlen and revlog._maxchainlen < deltainfo.chainlen:
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 return False
# 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.
Augie Fackler
formatting: blacken the codebase...
r43346 if (
deltainfo.snapshotdepth is not None
and (textlen >> deltainfo.snapshotdepth) < deltainfo.deltalen
):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 return False
# bad delta if new intermediate snapshot is larger than the previous
# snapshot
Augie Fackler
formatting: blacken the codebase...
r43346 if (
deltainfo.snapshotdepth
and revlog.length(deltainfo.base) < deltainfo.deltalen
):
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 return False
return True
Augie Fackler
formatting: blacken the codebase...
r43346
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
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
revlogdeltas: move finddeltainfo filtering inside _candidategroups...
r39373 def _candidategroups(revlog, textlen, p1, p2, cachedelta):
Boris Feld
revlogdeltas: split candidate groups selection from the filtering logic...
r39372 """Provides group of revision to be tested as delta base
This top level function focus on emitting groups with unique and worthwhile
content. See _raw_candidate_groups for details about the group order.
Boris Feld
revlogdeltas: extract _getcandidaterevs in a function...
r39370 """
Boris Feld
revlogdeltas: split candidate groups selection from the filtering logic...
r39372 # should we try to build a delta?
if not (len(revlog) and revlog._storedeltachains):
Boris Feld
snapshot: use None as a stop value when looking for a good delta...
r39533 yield None
Boris Feld
revlogdeltas: split candidate groups selection from the filtering logic...
r39372 return
Boris Feld
revlogdeltas: move finddeltainfo filtering inside _candidategroups...
r39373 deltalength = revlog.length
deltaparent = revlog.deltaparent
Boris Feld
delta: exclude base candidate much smaller than the target...
r41014 sparse = revlog._sparserevlog
Boris Feld
snapshot: add refining logic at the findeltainfo level...
r39534 good = None
Boris Feld
revlogdeltas: move finddeltainfo filtering inside _candidategroups...
r39373
deltas_limit = textlen * LIMIT_DELTA2TEXT
Martin von Zweigbergk
cleanup: use set literals where possible...
r42224 tested = {nullrev}
Boris Feld
snapshot: also use None as a stop value for `_refinegroup`...
r39535 candidates = _refinedgroups(revlog, p1, p2, cachedelta)
while True:
Boris Feld
snapshot: turn _refinedgroups into a coroutine...
r39536 temptative = candidates.send(good)
Boris Feld
snapshot: also use None as a stop value for `_refinegroup`...
r39535 if temptative is None:
break
Boris Feld
revlogdeltas: move finddeltainfo filtering inside _candidategroups...
r39373 group = []
for rev in temptative:
# skip over empty delta (no need to include them in a chain)
Augie Fackler
formatting: blacken the codebase...
r43346 while revlog._generaldelta and not (
rev == nullrev or rev in tested or deltalength(rev)
):
Boris Feld
snapshot: fix line order when skipping over empty deltas...
r39630 tested.add(rev)
Boris Feld
revlogdeltas: move finddeltainfo filtering inside _candidategroups...
r39373 rev = deltaparent(rev)
Boris Feld
delta: filter nullrev out first...
r40993 # no need to try a delta against nullrev, this will be done as a
# last resort.
if rev == nullrev:
continue
Boris Feld
revlogdeltas: move finddeltainfo filtering inside _candidategroups...
r39373 # filter out revision we tested already
if rev in tested:
continue
tested.add(rev)
# filter out delta base that will never produce good delta
if deltas_limit < revlog.length(rev):
continue
Boris Feld
delta: exclude base candidate much smaller than the target...
r41014 if sparse and revlog.rawsize(rev) < (textlen // LIMIT_BASE2TEXT):
continue
Boris Feld
revlogdeltas: move finddeltainfo filtering inside _candidategroups...
r39373 # no delta for rawtext-changing revs (see "candelta" for why)
if revlog.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
continue
Boris Feld
delta: ignore base whose chains already don't match expectations...
r41015 # 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 = revlog._chaininfo(rev)
# if chain will be too long, skip base
if revlog._maxchainlen and chainlen >= revlog._maxchainlen:
continue
# if chain already have too much data, skip base
if deltas_limit < chainsize:
continue
Valentin Gatien-Baron , Pierre-Yves David pierre-yves.david@octobus.net
deltas: skip if projected delta size does not match text size constraint...
r42663 if sparse and revlog.upperboundcomp is not None:
maxcomp = revlog.upperboundcomp
basenotsnap = (p1, p2, nullrev)
if rev not in basenotsnap and revlog.issnapshot(rev):
snapshotdepth = 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 = revlog.rawsize(rev)
rawsizedistance = max(textlen - revsize, 0)
# use an estimate of the compression upper bound.
lowestrealisticdeltalen = rawsizedistance // maxcomp
# check the absolute constraint on the delta size
snapshotlimit = textlen >> snapshotdepth
if snapshotlimit < lowestrealisticdeltalen:
# delta lower bound is larger than accepted upper bound
continue
Valentin Gatien-Baron
deltas: skip if projected delta size is bigger than previous snapshot...
r42664 # check the relative constraint on the delta size
revlength = revlog.length(rev)
if revlength < lowestrealisticdeltalen:
# delta probable lower bound is larger than target base
continue
Boris Feld
revlogdeltas: move finddeltainfo filtering inside _candidategroups...
r39373 group.append(rev)
Boris Feld
revlogdeltas: split candidate groups selection from the filtering logic...
r39372 if group:
Boris Feld
snapshot: search for unrelated but reusable full-snapshot...
r39529 # XXX: in the sparse revlog case, group can become large,
# impacting performances. Some bounding or slicing mecanism
# would help to reduce this impact.
Boris Feld
snapshot: add refining logic at the findeltainfo level...
r39534 good = yield tuple(group)
Boris Feld
snapshot: use None as a stop value when looking for a good delta...
r39533 yield None
Boris Feld
revlogdeltas: split candidate groups selection from the filtering logic...
r39372
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
snapshot: search for unrelated but reusable full-snapshot...
r39529 def _findsnapshots(revlog, cache, start_rev):
"""find snapshot from start_rev to tip"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if util.safehasattr(revlog.index, b'findsnapshots'):
Boris Feld
delta: have a native implementation of _findsnapshot...
r41141 revlog.index.findsnapshots(cache, start_rev)
else:
deltaparent = revlog.deltaparent
issnapshot = revlog.issnapshot
for rev in revlog.revs(start_rev):
if issnapshot(rev):
cache[deltaparent(rev)].append(rev)
Boris Feld
snapshot: search for unrelated but reusable full-snapshot...
r39529
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
snapshot: introduce an intermediate `_refinedgroups` generator...
r39532 def _refinedgroups(revlog, p1, p2, cachedelta):
good = None
Boris Feld
snapshot: make sure we'll never refine delta base from a reused source...
r39537 # 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.
if cachedelta and revlog._generaldelta and revlog._lazydeltabase:
# Assume what we received from the server is a good choice
# build delta will reuse the cache
good = yield (cachedelta[0],)
if good is not None:
yield None
return
Boris Feld
delta: reuse _findsnapshot call from previous stage...
r41142 snapshots = collections.defaultdict(list)
for candidates in _rawgroups(revlog, p1, p2, cachedelta, snapshots):
Boris Feld
snapshot: introduce an intermediate `_refinedgroups` generator...
r39532 good = yield candidates
if good is not None:
break
Boris Feld
snapshot: try to refine new snapshot base down the chain...
r39538
Boris Feld
sparse-revlog: only refine delta candidates in the sparse case (issue6006)...
r40479 # If sparse revlog is enabled, we can try to refine the available deltas
if not revlog._sparserevlog:
yield None
return
Boris Feld
snapshot: try to refine new snapshot base down the chain...
r39538 # if we have a refinable value, try to refine it
if good is not None and good not in (p1, p2) and revlog.issnapshot(good):
# refine snapshot down
previous = None
while previous != good:
previous = good
base = revlog.deltaparent(good)
if base == nullrev:
break
good = yield (base,)
Boris Feld
snapshot: refine candidate snapshot base upward...
r39539 # refine snapshot up
Boris Feld
delta: reuse _findsnapshot call from previous stage...
r41142 if not snapshots:
_findsnapshots(revlog, snapshots, good + 1)
Boris Feld
snapshot: refine candidate snapshot base upward...
r39539 previous = None
while good != previous:
previous = good
children = tuple(sorted(c for c in snapshots[good]))
good = yield children
Boris Feld
snapshot: also use None as a stop value for `_refinegroup`...
r39535 # we have found nothing
yield None
Boris Feld
snapshot: introduce an intermediate `_refinedgroups` generator...
r39532
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
delta: reuse _findsnapshot call from previous stage...
r41142 def _rawgroups(revlog, p1, p2, cachedelta, snapshots=None):
Boris Feld
revlogdeltas: split candidate groups selection from the filtering logic...
r39372 """Provides group of revision to be tested as delta base
This lower level function focus on emitting delta theorically interresting
without looking it any practical details.
The group order aims at providing fast or small candidates first.
Boris Feld
revlogdeltas: extract _getcandidaterevs in a function...
r39370 """
gdelta = revlog._generaldelta
Boris Feld
revlog: make sure we never use sparserevlog without general delta (issue6056)...
r41525 # gate sparse behind general-delta because of issue6056
sparse = gdelta and revlog._sparserevlog
Boris Feld
revlogdeltas: extract _getcandidaterevs in a function...
r39370 curr = len(revlog)
prev = curr - 1
Boris Feld
snapshot: try intermediate snapshot against parents' base...
r39528 deltachain = lambda rev: revlog._deltachain(rev)[0]
Boris Feld
revlogdeltas: extract _getcandidaterevs in a function...
r39370
Boris Feld
revlogdeltas: split candidate groups selection from the filtering logic...
r39372 if gdelta:
# exclude already lazy tested base if any
parents = [p for p in (p1, p2) if p != nullrev]
Boris Feld
revlogdeltas: extract _getcandidaterevs in a function...
r39370
Boris Feld
revlogdeltas: split candidate groups selection from the filtering logic...
r39372 if not revlog._deltabothparents 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
Boris Feld
revlogdeltas: extract _getcandidaterevs in a function...
r39370
Boris Feld
snapshot: try intermediate snapshot against parents' base...
r39528 if sparse and parents:
Boris Feld
delta: reuse _findsnapshot call from previous stage...
r41142 if snapshots is None:
# map: base-rev: snapshot-rev
snapshots = collections.defaultdict(list)
Boris Feld
snapshot: try intermediate snapshot against parents' base...
r39528 # See if we can use an existing snapshot in the parent chains to use as
# a base for a new intermediate-snapshot
Boris Feld
snapshot: consider all snapshots in the parents' chains...
r39530 #
# search for snapshot in parents delta chain
# map: snapshot-level: snapshot-rev
parents_snaps = collections.defaultdict(set)
Boris Feld
snapshot: extract parent chain computation...
r39540 candidate_chains = [deltachain(p) for p in parents]
for chain in candidate_chains:
for idx, s in enumerate(chain):
Boris Feld
snapshot: consider all snapshots in the parents' chains...
r39530 if not revlog.issnapshot(s):
break
parents_snaps[idx].add(s)
Boris Feld
snapshot: consider unrelated snapshots at a similar level first...
r39531 snapfloor = min(parents_snaps[0]) + 1
_findsnapshots(revlog, snapshots, snapfloor)
Boris Feld
snapshot: also consider the snapshot chain of one unrelated revision...
r39541 # 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 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 idx, s in enumerate(chain):
if s < snapfloor:
continue
if max_depth < idx:
break
if not revlog.issnapshot(s):
break
parents_snaps[idx].add(s)
Boris Feld
snapshot: consider all snapshots in the parents' chains...
r39530 # 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
Boris Feld
snapshot: consider unrelated snapshots at a similar level first...
r39531 floor = None
Boris Feld
snapshot: consider all snapshots in the parents' chains...
r39530 for idx, snaps in sorted(parents_snaps.items(), reverse=True):
Boris Feld
snapshot: consider unrelated snapshots at a similar level first...
r39531 siblings = set()
for s in snaps:
siblings.update(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.
Boris Feld
snapshot: consider all snapshots in the parents' chains...
r39530 yield tuple(sorted(snaps))
Boris Feld
snapshot: consider unrelated snapshots at a similar level first...
r39531 floor = min(snaps)
Boris Feld
snapshot: search for unrelated but reusable full-snapshot...
r39529 # 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.
yield tuple(snapshots[nullrev])
Boris Feld
snapshot: try intermediate snapshot against parents' base...
r39528
Boris Feld
snapshot: also consider the snapshot chain of one unrelated revision...
r39541 if not sparse:
# other approach failed try against prev to hopefully save us a
# fulltext.
yield (prev,)
Boris Feld
revlogdeltas: extract _getcandidaterevs in a function...
r39370
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 class deltacomputer(object):
def __init__(self, revlog):
self.revlog = revlog
def buildtext(self, revinfo, fh):
"""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 fh: file handle to either the .i or the .d revlog file,
depending on whether it is inlined or not
"""
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(
fh,
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
def _builddeltadiff(self, base, revinfo, fh):
revlog = self.revlog
t = self.buildtext(revinfo, fh)
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:
rawdata: update callers in delta utils...
r43048 ptext = revlog.rawdata(base, _df=fh)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 delta = mdiff.textdiff(ptext, t)
return delta
def _builddeltainfo(self, revinfo, base, fh):
# 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)
if revlog._generaldelta:
deltabase = base
else:
deltabase = chainbase
snapshotdepth = None
if revlog._sparserevlog and deltabase == nullrev:
snapshotdepth = 0
elif revlog._sparserevlog and revlog.issnapshot(deltabase):
# 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:
cachebase, cachediff = revinfo.cachedelta
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)
storage: introduce a `revlog.reuse-external-delta` config...
r41985 if self.revlog._lazydelta and currentbase == base:
Boris Feld
revlog: reuse cached delta for identical base revision (issue5975)...
r39631 delta = revinfo.cachedelta[1]
if delta is None:
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 delta = self._builddeltadiff(base, revinfo, fh)
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
if revlog.upperboundcomp is not None and snapshotdepth:
lowestrealisticdeltalen = len(delta) // revlog.upperboundcomp
snapshotlimit = revinfo.textlen >> snapshotdepth
if snapshotlimit < lowestrealisticdeltalen:
return None
Valentin Gatien-Baron
deltas: skip if projected compressed size is bigger than previous snapshot...
r42668 if revlog.length(base) < lowestrealisticdeltalen:
return None
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 header, data = revlog.compress(delta)
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
deltas: at a `target_rev` parameter to finddeltainfo...
r48249 def _fullsnapshotinfo(self, fh, revinfo, curr):
Boris Feld
revlogdeltas: always return a delta info object in finddeltainfo...
r39369 rawtext = self.buildtext(revinfo, fh)
data = self.revlog.compress(rawtext)
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
deltas: at a `target_rev` parameter to finddeltainfo...
r48249 def finddeltainfo(self, revinfo, fh, 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)
fh: file handle to either the .i or the .d revlog file,
depending on whether it is inlined or not
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
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 if not revinfo.textlen:
deltas: at a `target_rev` parameter to finddeltainfo...
r48249 return self._fullsnapshotinfo(fh, revinfo, target_rev)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366
revlog: add a ways to blacklist some revision when searching for a delta...
r48193 if excluded_bases is None:
excluded_bases = set()
Boris Feld
revlogdeltas: move special cases around raw revisions in finddeltainfo...
r39368 # 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 revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
deltas: at a `target_rev` parameter to finddeltainfo...
r48249 return self._fullsnapshotinfo(fh, revinfo, target_rev)
Boris Feld
revlogdeltas: move special cases around raw revisions in finddeltainfo...
r39368
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 cachedelta = revinfo.cachedelta
p1 = revinfo.p1
p2 = revinfo.p2
revlog = self.revlog
deltainfo = None
Boris Feld
revlogdeltas: pass revision number to _candidatesgroups...
r39371 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
Augie Fackler
formatting: blacken the codebase...
r43346 groups = _candidategroups(
self.revlog, revinfo.textlen, p1r, p2r, cachedelta
)
Boris Feld
snapshot: use None as a stop value when looking for a good delta...
r39533 candidaterevs = next(groups)
while candidaterevs is not None:
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:
# 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:
revlog: add a ways to blacklist some revision when searching for a delta...
r48193 if candidaterev in excluded_bases:
continue
deltas: at a `target_rev` parameter to finddeltainfo...
r48249 if candidaterev >= target_rev:
continue
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
Valentin Gatien-Baron
deltas: accept and skip None return for delta info...
r42666 if candidatedelta is not None:
if isgooddeltainfo(self.revlog, candidatedelta, revinfo):
nominateddeltas.append(candidatedelta)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 if nominateddeltas:
deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
Boris Feld
snapshot: add refining logic at the findeltainfo level...
r39534 if deltainfo is not None:
candidaterevs = groups.send(deltainfo.base)
else:
candidaterevs = next(groups)
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: at a `target_rev` parameter to finddeltainfo...
r48249 deltainfo = self._fullsnapshotinfo(fh, revinfo, target_rev)
Boris Feld
revlog: split functionality related to deltas computation in a new module...
r39366 return deltainfo
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