##// END OF EJS Templates
deltas: skip if projected delta size does not match text size constraint...
Valentin Gatien-Baron , Pierre-Yves David pierre-yves.david@octobus.net -
r42663:a0b26fc8 default
parent child Browse files
Show More
@@ -1,1016 +1,1035 b''
1 # revlogdeltas.py - Logic around delta computation for revlog
1 # revlogdeltas.py - Logic around delta computation for revlog
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2018 Octobus <contact@octobus.net>
4 # Copyright 2018 Octobus <contact@octobus.net>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8 """Helper class to compute deltas stored inside revlogs"""
8 """Helper class to compute deltas stored inside revlogs"""
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import collections
12 import collections
13 import struct
13 import struct
14
14
15 # import stuff from node for others to import from revlog
15 # import stuff from node for others to import from revlog
16 from ..node import (
16 from ..node import (
17 nullrev,
17 nullrev,
18 )
18 )
19 from ..i18n import _
19 from ..i18n import _
20
20
21 from .constants import (
21 from .constants import (
22 REVIDX_ISCENSORED,
22 REVIDX_ISCENSORED,
23 REVIDX_RAWTEXT_CHANGING_FLAGS,
23 REVIDX_RAWTEXT_CHANGING_FLAGS,
24 )
24 )
25
25
26 from ..thirdparty import (
26 from ..thirdparty import (
27 attr,
27 attr,
28 )
28 )
29
29
30 from .. import (
30 from .. import (
31 error,
31 error,
32 mdiff,
32 mdiff,
33 util,
33 util,
34 )
34 )
35
35
36 # maximum <delta-chain-data>/<revision-text-length> ratio
36 # maximum <delta-chain-data>/<revision-text-length> ratio
37 LIMIT_DELTA2TEXT = 2
37 LIMIT_DELTA2TEXT = 2
38
38
39 class _testrevlog(object):
39 class _testrevlog(object):
40 """minimalist fake revlog to use in doctests"""
40 """minimalist fake revlog to use in doctests"""
41
41
42 def __init__(self, data, density=0.5, mingap=0, snapshot=()):
42 def __init__(self, data, density=0.5, mingap=0, snapshot=()):
43 """data is an list of revision payload boundaries"""
43 """data is an list of revision payload boundaries"""
44 self._data = data
44 self._data = data
45 self._srdensitythreshold = density
45 self._srdensitythreshold = density
46 self._srmingapsize = mingap
46 self._srmingapsize = mingap
47 self._snapshot = set(snapshot)
47 self._snapshot = set(snapshot)
48 self.index = None
48 self.index = None
49
49
50 def start(self, rev):
50 def start(self, rev):
51 if rev == nullrev:
51 if rev == nullrev:
52 return 0
52 return 0
53 if rev == 0:
53 if rev == 0:
54 return 0
54 return 0
55 return self._data[rev - 1]
55 return self._data[rev - 1]
56
56
57 def end(self, rev):
57 def end(self, rev):
58 if rev == nullrev:
58 if rev == nullrev:
59 return 0
59 return 0
60 return self._data[rev]
60 return self._data[rev]
61
61
62 def length(self, rev):
62 def length(self, rev):
63 return self.end(rev) - self.start(rev)
63 return self.end(rev) - self.start(rev)
64
64
65 def __len__(self):
65 def __len__(self):
66 return len(self._data)
66 return len(self._data)
67
67
68 def issnapshot(self, rev):
68 def issnapshot(self, rev):
69 if rev == nullrev:
69 if rev == nullrev:
70 return True
70 return True
71 return rev in self._snapshot
71 return rev in self._snapshot
72
72
73 def slicechunk(revlog, revs, targetsize=None):
73 def slicechunk(revlog, revs, targetsize=None):
74 """slice revs to reduce the amount of unrelated data to be read from disk.
74 """slice revs to reduce the amount of unrelated data to be read from disk.
75
75
76 ``revs`` is sliced into groups that should be read in one time.
76 ``revs`` is sliced into groups that should be read in one time.
77 Assume that revs are sorted.
77 Assume that revs are sorted.
78
78
79 The initial chunk is sliced until the overall density (payload/chunks-span
79 The initial chunk is sliced until the overall density (payload/chunks-span
80 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
80 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
81 `revlog._srmingapsize` is skipped.
81 `revlog._srmingapsize` is skipped.
82
82
83 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
83 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
84 For consistency with other slicing choice, this limit won't go lower than
84 For consistency with other slicing choice, this limit won't go lower than
85 `revlog._srmingapsize`.
85 `revlog._srmingapsize`.
86
86
87 If individual revisions chunk are larger than this limit, they will still
87 If individual revisions chunk are larger than this limit, they will still
88 be raised individually.
88 be raised individually.
89
89
90 >>> data = [
90 >>> data = [
91 ... 5, #00 (5)
91 ... 5, #00 (5)
92 ... 10, #01 (5)
92 ... 10, #01 (5)
93 ... 12, #02 (2)
93 ... 12, #02 (2)
94 ... 12, #03 (empty)
94 ... 12, #03 (empty)
95 ... 27, #04 (15)
95 ... 27, #04 (15)
96 ... 31, #05 (4)
96 ... 31, #05 (4)
97 ... 31, #06 (empty)
97 ... 31, #06 (empty)
98 ... 42, #07 (11)
98 ... 42, #07 (11)
99 ... 47, #08 (5)
99 ... 47, #08 (5)
100 ... 47, #09 (empty)
100 ... 47, #09 (empty)
101 ... 48, #10 (1)
101 ... 48, #10 (1)
102 ... 51, #11 (3)
102 ... 51, #11 (3)
103 ... 74, #12 (23)
103 ... 74, #12 (23)
104 ... 85, #13 (11)
104 ... 85, #13 (11)
105 ... 86, #14 (1)
105 ... 86, #14 (1)
106 ... 91, #15 (5)
106 ... 91, #15 (5)
107 ... ]
107 ... ]
108 >>> revlog = _testrevlog(data, snapshot=range(16))
108 >>> revlog = _testrevlog(data, snapshot=range(16))
109
109
110 >>> list(slicechunk(revlog, list(range(16))))
110 >>> list(slicechunk(revlog, list(range(16))))
111 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
111 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
112 >>> list(slicechunk(revlog, [0, 15]))
112 >>> list(slicechunk(revlog, [0, 15]))
113 [[0], [15]]
113 [[0], [15]]
114 >>> list(slicechunk(revlog, [0, 11, 15]))
114 >>> list(slicechunk(revlog, [0, 11, 15]))
115 [[0], [11], [15]]
115 [[0], [11], [15]]
116 >>> list(slicechunk(revlog, [0, 11, 13, 15]))
116 >>> list(slicechunk(revlog, [0, 11, 13, 15]))
117 [[0], [11, 13, 15]]
117 [[0], [11, 13, 15]]
118 >>> list(slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
118 >>> list(slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
119 [[1, 2], [5, 8, 10, 11], [14]]
119 [[1, 2], [5, 8, 10, 11], [14]]
120
120
121 Slicing with a maximum chunk size
121 Slicing with a maximum chunk size
122 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
122 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
123 [[0], [11], [13], [15]]
123 [[0], [11], [13], [15]]
124 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
124 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
125 [[0], [11], [13, 15]]
125 [[0], [11], [13, 15]]
126
126
127 Slicing involving nullrev
127 Slicing involving nullrev
128 >>> list(slicechunk(revlog, [-1, 0, 11, 13, 15], targetsize=20))
128 >>> list(slicechunk(revlog, [-1, 0, 11, 13, 15], targetsize=20))
129 [[-1, 0], [11], [13, 15]]
129 [[-1, 0], [11], [13, 15]]
130 >>> list(slicechunk(revlog, [-1, 13, 15], targetsize=5))
130 >>> list(slicechunk(revlog, [-1, 13, 15], targetsize=5))
131 [[-1], [13], [15]]
131 [[-1], [13], [15]]
132 """
132 """
133 if targetsize is not None:
133 if targetsize is not None:
134 targetsize = max(targetsize, revlog._srmingapsize)
134 targetsize = max(targetsize, revlog._srmingapsize)
135 # targetsize should not be specified when evaluating delta candidates:
135 # targetsize should not be specified when evaluating delta candidates:
136 # * targetsize is used to ensure we stay within specification when reading,
136 # * targetsize is used to ensure we stay within specification when reading,
137 densityslicing = getattr(revlog.index, 'slicechunktodensity', None)
137 densityslicing = getattr(revlog.index, 'slicechunktodensity', None)
138 if densityslicing is None:
138 if densityslicing is None:
139 densityslicing = lambda x, y, z: _slicechunktodensity(revlog, x, y, z)
139 densityslicing = lambda x, y, z: _slicechunktodensity(revlog, x, y, z)
140 for chunk in densityslicing(revs,
140 for chunk in densityslicing(revs,
141 revlog._srdensitythreshold,
141 revlog._srdensitythreshold,
142 revlog._srmingapsize):
142 revlog._srmingapsize):
143 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
143 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
144 yield subchunk
144 yield subchunk
145
145
146 def _slicechunktosize(revlog, revs, targetsize=None):
146 def _slicechunktosize(revlog, revs, targetsize=None):
147 """slice revs to match the target size
147 """slice revs to match the target size
148
148
149 This is intended to be used on chunk that density slicing selected by that
149 This is intended to be used on chunk that density slicing selected by that
150 are still too large compared to the read garantee of revlog. This might
150 are still too large compared to the read garantee of revlog. This might
151 happens when "minimal gap size" interrupted the slicing or when chain are
151 happens when "minimal gap size" interrupted the slicing or when chain are
152 built in a way that create large blocks next to each other.
152 built in a way that create large blocks next to each other.
153
153
154 >>> data = [
154 >>> data = [
155 ... 3, #0 (3)
155 ... 3, #0 (3)
156 ... 5, #1 (2)
156 ... 5, #1 (2)
157 ... 6, #2 (1)
157 ... 6, #2 (1)
158 ... 8, #3 (2)
158 ... 8, #3 (2)
159 ... 8, #4 (empty)
159 ... 8, #4 (empty)
160 ... 11, #5 (3)
160 ... 11, #5 (3)
161 ... 12, #6 (1)
161 ... 12, #6 (1)
162 ... 13, #7 (1)
162 ... 13, #7 (1)
163 ... 14, #8 (1)
163 ... 14, #8 (1)
164 ... ]
164 ... ]
165
165
166 == All snapshots cases ==
166 == All snapshots cases ==
167 >>> revlog = _testrevlog(data, snapshot=range(9))
167 >>> revlog = _testrevlog(data, snapshot=range(9))
168
168
169 Cases where chunk is already small enough
169 Cases where chunk is already small enough
170 >>> list(_slicechunktosize(revlog, [0], 3))
170 >>> list(_slicechunktosize(revlog, [0], 3))
171 [[0]]
171 [[0]]
172 >>> list(_slicechunktosize(revlog, [6, 7], 3))
172 >>> list(_slicechunktosize(revlog, [6, 7], 3))
173 [[6, 7]]
173 [[6, 7]]
174 >>> list(_slicechunktosize(revlog, [0], None))
174 >>> list(_slicechunktosize(revlog, [0], None))
175 [[0]]
175 [[0]]
176 >>> list(_slicechunktosize(revlog, [6, 7], None))
176 >>> list(_slicechunktosize(revlog, [6, 7], None))
177 [[6, 7]]
177 [[6, 7]]
178
178
179 cases where we need actual slicing
179 cases where we need actual slicing
180 >>> list(_slicechunktosize(revlog, [0, 1], 3))
180 >>> list(_slicechunktosize(revlog, [0, 1], 3))
181 [[0], [1]]
181 [[0], [1]]
182 >>> list(_slicechunktosize(revlog, [1, 3], 3))
182 >>> list(_slicechunktosize(revlog, [1, 3], 3))
183 [[1], [3]]
183 [[1], [3]]
184 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
184 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
185 [[1, 2], [3]]
185 [[1, 2], [3]]
186 >>> list(_slicechunktosize(revlog, [3, 5], 3))
186 >>> list(_slicechunktosize(revlog, [3, 5], 3))
187 [[3], [5]]
187 [[3], [5]]
188 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
188 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
189 [[3], [5]]
189 [[3], [5]]
190 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
190 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
191 [[5], [6, 7, 8]]
191 [[5], [6, 7, 8]]
192 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
192 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
193 [[0], [1, 2], [3], [5], [6, 7, 8]]
193 [[0], [1, 2], [3], [5], [6, 7, 8]]
194
194
195 Case with too large individual chunk (must return valid chunk)
195 Case with too large individual chunk (must return valid chunk)
196 >>> list(_slicechunktosize(revlog, [0, 1], 2))
196 >>> list(_slicechunktosize(revlog, [0, 1], 2))
197 [[0], [1]]
197 [[0], [1]]
198 >>> list(_slicechunktosize(revlog, [1, 3], 1))
198 >>> list(_slicechunktosize(revlog, [1, 3], 1))
199 [[1], [3]]
199 [[1], [3]]
200 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
200 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
201 [[3], [5]]
201 [[3], [5]]
202
202
203 == No Snapshot cases ==
203 == No Snapshot cases ==
204 >>> revlog = _testrevlog(data)
204 >>> revlog = _testrevlog(data)
205
205
206 Cases where chunk is already small enough
206 Cases where chunk is already small enough
207 >>> list(_slicechunktosize(revlog, [0], 3))
207 >>> list(_slicechunktosize(revlog, [0], 3))
208 [[0]]
208 [[0]]
209 >>> list(_slicechunktosize(revlog, [6, 7], 3))
209 >>> list(_slicechunktosize(revlog, [6, 7], 3))
210 [[6, 7]]
210 [[6, 7]]
211 >>> list(_slicechunktosize(revlog, [0], None))
211 >>> list(_slicechunktosize(revlog, [0], None))
212 [[0]]
212 [[0]]
213 >>> list(_slicechunktosize(revlog, [6, 7], None))
213 >>> list(_slicechunktosize(revlog, [6, 7], None))
214 [[6, 7]]
214 [[6, 7]]
215
215
216 cases where we need actual slicing
216 cases where we need actual slicing
217 >>> list(_slicechunktosize(revlog, [0, 1], 3))
217 >>> list(_slicechunktosize(revlog, [0, 1], 3))
218 [[0], [1]]
218 [[0], [1]]
219 >>> list(_slicechunktosize(revlog, [1, 3], 3))
219 >>> list(_slicechunktosize(revlog, [1, 3], 3))
220 [[1], [3]]
220 [[1], [3]]
221 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
221 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
222 [[1], [2, 3]]
222 [[1], [2, 3]]
223 >>> list(_slicechunktosize(revlog, [3, 5], 3))
223 >>> list(_slicechunktosize(revlog, [3, 5], 3))
224 [[3], [5]]
224 [[3], [5]]
225 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
225 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
226 [[3], [4, 5]]
226 [[3], [4, 5]]
227 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
227 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
228 [[5], [6, 7, 8]]
228 [[5], [6, 7, 8]]
229 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
229 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
230 [[0], [1, 2], [3], [5], [6, 7, 8]]
230 [[0], [1, 2], [3], [5], [6, 7, 8]]
231
231
232 Case with too large individual chunk (must return valid chunk)
232 Case with too large individual chunk (must return valid chunk)
233 >>> list(_slicechunktosize(revlog, [0, 1], 2))
233 >>> list(_slicechunktosize(revlog, [0, 1], 2))
234 [[0], [1]]
234 [[0], [1]]
235 >>> list(_slicechunktosize(revlog, [1, 3], 1))
235 >>> list(_slicechunktosize(revlog, [1, 3], 1))
236 [[1], [3]]
236 [[1], [3]]
237 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
237 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
238 [[3], [5]]
238 [[3], [5]]
239
239
240 == mixed case ==
240 == mixed case ==
241 >>> revlog = _testrevlog(data, snapshot=[0, 1, 2])
241 >>> revlog = _testrevlog(data, snapshot=[0, 1, 2])
242 >>> list(_slicechunktosize(revlog, list(range(9)), 5))
242 >>> list(_slicechunktosize(revlog, list(range(9)), 5))
243 [[0, 1], [2], [3, 4, 5], [6, 7, 8]]
243 [[0, 1], [2], [3, 4, 5], [6, 7, 8]]
244 """
244 """
245 assert targetsize is None or 0 <= targetsize
245 assert targetsize is None or 0 <= targetsize
246 startdata = revlog.start(revs[0])
246 startdata = revlog.start(revs[0])
247 enddata = revlog.end(revs[-1])
247 enddata = revlog.end(revs[-1])
248 fullspan = enddata - startdata
248 fullspan = enddata - startdata
249 if targetsize is None or fullspan <= targetsize:
249 if targetsize is None or fullspan <= targetsize:
250 yield revs
250 yield revs
251 return
251 return
252
252
253 startrevidx = 0
253 startrevidx = 0
254 endrevidx = 1
254 endrevidx = 1
255 iterrevs = enumerate(revs)
255 iterrevs = enumerate(revs)
256 next(iterrevs) # skip first rev.
256 next(iterrevs) # skip first rev.
257 # first step: get snapshots out of the way
257 # first step: get snapshots out of the way
258 for idx, r in iterrevs:
258 for idx, r in iterrevs:
259 span = revlog.end(r) - startdata
259 span = revlog.end(r) - startdata
260 snapshot = revlog.issnapshot(r)
260 snapshot = revlog.issnapshot(r)
261 if span <= targetsize and snapshot:
261 if span <= targetsize and snapshot:
262 endrevidx = idx + 1
262 endrevidx = idx + 1
263 else:
263 else:
264 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
264 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
265 if chunk:
265 if chunk:
266 yield chunk
266 yield chunk
267 startrevidx = idx
267 startrevidx = idx
268 startdata = revlog.start(r)
268 startdata = revlog.start(r)
269 endrevidx = idx + 1
269 endrevidx = idx + 1
270 if not snapshot:
270 if not snapshot:
271 break
271 break
272
272
273 # for the others, we use binary slicing to quickly converge toward valid
273 # for the others, we use binary slicing to quickly converge toward valid
274 # chunks (otherwise, we might end up looking for start/end of many
274 # chunks (otherwise, we might end up looking for start/end of many
275 # revisions). This logic is not looking for the perfect slicing point, it
275 # revisions). This logic is not looking for the perfect slicing point, it
276 # focuses on quickly converging toward valid chunks.
276 # focuses on quickly converging toward valid chunks.
277 nbitem = len(revs)
277 nbitem = len(revs)
278 while (enddata - startdata) > targetsize:
278 while (enddata - startdata) > targetsize:
279 endrevidx = nbitem
279 endrevidx = nbitem
280 if nbitem - startrevidx <= 1:
280 if nbitem - startrevidx <= 1:
281 break # protect against individual chunk larger than limit
281 break # protect against individual chunk larger than limit
282 localenddata = revlog.end(revs[endrevidx - 1])
282 localenddata = revlog.end(revs[endrevidx - 1])
283 span = localenddata - startdata
283 span = localenddata - startdata
284 while span > targetsize:
284 while span > targetsize:
285 if endrevidx - startrevidx <= 1:
285 if endrevidx - startrevidx <= 1:
286 break # protect against individual chunk larger than limit
286 break # protect against individual chunk larger than limit
287 endrevidx -= (endrevidx - startrevidx) // 2
287 endrevidx -= (endrevidx - startrevidx) // 2
288 localenddata = revlog.end(revs[endrevidx - 1])
288 localenddata = revlog.end(revs[endrevidx - 1])
289 span = localenddata - startdata
289 span = localenddata - startdata
290 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
290 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
291 if chunk:
291 if chunk:
292 yield chunk
292 yield chunk
293 startrevidx = endrevidx
293 startrevidx = endrevidx
294 startdata = revlog.start(revs[startrevidx])
294 startdata = revlog.start(revs[startrevidx])
295
295
296 chunk = _trimchunk(revlog, revs, startrevidx)
296 chunk = _trimchunk(revlog, revs, startrevidx)
297 if chunk:
297 if chunk:
298 yield chunk
298 yield chunk
299
299
300 def _slicechunktodensity(revlog, revs, targetdensity=0.5,
300 def _slicechunktodensity(revlog, revs, targetdensity=0.5,
301 mingapsize=0):
301 mingapsize=0):
302 """slice revs to reduce the amount of unrelated data to be read from disk.
302 """slice revs to reduce the amount of unrelated data to be read from disk.
303
303
304 ``revs`` is sliced into groups that should be read in one time.
304 ``revs`` is sliced into groups that should be read in one time.
305 Assume that revs are sorted.
305 Assume that revs are sorted.
306
306
307 The initial chunk is sliced until the overall density (payload/chunks-span
307 The initial chunk is sliced until the overall density (payload/chunks-span
308 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
308 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
309 skipped.
309 skipped.
310
310
311 >>> revlog = _testrevlog([
311 >>> revlog = _testrevlog([
312 ... 5, #00 (5)
312 ... 5, #00 (5)
313 ... 10, #01 (5)
313 ... 10, #01 (5)
314 ... 12, #02 (2)
314 ... 12, #02 (2)
315 ... 12, #03 (empty)
315 ... 12, #03 (empty)
316 ... 27, #04 (15)
316 ... 27, #04 (15)
317 ... 31, #05 (4)
317 ... 31, #05 (4)
318 ... 31, #06 (empty)
318 ... 31, #06 (empty)
319 ... 42, #07 (11)
319 ... 42, #07 (11)
320 ... 47, #08 (5)
320 ... 47, #08 (5)
321 ... 47, #09 (empty)
321 ... 47, #09 (empty)
322 ... 48, #10 (1)
322 ... 48, #10 (1)
323 ... 51, #11 (3)
323 ... 51, #11 (3)
324 ... 74, #12 (23)
324 ... 74, #12 (23)
325 ... 85, #13 (11)
325 ... 85, #13 (11)
326 ... 86, #14 (1)
326 ... 86, #14 (1)
327 ... 91, #15 (5)
327 ... 91, #15 (5)
328 ... ])
328 ... ])
329
329
330 >>> list(_slicechunktodensity(revlog, list(range(16))))
330 >>> list(_slicechunktodensity(revlog, list(range(16))))
331 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
331 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
332 >>> list(_slicechunktodensity(revlog, [0, 15]))
332 >>> list(_slicechunktodensity(revlog, [0, 15]))
333 [[0], [15]]
333 [[0], [15]]
334 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
334 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
335 [[0], [11], [15]]
335 [[0], [11], [15]]
336 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
336 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
337 [[0], [11, 13, 15]]
337 [[0], [11, 13, 15]]
338 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
338 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
339 [[1, 2], [5, 8, 10, 11], [14]]
339 [[1, 2], [5, 8, 10, 11], [14]]
340 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
340 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
341 ... mingapsize=20))
341 ... mingapsize=20))
342 [[1, 2, 3, 5, 8, 10, 11], [14]]
342 [[1, 2, 3, 5, 8, 10, 11], [14]]
343 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
343 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
344 ... targetdensity=0.95))
344 ... targetdensity=0.95))
345 [[1, 2], [5], [8, 10, 11], [14]]
345 [[1, 2], [5], [8, 10, 11], [14]]
346 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
346 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
347 ... targetdensity=0.95, mingapsize=12))
347 ... targetdensity=0.95, mingapsize=12))
348 [[1, 2], [5, 8, 10, 11], [14]]
348 [[1, 2], [5, 8, 10, 11], [14]]
349 """
349 """
350 start = revlog.start
350 start = revlog.start
351 length = revlog.length
351 length = revlog.length
352
352
353 if len(revs) <= 1:
353 if len(revs) <= 1:
354 yield revs
354 yield revs
355 return
355 return
356
356
357 deltachainspan = segmentspan(revlog, revs)
357 deltachainspan = segmentspan(revlog, revs)
358
358
359 if deltachainspan < mingapsize:
359 if deltachainspan < mingapsize:
360 yield revs
360 yield revs
361 return
361 return
362
362
363 readdata = deltachainspan
363 readdata = deltachainspan
364 chainpayload = sum(length(r) for r in revs)
364 chainpayload = sum(length(r) for r in revs)
365
365
366 if deltachainspan:
366 if deltachainspan:
367 density = chainpayload / float(deltachainspan)
367 density = chainpayload / float(deltachainspan)
368 else:
368 else:
369 density = 1.0
369 density = 1.0
370
370
371 if density >= targetdensity:
371 if density >= targetdensity:
372 yield revs
372 yield revs
373 return
373 return
374
374
375 # Store the gaps in a heap to have them sorted by decreasing size
375 # Store the gaps in a heap to have them sorted by decreasing size
376 gaps = []
376 gaps = []
377 prevend = None
377 prevend = None
378 for i, rev in enumerate(revs):
378 for i, rev in enumerate(revs):
379 revstart = start(rev)
379 revstart = start(rev)
380 revlen = length(rev)
380 revlen = length(rev)
381
381
382 # Skip empty revisions to form larger holes
382 # Skip empty revisions to form larger holes
383 if revlen == 0:
383 if revlen == 0:
384 continue
384 continue
385
385
386 if prevend is not None:
386 if prevend is not None:
387 gapsize = revstart - prevend
387 gapsize = revstart - prevend
388 # only consider holes that are large enough
388 # only consider holes that are large enough
389 if gapsize > mingapsize:
389 if gapsize > mingapsize:
390 gaps.append((gapsize, i))
390 gaps.append((gapsize, i))
391
391
392 prevend = revstart + revlen
392 prevend = revstart + revlen
393 # sort the gaps to pop them from largest to small
393 # sort the gaps to pop them from largest to small
394 gaps.sort()
394 gaps.sort()
395
395
396 # Collect the indices of the largest holes until the density is acceptable
396 # Collect the indices of the largest holes until the density is acceptable
397 selected = []
397 selected = []
398 while gaps and density < targetdensity:
398 while gaps and density < targetdensity:
399 gapsize, gapidx = gaps.pop()
399 gapsize, gapidx = gaps.pop()
400
400
401 selected.append(gapidx)
401 selected.append(gapidx)
402
402
403 # the gap sizes are stored as negatives to be sorted decreasingly
403 # the gap sizes are stored as negatives to be sorted decreasingly
404 # by the heap
404 # by the heap
405 readdata -= gapsize
405 readdata -= gapsize
406 if readdata > 0:
406 if readdata > 0:
407 density = chainpayload / float(readdata)
407 density = chainpayload / float(readdata)
408 else:
408 else:
409 density = 1.0
409 density = 1.0
410 selected.sort()
410 selected.sort()
411
411
412 # Cut the revs at collected indices
412 # Cut the revs at collected indices
413 previdx = 0
413 previdx = 0
414 for idx in selected:
414 for idx in selected:
415
415
416 chunk = _trimchunk(revlog, revs, previdx, idx)
416 chunk = _trimchunk(revlog, revs, previdx, idx)
417 if chunk:
417 if chunk:
418 yield chunk
418 yield chunk
419
419
420 previdx = idx
420 previdx = idx
421
421
422 chunk = _trimchunk(revlog, revs, previdx)
422 chunk = _trimchunk(revlog, revs, previdx)
423 if chunk:
423 if chunk:
424 yield chunk
424 yield chunk
425
425
426 def _trimchunk(revlog, revs, startidx, endidx=None):
426 def _trimchunk(revlog, revs, startidx, endidx=None):
427 """returns revs[startidx:endidx] without empty trailing revs
427 """returns revs[startidx:endidx] without empty trailing revs
428
428
429 Doctest Setup
429 Doctest Setup
430 >>> revlog = _testrevlog([
430 >>> revlog = _testrevlog([
431 ... 5, #0
431 ... 5, #0
432 ... 10, #1
432 ... 10, #1
433 ... 12, #2
433 ... 12, #2
434 ... 12, #3 (empty)
434 ... 12, #3 (empty)
435 ... 17, #4
435 ... 17, #4
436 ... 21, #5
436 ... 21, #5
437 ... 21, #6 (empty)
437 ... 21, #6 (empty)
438 ... ])
438 ... ])
439
439
440 Contiguous cases:
440 Contiguous cases:
441 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
441 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
442 [0, 1, 2, 3, 4, 5]
442 [0, 1, 2, 3, 4, 5]
443 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
443 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
444 [0, 1, 2, 3, 4]
444 [0, 1, 2, 3, 4]
445 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
445 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
446 [0, 1, 2]
446 [0, 1, 2]
447 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
447 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
448 [2]
448 [2]
449 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
449 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
450 [3, 4, 5]
450 [3, 4, 5]
451 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
451 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
452 [3, 4]
452 [3, 4]
453
453
454 Discontiguous cases:
454 Discontiguous cases:
455 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
455 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
456 [1, 3, 5]
456 [1, 3, 5]
457 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
457 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
458 [1]
458 [1]
459 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
459 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
460 [3, 5]
460 [3, 5]
461 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
461 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
462 [3, 5]
462 [3, 5]
463 """
463 """
464 length = revlog.length
464 length = revlog.length
465
465
466 if endidx is None:
466 if endidx is None:
467 endidx = len(revs)
467 endidx = len(revs)
468
468
469 # If we have a non-emtpy delta candidate, there are nothing to trim
469 # If we have a non-emtpy delta candidate, there are nothing to trim
470 if revs[endidx - 1] < len(revlog):
470 if revs[endidx - 1] < len(revlog):
471 # Trim empty revs at the end, except the very first revision of a chain
471 # Trim empty revs at the end, except the very first revision of a chain
472 while (endidx > 1
472 while (endidx > 1
473 and endidx > startidx
473 and endidx > startidx
474 and length(revs[endidx - 1]) == 0):
474 and length(revs[endidx - 1]) == 0):
475 endidx -= 1
475 endidx -= 1
476
476
477 return revs[startidx:endidx]
477 return revs[startidx:endidx]
478
478
479 def segmentspan(revlog, revs):
479 def segmentspan(revlog, revs):
480 """Get the byte span of a segment of revisions
480 """Get the byte span of a segment of revisions
481
481
482 revs is a sorted array of revision numbers
482 revs is a sorted array of revision numbers
483
483
484 >>> revlog = _testrevlog([
484 >>> revlog = _testrevlog([
485 ... 5, #0
485 ... 5, #0
486 ... 10, #1
486 ... 10, #1
487 ... 12, #2
487 ... 12, #2
488 ... 12, #3 (empty)
488 ... 12, #3 (empty)
489 ... 17, #4
489 ... 17, #4
490 ... ])
490 ... ])
491
491
492 >>> segmentspan(revlog, [0, 1, 2, 3, 4])
492 >>> segmentspan(revlog, [0, 1, 2, 3, 4])
493 17
493 17
494 >>> segmentspan(revlog, [0, 4])
494 >>> segmentspan(revlog, [0, 4])
495 17
495 17
496 >>> segmentspan(revlog, [3, 4])
496 >>> segmentspan(revlog, [3, 4])
497 5
497 5
498 >>> segmentspan(revlog, [1, 2, 3,])
498 >>> segmentspan(revlog, [1, 2, 3,])
499 7
499 7
500 >>> segmentspan(revlog, [1, 3])
500 >>> segmentspan(revlog, [1, 3])
501 7
501 7
502 """
502 """
503 if not revs:
503 if not revs:
504 return 0
504 return 0
505 end = revlog.end(revs[-1])
505 end = revlog.end(revs[-1])
506 return end - revlog.start(revs[0])
506 return end - revlog.start(revs[0])
507
507
508 def _textfromdelta(fh, revlog, baserev, delta, p1, p2, flags, expectednode):
508 def _textfromdelta(fh, revlog, baserev, delta, p1, p2, flags, expectednode):
509 """build full text from a (base, delta) pair and other metadata"""
509 """build full text from a (base, delta) pair and other metadata"""
510 # special case deltas which replace entire base; no need to decode
510 # special case deltas which replace entire base; no need to decode
511 # base revision. this neatly avoids censored bases, which throw when
511 # base revision. this neatly avoids censored bases, which throw when
512 # they're decoded.
512 # they're decoded.
513 hlen = struct.calcsize(">lll")
513 hlen = struct.calcsize(">lll")
514 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
514 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
515 len(delta) - hlen):
515 len(delta) - hlen):
516 fulltext = delta[hlen:]
516 fulltext = delta[hlen:]
517 else:
517 else:
518 # deltabase is rawtext before changed by flag processors, which is
518 # deltabase is rawtext before changed by flag processors, which is
519 # equivalent to non-raw text
519 # equivalent to non-raw text
520 basetext = revlog.revision(baserev, _df=fh, raw=False)
520 basetext = revlog.revision(baserev, _df=fh, raw=False)
521 fulltext = mdiff.patch(basetext, delta)
521 fulltext = mdiff.patch(basetext, delta)
522
522
523 try:
523 try:
524 res = revlog._processflags(fulltext, flags, 'read', raw=True)
524 res = revlog._processflags(fulltext, flags, 'read', raw=True)
525 fulltext, validatehash = res
525 fulltext, validatehash = res
526 if validatehash:
526 if validatehash:
527 revlog.checkhash(fulltext, expectednode, p1=p1, p2=p2)
527 revlog.checkhash(fulltext, expectednode, p1=p1, p2=p2)
528 if flags & REVIDX_ISCENSORED:
528 if flags & REVIDX_ISCENSORED:
529 raise error.StorageError(_('node %s is not censored') %
529 raise error.StorageError(_('node %s is not censored') %
530 expectednode)
530 expectednode)
531 except error.CensoredNodeError:
531 except error.CensoredNodeError:
532 # must pass the censored index flag to add censored revisions
532 # must pass the censored index flag to add censored revisions
533 if not flags & REVIDX_ISCENSORED:
533 if not flags & REVIDX_ISCENSORED:
534 raise
534 raise
535 return fulltext
535 return fulltext
536
536
537 @attr.s(slots=True, frozen=True)
537 @attr.s(slots=True, frozen=True)
538 class _deltainfo(object):
538 class _deltainfo(object):
539 distance = attr.ib()
539 distance = attr.ib()
540 deltalen = attr.ib()
540 deltalen = attr.ib()
541 data = attr.ib()
541 data = attr.ib()
542 base = attr.ib()
542 base = attr.ib()
543 chainbase = attr.ib()
543 chainbase = attr.ib()
544 chainlen = attr.ib()
544 chainlen = attr.ib()
545 compresseddeltalen = attr.ib()
545 compresseddeltalen = attr.ib()
546 snapshotdepth = attr.ib()
546 snapshotdepth = attr.ib()
547
547
548 def isgooddeltainfo(revlog, deltainfo, revinfo):
548 def isgooddeltainfo(revlog, deltainfo, revinfo):
549 """Returns True if the given delta is good. Good means that it is within
549 """Returns True if the given delta is good. Good means that it is within
550 the disk span, disk size, and chain length bounds that we know to be
550 the disk span, disk size, and chain length bounds that we know to be
551 performant."""
551 performant."""
552 if deltainfo is None:
552 if deltainfo is None:
553 return False
553 return False
554
554
555 # - 'deltainfo.distance' is the distance from the base revision --
555 # - 'deltainfo.distance' is the distance from the base revision --
556 # bounding it limits the amount of I/O we need to do.
556 # bounding it limits the amount of I/O we need to do.
557 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
557 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
558 # deltas we need to apply -- bounding it limits the amount of CPU
558 # deltas we need to apply -- bounding it limits the amount of CPU
559 # we consume.
559 # we consume.
560
560
561 textlen = revinfo.textlen
561 textlen = revinfo.textlen
562 defaultmax = textlen * 4
562 defaultmax = textlen * 4
563 maxdist = revlog._maxdeltachainspan
563 maxdist = revlog._maxdeltachainspan
564 if not maxdist:
564 if not maxdist:
565 maxdist = deltainfo.distance # ensure the conditional pass
565 maxdist = deltainfo.distance # ensure the conditional pass
566 maxdist = max(maxdist, defaultmax)
566 maxdist = max(maxdist, defaultmax)
567
567
568 # Bad delta from read span:
568 # Bad delta from read span:
569 #
569 #
570 # If the span of data read is larger than the maximum allowed.
570 # If the span of data read is larger than the maximum allowed.
571 #
571 #
572 # In the sparse-revlog case, we rely on the associated "sparse reading"
572 # In the sparse-revlog case, we rely on the associated "sparse reading"
573 # to avoid issue related to the span of data. In theory, it would be
573 # to avoid issue related to the span of data. In theory, it would be
574 # possible to build pathological revlog where delta pattern would lead
574 # possible to build pathological revlog where delta pattern would lead
575 # to too many reads. However, they do not happen in practice at all. So
575 # to too many reads. However, they do not happen in practice at all. So
576 # we skip the span check entirely.
576 # we skip the span check entirely.
577 if not revlog._sparserevlog and maxdist < deltainfo.distance:
577 if not revlog._sparserevlog and maxdist < deltainfo.distance:
578 return False
578 return False
579
579
580 # Bad delta from new delta size:
580 # Bad delta from new delta size:
581 #
581 #
582 # If the delta size is larger than the target text, storing the
582 # If the delta size is larger than the target text, storing the
583 # delta will be inefficient.
583 # delta will be inefficient.
584 if textlen < deltainfo.deltalen:
584 if textlen < deltainfo.deltalen:
585 return False
585 return False
586
586
587 # Bad delta from cumulated payload size:
587 # Bad delta from cumulated payload size:
588 #
588 #
589 # If the sum of delta get larger than K * target text length.
589 # If the sum of delta get larger than K * target text length.
590 if textlen * LIMIT_DELTA2TEXT < deltainfo.compresseddeltalen:
590 if textlen * LIMIT_DELTA2TEXT < deltainfo.compresseddeltalen:
591 return False
591 return False
592
592
593 # Bad delta from chain length:
593 # Bad delta from chain length:
594 #
594 #
595 # If the number of delta in the chain gets too high.
595 # If the number of delta in the chain gets too high.
596 if (revlog._maxchainlen
596 if (revlog._maxchainlen
597 and revlog._maxchainlen < deltainfo.chainlen):
597 and revlog._maxchainlen < deltainfo.chainlen):
598 return False
598 return False
599
599
600 # bad delta from intermediate snapshot size limit
600 # bad delta from intermediate snapshot size limit
601 #
601 #
602 # If an intermediate snapshot size is higher than the limit. The
602 # If an intermediate snapshot size is higher than the limit. The
603 # limit exist to prevent endless chain of intermediate delta to be
603 # limit exist to prevent endless chain of intermediate delta to be
604 # created.
604 # created.
605 if (deltainfo.snapshotdepth is not None and
605 if (deltainfo.snapshotdepth is not None and
606 (textlen >> deltainfo.snapshotdepth) < deltainfo.deltalen):
606 (textlen >> deltainfo.snapshotdepth) < deltainfo.deltalen):
607 return False
607 return False
608
608
609 # bad delta if new intermediate snapshot is larger than the previous
609 # bad delta if new intermediate snapshot is larger than the previous
610 # snapshot
610 # snapshot
611 if (deltainfo.snapshotdepth
611 if (deltainfo.snapshotdepth
612 and revlog.length(deltainfo.base) < deltainfo.deltalen):
612 and revlog.length(deltainfo.base) < deltainfo.deltalen):
613 return False
613 return False
614
614
615 return True
615 return True
616
616
617 # If a revision's full text is that much bigger than a base candidate full
617 # If a revision's full text is that much bigger than a base candidate full
618 # text's, it is very unlikely that it will produce a valid delta. We no longer
618 # text's, it is very unlikely that it will produce a valid delta. We no longer
619 # consider these candidates.
619 # consider these candidates.
620 LIMIT_BASE2TEXT = 500
620 LIMIT_BASE2TEXT = 500
621
621
622 def _candidategroups(revlog, textlen, p1, p2, cachedelta):
622 def _candidategroups(revlog, textlen, p1, p2, cachedelta):
623 """Provides group of revision to be tested as delta base
623 """Provides group of revision to be tested as delta base
624
624
625 This top level function focus on emitting groups with unique and worthwhile
625 This top level function focus on emitting groups with unique and worthwhile
626 content. See _raw_candidate_groups for details about the group order.
626 content. See _raw_candidate_groups for details about the group order.
627 """
627 """
628 # should we try to build a delta?
628 # should we try to build a delta?
629 if not (len(revlog) and revlog._storedeltachains):
629 if not (len(revlog) and revlog._storedeltachains):
630 yield None
630 yield None
631 return
631 return
632
632
633 deltalength = revlog.length
633 deltalength = revlog.length
634 deltaparent = revlog.deltaparent
634 deltaparent = revlog.deltaparent
635 sparse = revlog._sparserevlog
635 sparse = revlog._sparserevlog
636 good = None
636 good = None
637
637
638 deltas_limit = textlen * LIMIT_DELTA2TEXT
638 deltas_limit = textlen * LIMIT_DELTA2TEXT
639
639
640 tested = {nullrev}
640 tested = {nullrev}
641 candidates = _refinedgroups(revlog, p1, p2, cachedelta)
641 candidates = _refinedgroups(revlog, p1, p2, cachedelta)
642 while True:
642 while True:
643 temptative = candidates.send(good)
643 temptative = candidates.send(good)
644 if temptative is None:
644 if temptative is None:
645 break
645 break
646 group = []
646 group = []
647 for rev in temptative:
647 for rev in temptative:
648 # skip over empty delta (no need to include them in a chain)
648 # skip over empty delta (no need to include them in a chain)
649 while (revlog._generaldelta
649 while (revlog._generaldelta
650 and not (rev == nullrev
650 and not (rev == nullrev
651 or rev in tested
651 or rev in tested
652 or deltalength(rev))):
652 or deltalength(rev))):
653 tested.add(rev)
653 tested.add(rev)
654 rev = deltaparent(rev)
654 rev = deltaparent(rev)
655 # no need to try a delta against nullrev, this will be done as a
655 # no need to try a delta against nullrev, this will be done as a
656 # last resort.
656 # last resort.
657 if rev == nullrev:
657 if rev == nullrev:
658 continue
658 continue
659 # filter out revision we tested already
659 # filter out revision we tested already
660 if rev in tested:
660 if rev in tested:
661 continue
661 continue
662 tested.add(rev)
662 tested.add(rev)
663 # filter out delta base that will never produce good delta
663 # filter out delta base that will never produce good delta
664 if deltas_limit < revlog.length(rev):
664 if deltas_limit < revlog.length(rev):
665 continue
665 continue
666 if sparse and revlog.rawsize(rev) < (textlen // LIMIT_BASE2TEXT):
666 if sparse and revlog.rawsize(rev) < (textlen // LIMIT_BASE2TEXT):
667 continue
667 continue
668 # no delta for rawtext-changing revs (see "candelta" for why)
668 # no delta for rawtext-changing revs (see "candelta" for why)
669 if revlog.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
669 if revlog.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
670 continue
670 continue
671 # If we reach here, we are about to build and test a delta.
671 # If we reach here, we are about to build and test a delta.
672 # The delta building process will compute the chaininfo in all
672 # The delta building process will compute the chaininfo in all
673 # case, since that computation is cached, it is fine to access it
673 # case, since that computation is cached, it is fine to access it
674 # here too.
674 # here too.
675 chainlen, chainsize = revlog._chaininfo(rev)
675 chainlen, chainsize = revlog._chaininfo(rev)
676 # if chain will be too long, skip base
676 # if chain will be too long, skip base
677 if revlog._maxchainlen and chainlen >= revlog._maxchainlen:
677 if revlog._maxchainlen and chainlen >= revlog._maxchainlen:
678 continue
678 continue
679 # if chain already have too much data, skip base
679 # if chain already have too much data, skip base
680 if deltas_limit < chainsize:
680 if deltas_limit < chainsize:
681 continue
681 continue
682 if sparse and revlog.upperboundcomp is not None:
683 maxcomp = revlog.upperboundcomp
684 basenotsnap = (p1, p2, nullrev)
685 if rev not in basenotsnap and revlog.issnapshot(rev):
686 snapshotdepth = revlog.snapshotdepth(rev)
687 # If text is significantly larger than the base, we can
688 # expect the resulting delta to be proportional to the size
689 # difference
690 revsize = revlog.rawsize(rev)
691 rawsizedistance = max(textlen - revsize, 0)
692 # use an estimate of the compression upper bound.
693 lowestrealisticdeltalen = rawsizedistance // maxcomp
694
695 # check the absolute constraint on the delta size
696 snapshotlimit = textlen >> snapshotdepth
697 if snapshotlimit < lowestrealisticdeltalen:
698 # delta lower bound is larger than accepted upper bound
699 continue
700
682 group.append(rev)
701 group.append(rev)
683 if group:
702 if group:
684 # XXX: in the sparse revlog case, group can become large,
703 # XXX: in the sparse revlog case, group can become large,
685 # impacting performances. Some bounding or slicing mecanism
704 # impacting performances. Some bounding or slicing mecanism
686 # would help to reduce this impact.
705 # would help to reduce this impact.
687 good = yield tuple(group)
706 good = yield tuple(group)
688 yield None
707 yield None
689
708
690 def _findsnapshots(revlog, cache, start_rev):
709 def _findsnapshots(revlog, cache, start_rev):
691 """find snapshot from start_rev to tip"""
710 """find snapshot from start_rev to tip"""
692 if util.safehasattr(revlog.index, 'findsnapshots'):
711 if util.safehasattr(revlog.index, 'findsnapshots'):
693 revlog.index.findsnapshots(cache, start_rev)
712 revlog.index.findsnapshots(cache, start_rev)
694 else:
713 else:
695 deltaparent = revlog.deltaparent
714 deltaparent = revlog.deltaparent
696 issnapshot = revlog.issnapshot
715 issnapshot = revlog.issnapshot
697 for rev in revlog.revs(start_rev):
716 for rev in revlog.revs(start_rev):
698 if issnapshot(rev):
717 if issnapshot(rev):
699 cache[deltaparent(rev)].append(rev)
718 cache[deltaparent(rev)].append(rev)
700
719
701 def _refinedgroups(revlog, p1, p2, cachedelta):
720 def _refinedgroups(revlog, p1, p2, cachedelta):
702 good = None
721 good = None
703 # First we try to reuse a the delta contained in the bundle.
722 # First we try to reuse a the delta contained in the bundle.
704 # (or from the source revlog)
723 # (or from the source revlog)
705 #
724 #
706 # This logic only applies to general delta repositories and can be disabled
725 # This logic only applies to general delta repositories and can be disabled
707 # through configuration. Disabling reuse source delta is useful when
726 # through configuration. Disabling reuse source delta is useful when
708 # we want to make sure we recomputed "optimal" deltas.
727 # we want to make sure we recomputed "optimal" deltas.
709 if cachedelta and revlog._generaldelta and revlog._lazydeltabase:
728 if cachedelta and revlog._generaldelta and revlog._lazydeltabase:
710 # Assume what we received from the server is a good choice
729 # Assume what we received from the server is a good choice
711 # build delta will reuse the cache
730 # build delta will reuse the cache
712 good = yield (cachedelta[0],)
731 good = yield (cachedelta[0],)
713 if good is not None:
732 if good is not None:
714 yield None
733 yield None
715 return
734 return
716 snapshots = collections.defaultdict(list)
735 snapshots = collections.defaultdict(list)
717 for candidates in _rawgroups(revlog, p1, p2, cachedelta, snapshots):
736 for candidates in _rawgroups(revlog, p1, p2, cachedelta, snapshots):
718 good = yield candidates
737 good = yield candidates
719 if good is not None:
738 if good is not None:
720 break
739 break
721
740
722 # If sparse revlog is enabled, we can try to refine the available deltas
741 # If sparse revlog is enabled, we can try to refine the available deltas
723 if not revlog._sparserevlog:
742 if not revlog._sparserevlog:
724 yield None
743 yield None
725 return
744 return
726
745
727 # if we have a refinable value, try to refine it
746 # if we have a refinable value, try to refine it
728 if good is not None and good not in (p1, p2) and revlog.issnapshot(good):
747 if good is not None and good not in (p1, p2) and revlog.issnapshot(good):
729 # refine snapshot down
748 # refine snapshot down
730 previous = None
749 previous = None
731 while previous != good:
750 while previous != good:
732 previous = good
751 previous = good
733 base = revlog.deltaparent(good)
752 base = revlog.deltaparent(good)
734 if base == nullrev:
753 if base == nullrev:
735 break
754 break
736 good = yield (base,)
755 good = yield (base,)
737 # refine snapshot up
756 # refine snapshot up
738 if not snapshots:
757 if not snapshots:
739 _findsnapshots(revlog, snapshots, good + 1)
758 _findsnapshots(revlog, snapshots, good + 1)
740 previous = None
759 previous = None
741 while good != previous:
760 while good != previous:
742 previous = good
761 previous = good
743 children = tuple(sorted(c for c in snapshots[good]))
762 children = tuple(sorted(c for c in snapshots[good]))
744 good = yield children
763 good = yield children
745
764
746 # we have found nothing
765 # we have found nothing
747 yield None
766 yield None
748
767
749 def _rawgroups(revlog, p1, p2, cachedelta, snapshots=None):
768 def _rawgroups(revlog, p1, p2, cachedelta, snapshots=None):
750 """Provides group of revision to be tested as delta base
769 """Provides group of revision to be tested as delta base
751
770
752 This lower level function focus on emitting delta theorically interresting
771 This lower level function focus on emitting delta theorically interresting
753 without looking it any practical details.
772 without looking it any practical details.
754
773
755 The group order aims at providing fast or small candidates first.
774 The group order aims at providing fast or small candidates first.
756 """
775 """
757 gdelta = revlog._generaldelta
776 gdelta = revlog._generaldelta
758 # gate sparse behind general-delta because of issue6056
777 # gate sparse behind general-delta because of issue6056
759 sparse = gdelta and revlog._sparserevlog
778 sparse = gdelta and revlog._sparserevlog
760 curr = len(revlog)
779 curr = len(revlog)
761 prev = curr - 1
780 prev = curr - 1
762 deltachain = lambda rev: revlog._deltachain(rev)[0]
781 deltachain = lambda rev: revlog._deltachain(rev)[0]
763
782
764 if gdelta:
783 if gdelta:
765 # exclude already lazy tested base if any
784 # exclude already lazy tested base if any
766 parents = [p for p in (p1, p2) if p != nullrev]
785 parents = [p for p in (p1, p2) if p != nullrev]
767
786
768 if not revlog._deltabothparents and len(parents) == 2:
787 if not revlog._deltabothparents and len(parents) == 2:
769 parents.sort()
788 parents.sort()
770 # To minimize the chance of having to build a fulltext,
789 # To minimize the chance of having to build a fulltext,
771 # pick first whichever parent is closest to us (max rev)
790 # pick first whichever parent is closest to us (max rev)
772 yield (parents[1],)
791 yield (parents[1],)
773 # then the other one (min rev) if the first did not fit
792 # then the other one (min rev) if the first did not fit
774 yield (parents[0],)
793 yield (parents[0],)
775 elif len(parents) > 0:
794 elif len(parents) > 0:
776 # Test all parents (1 or 2), and keep the best candidate
795 # Test all parents (1 or 2), and keep the best candidate
777 yield parents
796 yield parents
778
797
779 if sparse and parents:
798 if sparse and parents:
780 if snapshots is None:
799 if snapshots is None:
781 # map: base-rev: snapshot-rev
800 # map: base-rev: snapshot-rev
782 snapshots = collections.defaultdict(list)
801 snapshots = collections.defaultdict(list)
783 # See if we can use an existing snapshot in the parent chains to use as
802 # See if we can use an existing snapshot in the parent chains to use as
784 # a base for a new intermediate-snapshot
803 # a base for a new intermediate-snapshot
785 #
804 #
786 # search for snapshot in parents delta chain
805 # search for snapshot in parents delta chain
787 # map: snapshot-level: snapshot-rev
806 # map: snapshot-level: snapshot-rev
788 parents_snaps = collections.defaultdict(set)
807 parents_snaps = collections.defaultdict(set)
789 candidate_chains = [deltachain(p) for p in parents]
808 candidate_chains = [deltachain(p) for p in parents]
790 for chain in candidate_chains:
809 for chain in candidate_chains:
791 for idx, s in enumerate(chain):
810 for idx, s in enumerate(chain):
792 if not revlog.issnapshot(s):
811 if not revlog.issnapshot(s):
793 break
812 break
794 parents_snaps[idx].add(s)
813 parents_snaps[idx].add(s)
795 snapfloor = min(parents_snaps[0]) + 1
814 snapfloor = min(parents_snaps[0]) + 1
796 _findsnapshots(revlog, snapshots, snapfloor)
815 _findsnapshots(revlog, snapshots, snapfloor)
797 # search for the highest "unrelated" revision
816 # search for the highest "unrelated" revision
798 #
817 #
799 # Adding snapshots used by "unrelated" revision increase the odd we
818 # Adding snapshots used by "unrelated" revision increase the odd we
800 # reuse an independant, yet better snapshot chain.
819 # reuse an independant, yet better snapshot chain.
801 #
820 #
802 # XXX instead of building a set of revisions, we could lazily enumerate
821 # XXX instead of building a set of revisions, we could lazily enumerate
803 # over the chains. That would be more efficient, however we stick to
822 # over the chains. That would be more efficient, however we stick to
804 # simple code for now.
823 # simple code for now.
805 all_revs = set()
824 all_revs = set()
806 for chain in candidate_chains:
825 for chain in candidate_chains:
807 all_revs.update(chain)
826 all_revs.update(chain)
808 other = None
827 other = None
809 for r in revlog.revs(prev, snapfloor):
828 for r in revlog.revs(prev, snapfloor):
810 if r not in all_revs:
829 if r not in all_revs:
811 other = r
830 other = r
812 break
831 break
813 if other is not None:
832 if other is not None:
814 # To avoid unfair competition, we won't use unrelated intermediate
833 # To avoid unfair competition, we won't use unrelated intermediate
815 # snapshot that are deeper than the ones from the parent delta
834 # snapshot that are deeper than the ones from the parent delta
816 # chain.
835 # chain.
817 max_depth = max(parents_snaps.keys())
836 max_depth = max(parents_snaps.keys())
818 chain = deltachain(other)
837 chain = deltachain(other)
819 for idx, s in enumerate(chain):
838 for idx, s in enumerate(chain):
820 if s < snapfloor:
839 if s < snapfloor:
821 continue
840 continue
822 if max_depth < idx:
841 if max_depth < idx:
823 break
842 break
824 if not revlog.issnapshot(s):
843 if not revlog.issnapshot(s):
825 break
844 break
826 parents_snaps[idx].add(s)
845 parents_snaps[idx].add(s)
827 # Test them as possible intermediate snapshot base
846 # Test them as possible intermediate snapshot base
828 # We test them from highest to lowest level. High level one are more
847 # We test them from highest to lowest level. High level one are more
829 # likely to result in small delta
848 # likely to result in small delta
830 floor = None
849 floor = None
831 for idx, snaps in sorted(parents_snaps.items(), reverse=True):
850 for idx, snaps in sorted(parents_snaps.items(), reverse=True):
832 siblings = set()
851 siblings = set()
833 for s in snaps:
852 for s in snaps:
834 siblings.update(snapshots[s])
853 siblings.update(snapshots[s])
835 # Before considering making a new intermediate snapshot, we check
854 # Before considering making a new intermediate snapshot, we check
836 # if an existing snapshot, children of base we consider, would be
855 # if an existing snapshot, children of base we consider, would be
837 # suitable.
856 # suitable.
838 #
857 #
839 # It give a change to reuse a delta chain "unrelated" to the
858 # It give a change to reuse a delta chain "unrelated" to the
840 # current revision instead of starting our own. Without such
859 # current revision instead of starting our own. Without such
841 # re-use, topological branches would keep reopening new chains.
860 # re-use, topological branches would keep reopening new chains.
842 # Creating more and more snapshot as the repository grow.
861 # Creating more and more snapshot as the repository grow.
843
862
844 if floor is not None:
863 if floor is not None:
845 # We only do this for siblings created after the one in our
864 # We only do this for siblings created after the one in our
846 # parent's delta chain. Those created before has less chances
865 # parent's delta chain. Those created before has less chances
847 # to be valid base since our ancestors had to create a new
866 # to be valid base since our ancestors had to create a new
848 # snapshot.
867 # snapshot.
849 siblings = [r for r in siblings if floor < r]
868 siblings = [r for r in siblings if floor < r]
850 yield tuple(sorted(siblings))
869 yield tuple(sorted(siblings))
851 # then test the base from our parent's delta chain.
870 # then test the base from our parent's delta chain.
852 yield tuple(sorted(snaps))
871 yield tuple(sorted(snaps))
853 floor = min(snaps)
872 floor = min(snaps)
854 # No suitable base found in the parent chain, search if any full
873 # No suitable base found in the parent chain, search if any full
855 # snapshots emitted since parent's base would be a suitable base for an
874 # snapshots emitted since parent's base would be a suitable base for an
856 # intermediate snapshot.
875 # intermediate snapshot.
857 #
876 #
858 # It give a chance to reuse a delta chain unrelated to the current
877 # It give a chance to reuse a delta chain unrelated to the current
859 # revisions instead of starting our own. Without such re-use,
878 # revisions instead of starting our own. Without such re-use,
860 # topological branches would keep reopening new full chains. Creating
879 # topological branches would keep reopening new full chains. Creating
861 # more and more snapshot as the repository grow.
880 # more and more snapshot as the repository grow.
862 yield tuple(snapshots[nullrev])
881 yield tuple(snapshots[nullrev])
863
882
864 if not sparse:
883 if not sparse:
865 # other approach failed try against prev to hopefully save us a
884 # other approach failed try against prev to hopefully save us a
866 # fulltext.
885 # fulltext.
867 yield (prev,)
886 yield (prev,)
868
887
869 class deltacomputer(object):
888 class deltacomputer(object):
870 def __init__(self, revlog):
889 def __init__(self, revlog):
871 self.revlog = revlog
890 self.revlog = revlog
872
891
873 def buildtext(self, revinfo, fh):
892 def buildtext(self, revinfo, fh):
874 """Builds a fulltext version of a revision
893 """Builds a fulltext version of a revision
875
894
876 revinfo: _revisioninfo instance that contains all needed info
895 revinfo: _revisioninfo instance that contains all needed info
877 fh: file handle to either the .i or the .d revlog file,
896 fh: file handle to either the .i or the .d revlog file,
878 depending on whether it is inlined or not
897 depending on whether it is inlined or not
879 """
898 """
880 btext = revinfo.btext
899 btext = revinfo.btext
881 if btext[0] is not None:
900 if btext[0] is not None:
882 return btext[0]
901 return btext[0]
883
902
884 revlog = self.revlog
903 revlog = self.revlog
885 cachedelta = revinfo.cachedelta
904 cachedelta = revinfo.cachedelta
886 baserev = cachedelta[0]
905 baserev = cachedelta[0]
887 delta = cachedelta[1]
906 delta = cachedelta[1]
888
907
889 fulltext = btext[0] = _textfromdelta(fh, revlog, baserev, delta,
908 fulltext = btext[0] = _textfromdelta(fh, revlog, baserev, delta,
890 revinfo.p1, revinfo.p2,
909 revinfo.p1, revinfo.p2,
891 revinfo.flags, revinfo.node)
910 revinfo.flags, revinfo.node)
892 return fulltext
911 return fulltext
893
912
894 def _builddeltadiff(self, base, revinfo, fh):
913 def _builddeltadiff(self, base, revinfo, fh):
895 revlog = self.revlog
914 revlog = self.revlog
896 t = self.buildtext(revinfo, fh)
915 t = self.buildtext(revinfo, fh)
897 if revlog.iscensored(base):
916 if revlog.iscensored(base):
898 # deltas based on a censored revision must replace the
917 # deltas based on a censored revision must replace the
899 # full content in one patch, so delta works everywhere
918 # full content in one patch, so delta works everywhere
900 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
919 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
901 delta = header + t
920 delta = header + t
902 else:
921 else:
903 ptext = revlog.revision(base, _df=fh, raw=True)
922 ptext = revlog.revision(base, _df=fh, raw=True)
904 delta = mdiff.textdiff(ptext, t)
923 delta = mdiff.textdiff(ptext, t)
905
924
906 return delta
925 return delta
907
926
908 def _builddeltainfo(self, revinfo, base, fh):
927 def _builddeltainfo(self, revinfo, base, fh):
909 # can we use the cached delta?
928 # can we use the cached delta?
910 delta = None
929 delta = None
911 if revinfo.cachedelta:
930 if revinfo.cachedelta:
912 cachebase, cachediff = revinfo.cachedelta
931 cachebase, cachediff = revinfo.cachedelta
913 #check if the diff still apply
932 #check if the diff still apply
914 currentbase = cachebase
933 currentbase = cachebase
915 while (currentbase != nullrev
934 while (currentbase != nullrev
916 and currentbase != base
935 and currentbase != base
917 and self.revlog.length(currentbase) == 0):
936 and self.revlog.length(currentbase) == 0):
918 currentbase = self.revlog.deltaparent(currentbase)
937 currentbase = self.revlog.deltaparent(currentbase)
919 if self.revlog._lazydelta and currentbase == base:
938 if self.revlog._lazydelta and currentbase == base:
920 delta = revinfo.cachedelta[1]
939 delta = revinfo.cachedelta[1]
921 if delta is None:
940 if delta is None:
922 delta = self._builddeltadiff(base, revinfo, fh)
941 delta = self._builddeltadiff(base, revinfo, fh)
923 revlog = self.revlog
942 revlog = self.revlog
924 header, data = revlog.compress(delta)
943 header, data = revlog.compress(delta)
925 deltalen = len(header) + len(data)
944 deltalen = len(header) + len(data)
926 chainbase = revlog.chainbase(base)
945 chainbase = revlog.chainbase(base)
927 offset = revlog.end(len(revlog) - 1)
946 offset = revlog.end(len(revlog) - 1)
928 dist = deltalen + offset - revlog.start(chainbase)
947 dist = deltalen + offset - revlog.start(chainbase)
929 if revlog._generaldelta:
948 if revlog._generaldelta:
930 deltabase = base
949 deltabase = base
931 else:
950 else:
932 deltabase = chainbase
951 deltabase = chainbase
933 chainlen, compresseddeltalen = revlog._chaininfo(base)
952 chainlen, compresseddeltalen = revlog._chaininfo(base)
934 chainlen += 1
953 chainlen += 1
935 compresseddeltalen += deltalen
954 compresseddeltalen += deltalen
936
955
937 revlog = self.revlog
956 revlog = self.revlog
938 snapshotdepth = None
957 snapshotdepth = None
939 if deltabase == nullrev:
958 if deltabase == nullrev:
940 snapshotdepth = 0
959 snapshotdepth = 0
941 elif revlog._sparserevlog and revlog.issnapshot(deltabase):
960 elif revlog._sparserevlog and revlog.issnapshot(deltabase):
942 # A delta chain should always be one full snapshot,
961 # A delta chain should always be one full snapshot,
943 # zero or more semi-snapshots, and zero or more deltas
962 # zero or more semi-snapshots, and zero or more deltas
944 p1, p2 = revlog.rev(revinfo.p1), revlog.rev(revinfo.p2)
963 p1, p2 = revlog.rev(revinfo.p1), revlog.rev(revinfo.p2)
945 if deltabase not in (p1, p2) and revlog.issnapshot(deltabase):
964 if deltabase not in (p1, p2) and revlog.issnapshot(deltabase):
946 snapshotdepth = len(revlog._deltachain(deltabase)[0])
965 snapshotdepth = len(revlog._deltachain(deltabase)[0])
947
966
948 return _deltainfo(dist, deltalen, (header, data), deltabase,
967 return _deltainfo(dist, deltalen, (header, data), deltabase,
949 chainbase, chainlen, compresseddeltalen,
968 chainbase, chainlen, compresseddeltalen,
950 snapshotdepth)
969 snapshotdepth)
951
970
952 def _fullsnapshotinfo(self, fh, revinfo):
971 def _fullsnapshotinfo(self, fh, revinfo):
953 curr = len(self.revlog)
972 curr = len(self.revlog)
954 rawtext = self.buildtext(revinfo, fh)
973 rawtext = self.buildtext(revinfo, fh)
955 data = self.revlog.compress(rawtext)
974 data = self.revlog.compress(rawtext)
956 compresseddeltalen = deltalen = dist = len(data[1]) + len(data[0])
975 compresseddeltalen = deltalen = dist = len(data[1]) + len(data[0])
957 deltabase = chainbase = curr
976 deltabase = chainbase = curr
958 snapshotdepth = 0
977 snapshotdepth = 0
959 chainlen = 1
978 chainlen = 1
960
979
961 return _deltainfo(dist, deltalen, data, deltabase,
980 return _deltainfo(dist, deltalen, data, deltabase,
962 chainbase, chainlen, compresseddeltalen,
981 chainbase, chainlen, compresseddeltalen,
963 snapshotdepth)
982 snapshotdepth)
964
983
965 def finddeltainfo(self, revinfo, fh):
984 def finddeltainfo(self, revinfo, fh):
966 """Find an acceptable delta against a candidate revision
985 """Find an acceptable delta against a candidate revision
967
986
968 revinfo: information about the revision (instance of _revisioninfo)
987 revinfo: information about the revision (instance of _revisioninfo)
969 fh: file handle to either the .i or the .d revlog file,
988 fh: file handle to either the .i or the .d revlog file,
970 depending on whether it is inlined or not
989 depending on whether it is inlined or not
971
990
972 Returns the first acceptable candidate revision, as ordered by
991 Returns the first acceptable candidate revision, as ordered by
973 _candidategroups
992 _candidategroups
974
993
975 If no suitable deltabase is found, we return delta info for a full
994 If no suitable deltabase is found, we return delta info for a full
976 snapshot.
995 snapshot.
977 """
996 """
978 if not revinfo.textlen:
997 if not revinfo.textlen:
979 return self._fullsnapshotinfo(fh, revinfo)
998 return self._fullsnapshotinfo(fh, revinfo)
980
999
981 # no delta for flag processor revision (see "candelta" for why)
1000 # no delta for flag processor revision (see "candelta" for why)
982 # not calling candelta since only one revision needs test, also to
1001 # not calling candelta since only one revision needs test, also to
983 # avoid overhead fetching flags again.
1002 # avoid overhead fetching flags again.
984 if revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
1003 if revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
985 return self._fullsnapshotinfo(fh, revinfo)
1004 return self._fullsnapshotinfo(fh, revinfo)
986
1005
987 cachedelta = revinfo.cachedelta
1006 cachedelta = revinfo.cachedelta
988 p1 = revinfo.p1
1007 p1 = revinfo.p1
989 p2 = revinfo.p2
1008 p2 = revinfo.p2
990 revlog = self.revlog
1009 revlog = self.revlog
991
1010
992 deltainfo = None
1011 deltainfo = None
993 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
1012 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
994 groups = _candidategroups(self.revlog, revinfo.textlen,
1013 groups = _candidategroups(self.revlog, revinfo.textlen,
995 p1r, p2r, cachedelta)
1014 p1r, p2r, cachedelta)
996 candidaterevs = next(groups)
1015 candidaterevs = next(groups)
997 while candidaterevs is not None:
1016 while candidaterevs is not None:
998 nominateddeltas = []
1017 nominateddeltas = []
999 if deltainfo is not None:
1018 if deltainfo is not None:
1000 # if we already found a good delta,
1019 # if we already found a good delta,
1001 # challenge it against refined candidates
1020 # challenge it against refined candidates
1002 nominateddeltas.append(deltainfo)
1021 nominateddeltas.append(deltainfo)
1003 for candidaterev in candidaterevs:
1022 for candidaterev in candidaterevs:
1004 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
1023 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
1005 if isgooddeltainfo(self.revlog, candidatedelta, revinfo):
1024 if isgooddeltainfo(self.revlog, candidatedelta, revinfo):
1006 nominateddeltas.append(candidatedelta)
1025 nominateddeltas.append(candidatedelta)
1007 if nominateddeltas:
1026 if nominateddeltas:
1008 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
1027 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
1009 if deltainfo is not None:
1028 if deltainfo is not None:
1010 candidaterevs = groups.send(deltainfo.base)
1029 candidaterevs = groups.send(deltainfo.base)
1011 else:
1030 else:
1012 candidaterevs = next(groups)
1031 candidaterevs = next(groups)
1013
1032
1014 if deltainfo is None:
1033 if deltainfo is None:
1015 deltainfo = self._fullsnapshotinfo(fh, revinfo)
1034 deltainfo = self._fullsnapshotinfo(fh, revinfo)
1016 return deltainfo
1035 return deltainfo
General Comments 0
You need to be logged in to leave comments. Login now