##// END OF EJS Templates
delta: exclude base candidate much smaller than the target...
Boris Feld -
r41014:42f59d3f default
parent child Browse files
Show More
@@ -1,981 +1,989 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 )
33 )
34
34
35 # maximum <delta-chain-data>/<revision-text-length> ratio
35 # maximum <delta-chain-data>/<revision-text-length> ratio
36 LIMIT_DELTA2TEXT = 2
36 LIMIT_DELTA2TEXT = 2
37
37
38 class _testrevlog(object):
38 class _testrevlog(object):
39 """minimalist fake revlog to use in doctests"""
39 """minimalist fake revlog to use in doctests"""
40
40
41 def __init__(self, data, density=0.5, mingap=0, snapshot=()):
41 def __init__(self, data, density=0.5, mingap=0, snapshot=()):
42 """data is an list of revision payload boundaries"""
42 """data is an list of revision payload boundaries"""
43 self._data = data
43 self._data = data
44 self._srdensitythreshold = density
44 self._srdensitythreshold = density
45 self._srmingapsize = mingap
45 self._srmingapsize = mingap
46 self._snapshot = set(snapshot)
46 self._snapshot = set(snapshot)
47 self.index = None
47 self.index = None
48
48
49 def start(self, rev):
49 def start(self, rev):
50 if rev == 0:
50 if rev == 0:
51 return 0
51 return 0
52 return self._data[rev - 1]
52 return self._data[rev - 1]
53
53
54 def end(self, rev):
54 def end(self, rev):
55 return self._data[rev]
55 return self._data[rev]
56
56
57 def length(self, rev):
57 def length(self, rev):
58 return self.end(rev) - self.start(rev)
58 return self.end(rev) - self.start(rev)
59
59
60 def __len__(self):
60 def __len__(self):
61 return len(self._data)
61 return len(self._data)
62
62
63 def issnapshot(self, rev):
63 def issnapshot(self, rev):
64 return rev in self._snapshot
64 return rev in self._snapshot
65
65
66 def slicechunk(revlog, revs, targetsize=None):
66 def slicechunk(revlog, revs, targetsize=None):
67 """slice revs to reduce the amount of unrelated data to be read from disk.
67 """slice revs to reduce the amount of unrelated data to be read from disk.
68
68
69 ``revs`` is sliced into groups that should be read in one time.
69 ``revs`` is sliced into groups that should be read in one time.
70 Assume that revs are sorted.
70 Assume that revs are sorted.
71
71
72 The initial chunk is sliced until the overall density (payload/chunks-span
72 The initial chunk is sliced until the overall density (payload/chunks-span
73 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
73 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
74 `revlog._srmingapsize` is skipped.
74 `revlog._srmingapsize` is skipped.
75
75
76 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
76 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
77 For consistency with other slicing choice, this limit won't go lower than
77 For consistency with other slicing choice, this limit won't go lower than
78 `revlog._srmingapsize`.
78 `revlog._srmingapsize`.
79
79
80 If individual revisions chunk are larger than this limit, they will still
80 If individual revisions chunk are larger than this limit, they will still
81 be raised individually.
81 be raised individually.
82
82
83 >>> data = [
83 >>> data = [
84 ... 5, #00 (5)
84 ... 5, #00 (5)
85 ... 10, #01 (5)
85 ... 10, #01 (5)
86 ... 12, #02 (2)
86 ... 12, #02 (2)
87 ... 12, #03 (empty)
87 ... 12, #03 (empty)
88 ... 27, #04 (15)
88 ... 27, #04 (15)
89 ... 31, #05 (4)
89 ... 31, #05 (4)
90 ... 31, #06 (empty)
90 ... 31, #06 (empty)
91 ... 42, #07 (11)
91 ... 42, #07 (11)
92 ... 47, #08 (5)
92 ... 47, #08 (5)
93 ... 47, #09 (empty)
93 ... 47, #09 (empty)
94 ... 48, #10 (1)
94 ... 48, #10 (1)
95 ... 51, #11 (3)
95 ... 51, #11 (3)
96 ... 74, #12 (23)
96 ... 74, #12 (23)
97 ... 85, #13 (11)
97 ... 85, #13 (11)
98 ... 86, #14 (1)
98 ... 86, #14 (1)
99 ... 91, #15 (5)
99 ... 91, #15 (5)
100 ... ]
100 ... ]
101 >>> revlog = _testrevlog(data, snapshot=range(16))
101 >>> revlog = _testrevlog(data, snapshot=range(16))
102
102
103 >>> list(slicechunk(revlog, list(range(16))))
103 >>> list(slicechunk(revlog, list(range(16))))
104 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
104 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
105 >>> list(slicechunk(revlog, [0, 15]))
105 >>> list(slicechunk(revlog, [0, 15]))
106 [[0], [15]]
106 [[0], [15]]
107 >>> list(slicechunk(revlog, [0, 11, 15]))
107 >>> list(slicechunk(revlog, [0, 11, 15]))
108 [[0], [11], [15]]
108 [[0], [11], [15]]
109 >>> list(slicechunk(revlog, [0, 11, 13, 15]))
109 >>> list(slicechunk(revlog, [0, 11, 13, 15]))
110 [[0], [11, 13, 15]]
110 [[0], [11, 13, 15]]
111 >>> list(slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
111 >>> list(slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
112 [[1, 2], [5, 8, 10, 11], [14]]
112 [[1, 2], [5, 8, 10, 11], [14]]
113
113
114 Slicing with a maximum chunk size
114 Slicing with a maximum chunk size
115 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
115 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
116 [[0], [11], [13], [15]]
116 [[0], [11], [13], [15]]
117 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
117 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
118 [[0], [11], [13, 15]]
118 [[0], [11], [13, 15]]
119 """
119 """
120 if targetsize is not None:
120 if targetsize is not None:
121 targetsize = max(targetsize, revlog._srmingapsize)
121 targetsize = max(targetsize, revlog._srmingapsize)
122 # targetsize should not be specified when evaluating delta candidates:
122 # targetsize should not be specified when evaluating delta candidates:
123 # * targetsize is used to ensure we stay within specification when reading,
123 # * targetsize is used to ensure we stay within specification when reading,
124 densityslicing = getattr(revlog.index, 'slicechunktodensity', None)
124 densityslicing = getattr(revlog.index, 'slicechunktodensity', None)
125 if densityslicing is None:
125 if densityslicing is None:
126 densityslicing = lambda x, y, z: _slicechunktodensity(revlog, x, y, z)
126 densityslicing = lambda x, y, z: _slicechunktodensity(revlog, x, y, z)
127 for chunk in densityslicing(revs,
127 for chunk in densityslicing(revs,
128 revlog._srdensitythreshold,
128 revlog._srdensitythreshold,
129 revlog._srmingapsize):
129 revlog._srmingapsize):
130 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
130 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
131 yield subchunk
131 yield subchunk
132
132
133 def _slicechunktosize(revlog, revs, targetsize=None):
133 def _slicechunktosize(revlog, revs, targetsize=None):
134 """slice revs to match the target size
134 """slice revs to match the target size
135
135
136 This is intended to be used on chunk that density slicing selected by that
136 This is intended to be used on chunk that density slicing selected by that
137 are still too large compared to the read garantee of revlog. This might
137 are still too large compared to the read garantee of revlog. This might
138 happens when "minimal gap size" interrupted the slicing or when chain are
138 happens when "minimal gap size" interrupted the slicing or when chain are
139 built in a way that create large blocks next to each other.
139 built in a way that create large blocks next to each other.
140
140
141 >>> data = [
141 >>> data = [
142 ... 3, #0 (3)
142 ... 3, #0 (3)
143 ... 5, #1 (2)
143 ... 5, #1 (2)
144 ... 6, #2 (1)
144 ... 6, #2 (1)
145 ... 8, #3 (2)
145 ... 8, #3 (2)
146 ... 8, #4 (empty)
146 ... 8, #4 (empty)
147 ... 11, #5 (3)
147 ... 11, #5 (3)
148 ... 12, #6 (1)
148 ... 12, #6 (1)
149 ... 13, #7 (1)
149 ... 13, #7 (1)
150 ... 14, #8 (1)
150 ... 14, #8 (1)
151 ... ]
151 ... ]
152
152
153 == All snapshots cases ==
153 == All snapshots cases ==
154 >>> revlog = _testrevlog(data, snapshot=range(9))
154 >>> revlog = _testrevlog(data, snapshot=range(9))
155
155
156 Cases where chunk is already small enough
156 Cases where chunk is already small enough
157 >>> list(_slicechunktosize(revlog, [0], 3))
157 >>> list(_slicechunktosize(revlog, [0], 3))
158 [[0]]
158 [[0]]
159 >>> list(_slicechunktosize(revlog, [6, 7], 3))
159 >>> list(_slicechunktosize(revlog, [6, 7], 3))
160 [[6, 7]]
160 [[6, 7]]
161 >>> list(_slicechunktosize(revlog, [0], None))
161 >>> list(_slicechunktosize(revlog, [0], None))
162 [[0]]
162 [[0]]
163 >>> list(_slicechunktosize(revlog, [6, 7], None))
163 >>> list(_slicechunktosize(revlog, [6, 7], None))
164 [[6, 7]]
164 [[6, 7]]
165
165
166 cases where we need actual slicing
166 cases where we need actual slicing
167 >>> list(_slicechunktosize(revlog, [0, 1], 3))
167 >>> list(_slicechunktosize(revlog, [0, 1], 3))
168 [[0], [1]]
168 [[0], [1]]
169 >>> list(_slicechunktosize(revlog, [1, 3], 3))
169 >>> list(_slicechunktosize(revlog, [1, 3], 3))
170 [[1], [3]]
170 [[1], [3]]
171 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
171 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
172 [[1, 2], [3]]
172 [[1, 2], [3]]
173 >>> list(_slicechunktosize(revlog, [3, 5], 3))
173 >>> list(_slicechunktosize(revlog, [3, 5], 3))
174 [[3], [5]]
174 [[3], [5]]
175 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
175 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
176 [[3], [5]]
176 [[3], [5]]
177 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
177 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
178 [[5], [6, 7, 8]]
178 [[5], [6, 7, 8]]
179 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
179 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
180 [[0], [1, 2], [3], [5], [6, 7, 8]]
180 [[0], [1, 2], [3], [5], [6, 7, 8]]
181
181
182 Case with too large individual chunk (must return valid chunk)
182 Case with too large individual chunk (must return valid chunk)
183 >>> list(_slicechunktosize(revlog, [0, 1], 2))
183 >>> list(_slicechunktosize(revlog, [0, 1], 2))
184 [[0], [1]]
184 [[0], [1]]
185 >>> list(_slicechunktosize(revlog, [1, 3], 1))
185 >>> list(_slicechunktosize(revlog, [1, 3], 1))
186 [[1], [3]]
186 [[1], [3]]
187 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
187 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
188 [[3], [5]]
188 [[3], [5]]
189
189
190 == No Snapshot cases ==
190 == No Snapshot cases ==
191 >>> revlog = _testrevlog(data)
191 >>> revlog = _testrevlog(data)
192
192
193 Cases where chunk is already small enough
193 Cases where chunk is already small enough
194 >>> list(_slicechunktosize(revlog, [0], 3))
194 >>> list(_slicechunktosize(revlog, [0], 3))
195 [[0]]
195 [[0]]
196 >>> list(_slicechunktosize(revlog, [6, 7], 3))
196 >>> list(_slicechunktosize(revlog, [6, 7], 3))
197 [[6, 7]]
197 [[6, 7]]
198 >>> list(_slicechunktosize(revlog, [0], None))
198 >>> list(_slicechunktosize(revlog, [0], None))
199 [[0]]
199 [[0]]
200 >>> list(_slicechunktosize(revlog, [6, 7], None))
200 >>> list(_slicechunktosize(revlog, [6, 7], None))
201 [[6, 7]]
201 [[6, 7]]
202
202
203 cases where we need actual slicing
203 cases where we need actual slicing
204 >>> list(_slicechunktosize(revlog, [0, 1], 3))
204 >>> list(_slicechunktosize(revlog, [0, 1], 3))
205 [[0], [1]]
205 [[0], [1]]
206 >>> list(_slicechunktosize(revlog, [1, 3], 3))
206 >>> list(_slicechunktosize(revlog, [1, 3], 3))
207 [[1], [3]]
207 [[1], [3]]
208 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
208 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
209 [[1], [2, 3]]
209 [[1], [2, 3]]
210 >>> list(_slicechunktosize(revlog, [3, 5], 3))
210 >>> list(_slicechunktosize(revlog, [3, 5], 3))
211 [[3], [5]]
211 [[3], [5]]
212 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
212 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
213 [[3], [4, 5]]
213 [[3], [4, 5]]
214 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
214 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
215 [[5], [6, 7, 8]]
215 [[5], [6, 7, 8]]
216 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
216 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
217 [[0], [1, 2], [3], [5], [6, 7, 8]]
217 [[0], [1, 2], [3], [5], [6, 7, 8]]
218
218
219 Case with too large individual chunk (must return valid chunk)
219 Case with too large individual chunk (must return valid chunk)
220 >>> list(_slicechunktosize(revlog, [0, 1], 2))
220 >>> list(_slicechunktosize(revlog, [0, 1], 2))
221 [[0], [1]]
221 [[0], [1]]
222 >>> list(_slicechunktosize(revlog, [1, 3], 1))
222 >>> list(_slicechunktosize(revlog, [1, 3], 1))
223 [[1], [3]]
223 [[1], [3]]
224 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
224 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
225 [[3], [5]]
225 [[3], [5]]
226
226
227 == mixed case ==
227 == mixed case ==
228 >>> revlog = _testrevlog(data, snapshot=[0, 1, 2])
228 >>> revlog = _testrevlog(data, snapshot=[0, 1, 2])
229 >>> list(_slicechunktosize(revlog, list(range(9)), 5))
229 >>> list(_slicechunktosize(revlog, list(range(9)), 5))
230 [[0, 1], [2], [3, 4, 5], [6, 7, 8]]
230 [[0, 1], [2], [3, 4, 5], [6, 7, 8]]
231 """
231 """
232 assert targetsize is None or 0 <= targetsize
232 assert targetsize is None or 0 <= targetsize
233 startdata = revlog.start(revs[0])
233 startdata = revlog.start(revs[0])
234 enddata = revlog.end(revs[-1])
234 enddata = revlog.end(revs[-1])
235 fullspan = enddata - startdata
235 fullspan = enddata - startdata
236 if targetsize is None or fullspan <= targetsize:
236 if targetsize is None or fullspan <= targetsize:
237 yield revs
237 yield revs
238 return
238 return
239
239
240 startrevidx = 0
240 startrevidx = 0
241 endrevidx = 1
241 endrevidx = 1
242 iterrevs = enumerate(revs)
242 iterrevs = enumerate(revs)
243 next(iterrevs) # skip first rev.
243 next(iterrevs) # skip first rev.
244 # first step: get snapshots out of the way
244 # first step: get snapshots out of the way
245 for idx, r in iterrevs:
245 for idx, r in iterrevs:
246 span = revlog.end(r) - startdata
246 span = revlog.end(r) - startdata
247 snapshot = revlog.issnapshot(r)
247 snapshot = revlog.issnapshot(r)
248 if span <= targetsize and snapshot:
248 if span <= targetsize and snapshot:
249 endrevidx = idx + 1
249 endrevidx = idx + 1
250 else:
250 else:
251 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
251 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
252 if chunk:
252 if chunk:
253 yield chunk
253 yield chunk
254 startrevidx = idx
254 startrevidx = idx
255 startdata = revlog.start(r)
255 startdata = revlog.start(r)
256 endrevidx = idx + 1
256 endrevidx = idx + 1
257 if not snapshot:
257 if not snapshot:
258 break
258 break
259
259
260 # for the others, we use binary slicing to quickly converge toward valid
260 # for the others, we use binary slicing to quickly converge toward valid
261 # chunks (otherwise, we might end up looking for start/end of many
261 # chunks (otherwise, we might end up looking for start/end of many
262 # revisions). This logic is not looking for the perfect slicing point, it
262 # revisions). This logic is not looking for the perfect slicing point, it
263 # focuses on quickly converging toward valid chunks.
263 # focuses on quickly converging toward valid chunks.
264 nbitem = len(revs)
264 nbitem = len(revs)
265 while (enddata - startdata) > targetsize:
265 while (enddata - startdata) > targetsize:
266 endrevidx = nbitem
266 endrevidx = nbitem
267 if nbitem - startrevidx <= 1:
267 if nbitem - startrevidx <= 1:
268 break # protect against individual chunk larger than limit
268 break # protect against individual chunk larger than limit
269 localenddata = revlog.end(revs[endrevidx - 1])
269 localenddata = revlog.end(revs[endrevidx - 1])
270 span = localenddata - startdata
270 span = localenddata - startdata
271 while span > targetsize:
271 while span > targetsize:
272 if endrevidx - startrevidx <= 1:
272 if endrevidx - startrevidx <= 1:
273 break # protect against individual chunk larger than limit
273 break # protect against individual chunk larger than limit
274 endrevidx -= (endrevidx - startrevidx) // 2
274 endrevidx -= (endrevidx - startrevidx) // 2
275 localenddata = revlog.end(revs[endrevidx - 1])
275 localenddata = revlog.end(revs[endrevidx - 1])
276 span = localenddata - startdata
276 span = localenddata - startdata
277 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
277 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx)
278 if chunk:
278 if chunk:
279 yield chunk
279 yield chunk
280 startrevidx = endrevidx
280 startrevidx = endrevidx
281 startdata = revlog.start(revs[startrevidx])
281 startdata = revlog.start(revs[startrevidx])
282
282
283 chunk = _trimchunk(revlog, revs, startrevidx)
283 chunk = _trimchunk(revlog, revs, startrevidx)
284 if chunk:
284 if chunk:
285 yield chunk
285 yield chunk
286
286
287 def _slicechunktodensity(revlog, revs, targetdensity=0.5,
287 def _slicechunktodensity(revlog, revs, targetdensity=0.5,
288 mingapsize=0):
288 mingapsize=0):
289 """slice revs to reduce the amount of unrelated data to be read from disk.
289 """slice revs to reduce the amount of unrelated data to be read from disk.
290
290
291 ``revs`` is sliced into groups that should be read in one time.
291 ``revs`` is sliced into groups that should be read in one time.
292 Assume that revs are sorted.
292 Assume that revs are sorted.
293
293
294 The initial chunk is sliced until the overall density (payload/chunks-span
294 The initial chunk is sliced until the overall density (payload/chunks-span
295 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
295 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
296 skipped.
296 skipped.
297
297
298 >>> revlog = _testrevlog([
298 >>> revlog = _testrevlog([
299 ... 5, #00 (5)
299 ... 5, #00 (5)
300 ... 10, #01 (5)
300 ... 10, #01 (5)
301 ... 12, #02 (2)
301 ... 12, #02 (2)
302 ... 12, #03 (empty)
302 ... 12, #03 (empty)
303 ... 27, #04 (15)
303 ... 27, #04 (15)
304 ... 31, #05 (4)
304 ... 31, #05 (4)
305 ... 31, #06 (empty)
305 ... 31, #06 (empty)
306 ... 42, #07 (11)
306 ... 42, #07 (11)
307 ... 47, #08 (5)
307 ... 47, #08 (5)
308 ... 47, #09 (empty)
308 ... 47, #09 (empty)
309 ... 48, #10 (1)
309 ... 48, #10 (1)
310 ... 51, #11 (3)
310 ... 51, #11 (3)
311 ... 74, #12 (23)
311 ... 74, #12 (23)
312 ... 85, #13 (11)
312 ... 85, #13 (11)
313 ... 86, #14 (1)
313 ... 86, #14 (1)
314 ... 91, #15 (5)
314 ... 91, #15 (5)
315 ... ])
315 ... ])
316
316
317 >>> list(_slicechunktodensity(revlog, list(range(16))))
317 >>> list(_slicechunktodensity(revlog, list(range(16))))
318 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
318 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
319 >>> list(_slicechunktodensity(revlog, [0, 15]))
319 >>> list(_slicechunktodensity(revlog, [0, 15]))
320 [[0], [15]]
320 [[0], [15]]
321 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
321 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
322 [[0], [11], [15]]
322 [[0], [11], [15]]
323 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
323 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
324 [[0], [11, 13, 15]]
324 [[0], [11, 13, 15]]
325 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
325 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
326 [[1, 2], [5, 8, 10, 11], [14]]
326 [[1, 2], [5, 8, 10, 11], [14]]
327 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
327 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
328 ... mingapsize=20))
328 ... mingapsize=20))
329 [[1, 2, 3, 5, 8, 10, 11], [14]]
329 [[1, 2, 3, 5, 8, 10, 11], [14]]
330 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
330 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
331 ... targetdensity=0.95))
331 ... targetdensity=0.95))
332 [[1, 2], [5], [8, 10, 11], [14]]
332 [[1, 2], [5], [8, 10, 11], [14]]
333 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
333 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
334 ... targetdensity=0.95, mingapsize=12))
334 ... targetdensity=0.95, mingapsize=12))
335 [[1, 2], [5, 8, 10, 11], [14]]
335 [[1, 2], [5, 8, 10, 11], [14]]
336 """
336 """
337 start = revlog.start
337 start = revlog.start
338 length = revlog.length
338 length = revlog.length
339
339
340 if len(revs) <= 1:
340 if len(revs) <= 1:
341 yield revs
341 yield revs
342 return
342 return
343
343
344 deltachainspan = segmentspan(revlog, revs)
344 deltachainspan = segmentspan(revlog, revs)
345
345
346 if deltachainspan < mingapsize:
346 if deltachainspan < mingapsize:
347 yield revs
347 yield revs
348 return
348 return
349
349
350 readdata = deltachainspan
350 readdata = deltachainspan
351 chainpayload = sum(length(r) for r in revs)
351 chainpayload = sum(length(r) for r in revs)
352
352
353 if deltachainspan:
353 if deltachainspan:
354 density = chainpayload / float(deltachainspan)
354 density = chainpayload / float(deltachainspan)
355 else:
355 else:
356 density = 1.0
356 density = 1.0
357
357
358 if density >= targetdensity:
358 if density >= targetdensity:
359 yield revs
359 yield revs
360 return
360 return
361
361
362 # Store the gaps in a heap to have them sorted by decreasing size
362 # Store the gaps in a heap to have them sorted by decreasing size
363 gaps = []
363 gaps = []
364 prevend = None
364 prevend = None
365 for i, rev in enumerate(revs):
365 for i, rev in enumerate(revs):
366 revstart = start(rev)
366 revstart = start(rev)
367 revlen = length(rev)
367 revlen = length(rev)
368
368
369 # Skip empty revisions to form larger holes
369 # Skip empty revisions to form larger holes
370 if revlen == 0:
370 if revlen == 0:
371 continue
371 continue
372
372
373 if prevend is not None:
373 if prevend is not None:
374 gapsize = revstart - prevend
374 gapsize = revstart - prevend
375 # only consider holes that are large enough
375 # only consider holes that are large enough
376 if gapsize > mingapsize:
376 if gapsize > mingapsize:
377 gaps.append((gapsize, i))
377 gaps.append((gapsize, i))
378
378
379 prevend = revstart + revlen
379 prevend = revstart + revlen
380 # sort the gaps to pop them from largest to small
380 # sort the gaps to pop them from largest to small
381 gaps.sort()
381 gaps.sort()
382
382
383 # Collect the indices of the largest holes until the density is acceptable
383 # Collect the indices of the largest holes until the density is acceptable
384 selected = []
384 selected = []
385 while gaps and density < targetdensity:
385 while gaps and density < targetdensity:
386 gapsize, gapidx = gaps.pop()
386 gapsize, gapidx = gaps.pop()
387
387
388 selected.append(gapidx)
388 selected.append(gapidx)
389
389
390 # the gap sizes are stored as negatives to be sorted decreasingly
390 # the gap sizes are stored as negatives to be sorted decreasingly
391 # by the heap
391 # by the heap
392 readdata -= gapsize
392 readdata -= gapsize
393 if readdata > 0:
393 if readdata > 0:
394 density = chainpayload / float(readdata)
394 density = chainpayload / float(readdata)
395 else:
395 else:
396 density = 1.0
396 density = 1.0
397 selected.sort()
397 selected.sort()
398
398
399 # Cut the revs at collected indices
399 # Cut the revs at collected indices
400 previdx = 0
400 previdx = 0
401 for idx in selected:
401 for idx in selected:
402
402
403 chunk = _trimchunk(revlog, revs, previdx, idx)
403 chunk = _trimchunk(revlog, revs, previdx, idx)
404 if chunk:
404 if chunk:
405 yield chunk
405 yield chunk
406
406
407 previdx = idx
407 previdx = idx
408
408
409 chunk = _trimchunk(revlog, revs, previdx)
409 chunk = _trimchunk(revlog, revs, previdx)
410 if chunk:
410 if chunk:
411 yield chunk
411 yield chunk
412
412
413 def _trimchunk(revlog, revs, startidx, endidx=None):
413 def _trimchunk(revlog, revs, startidx, endidx=None):
414 """returns revs[startidx:endidx] without empty trailing revs
414 """returns revs[startidx:endidx] without empty trailing revs
415
415
416 Doctest Setup
416 Doctest Setup
417 >>> revlog = _testrevlog([
417 >>> revlog = _testrevlog([
418 ... 5, #0
418 ... 5, #0
419 ... 10, #1
419 ... 10, #1
420 ... 12, #2
420 ... 12, #2
421 ... 12, #3 (empty)
421 ... 12, #3 (empty)
422 ... 17, #4
422 ... 17, #4
423 ... 21, #5
423 ... 21, #5
424 ... 21, #6 (empty)
424 ... 21, #6 (empty)
425 ... ])
425 ... ])
426
426
427 Contiguous cases:
427 Contiguous cases:
428 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
428 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
429 [0, 1, 2, 3, 4, 5]
429 [0, 1, 2, 3, 4, 5]
430 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
430 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
431 [0, 1, 2, 3, 4]
431 [0, 1, 2, 3, 4]
432 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
432 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
433 [0, 1, 2]
433 [0, 1, 2]
434 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
434 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
435 [2]
435 [2]
436 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
436 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
437 [3, 4, 5]
437 [3, 4, 5]
438 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
438 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
439 [3, 4]
439 [3, 4]
440
440
441 Discontiguous cases:
441 Discontiguous cases:
442 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
442 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
443 [1, 3, 5]
443 [1, 3, 5]
444 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
444 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
445 [1]
445 [1]
446 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
446 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
447 [3, 5]
447 [3, 5]
448 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
448 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
449 [3, 5]
449 [3, 5]
450 """
450 """
451 length = revlog.length
451 length = revlog.length
452
452
453 if endidx is None:
453 if endidx is None:
454 endidx = len(revs)
454 endidx = len(revs)
455
455
456 # If we have a non-emtpy delta candidate, there are nothing to trim
456 # If we have a non-emtpy delta candidate, there are nothing to trim
457 if revs[endidx - 1] < len(revlog):
457 if revs[endidx - 1] < len(revlog):
458 # Trim empty revs at the end, except the very first revision of a chain
458 # Trim empty revs at the end, except the very first revision of a chain
459 while (endidx > 1
459 while (endidx > 1
460 and endidx > startidx
460 and endidx > startidx
461 and length(revs[endidx - 1]) == 0):
461 and length(revs[endidx - 1]) == 0):
462 endidx -= 1
462 endidx -= 1
463
463
464 return revs[startidx:endidx]
464 return revs[startidx:endidx]
465
465
466 def segmentspan(revlog, revs):
466 def segmentspan(revlog, revs):
467 """Get the byte span of a segment of revisions
467 """Get the byte span of a segment of revisions
468
468
469 revs is a sorted array of revision numbers
469 revs is a sorted array of revision numbers
470
470
471 >>> revlog = _testrevlog([
471 >>> revlog = _testrevlog([
472 ... 5, #0
472 ... 5, #0
473 ... 10, #1
473 ... 10, #1
474 ... 12, #2
474 ... 12, #2
475 ... 12, #3 (empty)
475 ... 12, #3 (empty)
476 ... 17, #4
476 ... 17, #4
477 ... ])
477 ... ])
478
478
479 >>> segmentspan(revlog, [0, 1, 2, 3, 4])
479 >>> segmentspan(revlog, [0, 1, 2, 3, 4])
480 17
480 17
481 >>> segmentspan(revlog, [0, 4])
481 >>> segmentspan(revlog, [0, 4])
482 17
482 17
483 >>> segmentspan(revlog, [3, 4])
483 >>> segmentspan(revlog, [3, 4])
484 5
484 5
485 >>> segmentspan(revlog, [1, 2, 3,])
485 >>> segmentspan(revlog, [1, 2, 3,])
486 7
486 7
487 >>> segmentspan(revlog, [1, 3])
487 >>> segmentspan(revlog, [1, 3])
488 7
488 7
489 """
489 """
490 if not revs:
490 if not revs:
491 return 0
491 return 0
492 end = revlog.end(revs[-1])
492 end = revlog.end(revs[-1])
493 return end - revlog.start(revs[0])
493 return end - revlog.start(revs[0])
494
494
495 def _textfromdelta(fh, revlog, baserev, delta, p1, p2, flags, expectednode):
495 def _textfromdelta(fh, revlog, baserev, delta, p1, p2, flags, expectednode):
496 """build full text from a (base, delta) pair and other metadata"""
496 """build full text from a (base, delta) pair and other metadata"""
497 # special case deltas which replace entire base; no need to decode
497 # special case deltas which replace entire base; no need to decode
498 # base revision. this neatly avoids censored bases, which throw when
498 # base revision. this neatly avoids censored bases, which throw when
499 # they're decoded.
499 # they're decoded.
500 hlen = struct.calcsize(">lll")
500 hlen = struct.calcsize(">lll")
501 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
501 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
502 len(delta) - hlen):
502 len(delta) - hlen):
503 fulltext = delta[hlen:]
503 fulltext = delta[hlen:]
504 else:
504 else:
505 # deltabase is rawtext before changed by flag processors, which is
505 # deltabase is rawtext before changed by flag processors, which is
506 # equivalent to non-raw text
506 # equivalent to non-raw text
507 basetext = revlog.revision(baserev, _df=fh, raw=False)
507 basetext = revlog.revision(baserev, _df=fh, raw=False)
508 fulltext = mdiff.patch(basetext, delta)
508 fulltext = mdiff.patch(basetext, delta)
509
509
510 try:
510 try:
511 res = revlog._processflags(fulltext, flags, 'read', raw=True)
511 res = revlog._processflags(fulltext, flags, 'read', raw=True)
512 fulltext, validatehash = res
512 fulltext, validatehash = res
513 if validatehash:
513 if validatehash:
514 revlog.checkhash(fulltext, expectednode, p1=p1, p2=p2)
514 revlog.checkhash(fulltext, expectednode, p1=p1, p2=p2)
515 if flags & REVIDX_ISCENSORED:
515 if flags & REVIDX_ISCENSORED:
516 raise error.StorageError(_('node %s is not censored') %
516 raise error.StorageError(_('node %s is not censored') %
517 expectednode)
517 expectednode)
518 except error.CensoredNodeError:
518 except error.CensoredNodeError:
519 # must pass the censored index flag to add censored revisions
519 # must pass the censored index flag to add censored revisions
520 if not flags & REVIDX_ISCENSORED:
520 if not flags & REVIDX_ISCENSORED:
521 raise
521 raise
522 return fulltext
522 return fulltext
523
523
524 @attr.s(slots=True, frozen=True)
524 @attr.s(slots=True, frozen=True)
525 class _deltainfo(object):
525 class _deltainfo(object):
526 distance = attr.ib()
526 distance = attr.ib()
527 deltalen = attr.ib()
527 deltalen = attr.ib()
528 data = attr.ib()
528 data = attr.ib()
529 base = attr.ib()
529 base = attr.ib()
530 chainbase = attr.ib()
530 chainbase = attr.ib()
531 chainlen = attr.ib()
531 chainlen = attr.ib()
532 compresseddeltalen = attr.ib()
532 compresseddeltalen = attr.ib()
533 snapshotdepth = attr.ib()
533 snapshotdepth = attr.ib()
534
534
535 def isgooddeltainfo(revlog, deltainfo, revinfo):
535 def isgooddeltainfo(revlog, deltainfo, revinfo):
536 """Returns True if the given delta is good. Good means that it is within
536 """Returns True if the given delta is good. Good means that it is within
537 the disk span, disk size, and chain length bounds that we know to be
537 the disk span, disk size, and chain length bounds that we know to be
538 performant."""
538 performant."""
539 if deltainfo is None:
539 if deltainfo is None:
540 return False
540 return False
541
541
542 # - 'deltainfo.distance' is the distance from the base revision --
542 # - 'deltainfo.distance' is the distance from the base revision --
543 # bounding it limits the amount of I/O we need to do.
543 # bounding it limits the amount of I/O we need to do.
544 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
544 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
545 # deltas we need to apply -- bounding it limits the amount of CPU
545 # deltas we need to apply -- bounding it limits the amount of CPU
546 # we consume.
546 # we consume.
547
547
548 textlen = revinfo.textlen
548 textlen = revinfo.textlen
549 defaultmax = textlen * 4
549 defaultmax = textlen * 4
550 maxdist = revlog._maxdeltachainspan
550 maxdist = revlog._maxdeltachainspan
551 if not maxdist:
551 if not maxdist:
552 maxdist = deltainfo.distance # ensure the conditional pass
552 maxdist = deltainfo.distance # ensure the conditional pass
553 maxdist = max(maxdist, defaultmax)
553 maxdist = max(maxdist, defaultmax)
554
554
555 # Bad delta from read span:
555 # Bad delta from read span:
556 #
556 #
557 # If the span of data read is larger than the maximum allowed.
557 # If the span of data read is larger than the maximum allowed.
558 #
558 #
559 # In the sparse-revlog case, we rely on the associated "sparse reading"
559 # In the sparse-revlog case, we rely on the associated "sparse reading"
560 # to avoid issue related to the span of data. In theory, it would be
560 # to avoid issue related to the span of data. In theory, it would be
561 # possible to build pathological revlog where delta pattern would lead
561 # possible to build pathological revlog where delta pattern would lead
562 # to too many reads. However, they do not happen in practice at all. So
562 # to too many reads. However, they do not happen in practice at all. So
563 # we skip the span check entirely.
563 # we skip the span check entirely.
564 if not revlog._sparserevlog and maxdist < deltainfo.distance:
564 if not revlog._sparserevlog and maxdist < deltainfo.distance:
565 return False
565 return False
566
566
567 # Bad delta from new delta size:
567 # Bad delta from new delta size:
568 #
568 #
569 # If the delta size is larger than the target text, storing the
569 # If the delta size is larger than the target text, storing the
570 # delta will be inefficient.
570 # delta will be inefficient.
571 if textlen < deltainfo.deltalen:
571 if textlen < deltainfo.deltalen:
572 return False
572 return False
573
573
574 # Bad delta from cumulated payload size:
574 # Bad delta from cumulated payload size:
575 #
575 #
576 # If the sum of delta get larger than K * target text length.
576 # If the sum of delta get larger than K * target text length.
577 if textlen * LIMIT_DELTA2TEXT < deltainfo.compresseddeltalen:
577 if textlen * LIMIT_DELTA2TEXT < deltainfo.compresseddeltalen:
578 return False
578 return False
579
579
580 # Bad delta from chain length:
580 # Bad delta from chain length:
581 #
581 #
582 # If the number of delta in the chain gets too high.
582 # If the number of delta in the chain gets too high.
583 if (revlog._maxchainlen
583 if (revlog._maxchainlen
584 and revlog._maxchainlen < deltainfo.chainlen):
584 and revlog._maxchainlen < deltainfo.chainlen):
585 return False
585 return False
586
586
587 # bad delta from intermediate snapshot size limit
587 # bad delta from intermediate snapshot size limit
588 #
588 #
589 # If an intermediate snapshot size is higher than the limit. The
589 # If an intermediate snapshot size is higher than the limit. The
590 # limit exist to prevent endless chain of intermediate delta to be
590 # limit exist to prevent endless chain of intermediate delta to be
591 # created.
591 # created.
592 if (deltainfo.snapshotdepth is not None and
592 if (deltainfo.snapshotdepth is not None and
593 (textlen >> deltainfo.snapshotdepth) < deltainfo.deltalen):
593 (textlen >> deltainfo.snapshotdepth) < deltainfo.deltalen):
594 return False
594 return False
595
595
596 # bad delta if new intermediate snapshot is larger than the previous
596 # bad delta if new intermediate snapshot is larger than the previous
597 # snapshot
597 # snapshot
598 if (deltainfo.snapshotdepth
598 if (deltainfo.snapshotdepth
599 and revlog.length(deltainfo.base) < deltainfo.deltalen):
599 and revlog.length(deltainfo.base) < deltainfo.deltalen):
600 return False
600 return False
601
601
602 return True
602 return True
603
603
604 # If a revision's full text is that much bigger than a base candidate full
605 # text's, it is very unlikely that it will produce a valid delta. We no longer
606 # consider these candidates.
607 LIMIT_BASE2TEXT = 50
608
604 def _candidategroups(revlog, textlen, p1, p2, cachedelta):
609 def _candidategroups(revlog, textlen, p1, p2, cachedelta):
605 """Provides group of revision to be tested as delta base
610 """Provides group of revision to be tested as delta base
606
611
607 This top level function focus on emitting groups with unique and worthwhile
612 This top level function focus on emitting groups with unique and worthwhile
608 content. See _raw_candidate_groups for details about the group order.
613 content. See _raw_candidate_groups for details about the group order.
609 """
614 """
610 # should we try to build a delta?
615 # should we try to build a delta?
611 if not (len(revlog) and revlog._storedeltachains):
616 if not (len(revlog) and revlog._storedeltachains):
612 yield None
617 yield None
613 return
618 return
614
619
615 deltalength = revlog.length
620 deltalength = revlog.length
616 deltaparent = revlog.deltaparent
621 deltaparent = revlog.deltaparent
622 sparse = revlog._sparserevlog
617 good = None
623 good = None
618
624
619 deltas_limit = textlen * LIMIT_DELTA2TEXT
625 deltas_limit = textlen * LIMIT_DELTA2TEXT
620
626
621 tested = set([nullrev])
627 tested = set([nullrev])
622 candidates = _refinedgroups(revlog, p1, p2, cachedelta)
628 candidates = _refinedgroups(revlog, p1, p2, cachedelta)
623 while True:
629 while True:
624 temptative = candidates.send(good)
630 temptative = candidates.send(good)
625 if temptative is None:
631 if temptative is None:
626 break
632 break
627 group = []
633 group = []
628 for rev in temptative:
634 for rev in temptative:
629 # skip over empty delta (no need to include them in a chain)
635 # skip over empty delta (no need to include them in a chain)
630 while (revlog._generaldelta
636 while (revlog._generaldelta
631 and not (rev == nullrev
637 and not (rev == nullrev
632 or rev in tested
638 or rev in tested
633 or deltalength(rev))):
639 or deltalength(rev))):
634 tested.add(rev)
640 tested.add(rev)
635 rev = deltaparent(rev)
641 rev = deltaparent(rev)
636 # no need to try a delta against nullrev, this will be done as a
642 # no need to try a delta against nullrev, this will be done as a
637 # last resort.
643 # last resort.
638 if rev == nullrev:
644 if rev == nullrev:
639 continue
645 continue
640 # filter out revision we tested already
646 # filter out revision we tested already
641 if rev in tested:
647 if rev in tested:
642 continue
648 continue
643 tested.add(rev)
649 tested.add(rev)
644 # filter out delta base that will never produce good delta
650 # filter out delta base that will never produce good delta
645 if deltas_limit < revlog.length(rev):
651 if deltas_limit < revlog.length(rev):
646 continue
652 continue
653 if sparse and revlog.rawsize(rev) < (textlen // LIMIT_BASE2TEXT):
654 continue
647 # no delta for rawtext-changing revs (see "candelta" for why)
655 # no delta for rawtext-changing revs (see "candelta" for why)
648 if revlog.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
656 if revlog.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
649 continue
657 continue
650 group.append(rev)
658 group.append(rev)
651 if group:
659 if group:
652 # XXX: in the sparse revlog case, group can become large,
660 # XXX: in the sparse revlog case, group can become large,
653 # impacting performances. Some bounding or slicing mecanism
661 # impacting performances. Some bounding or slicing mecanism
654 # would help to reduce this impact.
662 # would help to reduce this impact.
655 good = yield tuple(group)
663 good = yield tuple(group)
656 yield None
664 yield None
657
665
658 def _findsnapshots(revlog, cache, start_rev):
666 def _findsnapshots(revlog, cache, start_rev):
659 """find snapshot from start_rev to tip"""
667 """find snapshot from start_rev to tip"""
660 deltaparent = revlog.deltaparent
668 deltaparent = revlog.deltaparent
661 issnapshot = revlog.issnapshot
669 issnapshot = revlog.issnapshot
662 for rev in revlog.revs(start_rev):
670 for rev in revlog.revs(start_rev):
663 if issnapshot(rev):
671 if issnapshot(rev):
664 cache[deltaparent(rev)].append(rev)
672 cache[deltaparent(rev)].append(rev)
665
673
666 def _refinedgroups(revlog, p1, p2, cachedelta):
674 def _refinedgroups(revlog, p1, p2, cachedelta):
667 good = None
675 good = None
668 # First we try to reuse a the delta contained in the bundle.
676 # First we try to reuse a the delta contained in the bundle.
669 # (or from the source revlog)
677 # (or from the source revlog)
670 #
678 #
671 # This logic only applies to general delta repositories and can be disabled
679 # This logic only applies to general delta repositories and can be disabled
672 # through configuration. Disabling reuse source delta is useful when
680 # through configuration. Disabling reuse source delta is useful when
673 # we want to make sure we recomputed "optimal" deltas.
681 # we want to make sure we recomputed "optimal" deltas.
674 if cachedelta and revlog._generaldelta and revlog._lazydeltabase:
682 if cachedelta and revlog._generaldelta and revlog._lazydeltabase:
675 # Assume what we received from the server is a good choice
683 # Assume what we received from the server is a good choice
676 # build delta will reuse the cache
684 # build delta will reuse the cache
677 good = yield (cachedelta[0],)
685 good = yield (cachedelta[0],)
678 if good is not None:
686 if good is not None:
679 yield None
687 yield None
680 return
688 return
681 for candidates in _rawgroups(revlog, p1, p2, cachedelta):
689 for candidates in _rawgroups(revlog, p1, p2, cachedelta):
682 good = yield candidates
690 good = yield candidates
683 if good is not None:
691 if good is not None:
684 break
692 break
685
693
686 # If sparse revlog is enabled, we can try to refine the available deltas
694 # If sparse revlog is enabled, we can try to refine the available deltas
687 if not revlog._sparserevlog:
695 if not revlog._sparserevlog:
688 yield None
696 yield None
689 return
697 return
690
698
691 # if we have a refinable value, try to refine it
699 # if we have a refinable value, try to refine it
692 if good is not None and good not in (p1, p2) and revlog.issnapshot(good):
700 if good is not None and good not in (p1, p2) and revlog.issnapshot(good):
693 # refine snapshot down
701 # refine snapshot down
694 previous = None
702 previous = None
695 while previous != good:
703 while previous != good:
696 previous = good
704 previous = good
697 base = revlog.deltaparent(good)
705 base = revlog.deltaparent(good)
698 if base == nullrev:
706 if base == nullrev:
699 break
707 break
700 good = yield (base,)
708 good = yield (base,)
701 # refine snapshot up
709 # refine snapshot up
702 #
710 #
703 # XXX the _findsnapshots call can be expensive and is "duplicated" with
711 # XXX the _findsnapshots call can be expensive and is "duplicated" with
704 # the one done in `_rawgroups`. Once we start working on performance,
712 # the one done in `_rawgroups`. Once we start working on performance,
705 # we should make the two logics share this computation.
713 # we should make the two logics share this computation.
706 snapshots = collections.defaultdict(list)
714 snapshots = collections.defaultdict(list)
707 _findsnapshots(revlog, snapshots, good + 1)
715 _findsnapshots(revlog, snapshots, good + 1)
708 previous = None
716 previous = None
709 while good != previous:
717 while good != previous:
710 previous = good
718 previous = good
711 children = tuple(sorted(c for c in snapshots[good]))
719 children = tuple(sorted(c for c in snapshots[good]))
712 good = yield children
720 good = yield children
713
721
714 # we have found nothing
722 # we have found nothing
715 yield None
723 yield None
716
724
717 def _rawgroups(revlog, p1, p2, cachedelta):
725 def _rawgroups(revlog, p1, p2, cachedelta):
718 """Provides group of revision to be tested as delta base
726 """Provides group of revision to be tested as delta base
719
727
720 This lower level function focus on emitting delta theorically interresting
728 This lower level function focus on emitting delta theorically interresting
721 without looking it any practical details.
729 without looking it any practical details.
722
730
723 The group order aims at providing fast or small candidates first.
731 The group order aims at providing fast or small candidates first.
724 """
732 """
725 gdelta = revlog._generaldelta
733 gdelta = revlog._generaldelta
726 sparse = revlog._sparserevlog
734 sparse = revlog._sparserevlog
727 curr = len(revlog)
735 curr = len(revlog)
728 prev = curr - 1
736 prev = curr - 1
729 deltachain = lambda rev: revlog._deltachain(rev)[0]
737 deltachain = lambda rev: revlog._deltachain(rev)[0]
730
738
731 if gdelta:
739 if gdelta:
732 # exclude already lazy tested base if any
740 # exclude already lazy tested base if any
733 parents = [p for p in (p1, p2) if p != nullrev]
741 parents = [p for p in (p1, p2) if p != nullrev]
734
742
735 if not revlog._deltabothparents and len(parents) == 2:
743 if not revlog._deltabothparents and len(parents) == 2:
736 parents.sort()
744 parents.sort()
737 # To minimize the chance of having to build a fulltext,
745 # To minimize the chance of having to build a fulltext,
738 # pick first whichever parent is closest to us (max rev)
746 # pick first whichever parent is closest to us (max rev)
739 yield (parents[1],)
747 yield (parents[1],)
740 # then the other one (min rev) if the first did not fit
748 # then the other one (min rev) if the first did not fit
741 yield (parents[0],)
749 yield (parents[0],)
742 elif len(parents) > 0:
750 elif len(parents) > 0:
743 # Test all parents (1 or 2), and keep the best candidate
751 # Test all parents (1 or 2), and keep the best candidate
744 yield parents
752 yield parents
745
753
746 if sparse and parents:
754 if sparse and parents:
747 snapshots = collections.defaultdict(list) # map: base-rev: snapshot-rev
755 snapshots = collections.defaultdict(list) # map: base-rev: snapshot-rev
748 # See if we can use an existing snapshot in the parent chains to use as
756 # See if we can use an existing snapshot in the parent chains to use as
749 # a base for a new intermediate-snapshot
757 # a base for a new intermediate-snapshot
750 #
758 #
751 # search for snapshot in parents delta chain
759 # search for snapshot in parents delta chain
752 # map: snapshot-level: snapshot-rev
760 # map: snapshot-level: snapshot-rev
753 parents_snaps = collections.defaultdict(set)
761 parents_snaps = collections.defaultdict(set)
754 candidate_chains = [deltachain(p) for p in parents]
762 candidate_chains = [deltachain(p) for p in parents]
755 for chain in candidate_chains:
763 for chain in candidate_chains:
756 for idx, s in enumerate(chain):
764 for idx, s in enumerate(chain):
757 if not revlog.issnapshot(s):
765 if not revlog.issnapshot(s):
758 break
766 break
759 parents_snaps[idx].add(s)
767 parents_snaps[idx].add(s)
760 snapfloor = min(parents_snaps[0]) + 1
768 snapfloor = min(parents_snaps[0]) + 1
761 _findsnapshots(revlog, snapshots, snapfloor)
769 _findsnapshots(revlog, snapshots, snapfloor)
762 # search for the highest "unrelated" revision
770 # search for the highest "unrelated" revision
763 #
771 #
764 # Adding snapshots used by "unrelated" revision increase the odd we
772 # Adding snapshots used by "unrelated" revision increase the odd we
765 # reuse an independant, yet better snapshot chain.
773 # reuse an independant, yet better snapshot chain.
766 #
774 #
767 # XXX instead of building a set of revisions, we could lazily enumerate
775 # XXX instead of building a set of revisions, we could lazily enumerate
768 # over the chains. That would be more efficient, however we stick to
776 # over the chains. That would be more efficient, however we stick to
769 # simple code for now.
777 # simple code for now.
770 all_revs = set()
778 all_revs = set()
771 for chain in candidate_chains:
779 for chain in candidate_chains:
772 all_revs.update(chain)
780 all_revs.update(chain)
773 other = None
781 other = None
774 for r in revlog.revs(prev, snapfloor):
782 for r in revlog.revs(prev, snapfloor):
775 if r not in all_revs:
783 if r not in all_revs:
776 other = r
784 other = r
777 break
785 break
778 if other is not None:
786 if other is not None:
779 # To avoid unfair competition, we won't use unrelated intermediate
787 # To avoid unfair competition, we won't use unrelated intermediate
780 # snapshot that are deeper than the ones from the parent delta
788 # snapshot that are deeper than the ones from the parent delta
781 # chain.
789 # chain.
782 max_depth = max(parents_snaps.keys())
790 max_depth = max(parents_snaps.keys())
783 chain = deltachain(other)
791 chain = deltachain(other)
784 for idx, s in enumerate(chain):
792 for idx, s in enumerate(chain):
785 if s < snapfloor:
793 if s < snapfloor:
786 continue
794 continue
787 if max_depth < idx:
795 if max_depth < idx:
788 break
796 break
789 if not revlog.issnapshot(s):
797 if not revlog.issnapshot(s):
790 break
798 break
791 parents_snaps[idx].add(s)
799 parents_snaps[idx].add(s)
792 # Test them as possible intermediate snapshot base
800 # Test them as possible intermediate snapshot base
793 # We test them from highest to lowest level. High level one are more
801 # We test them from highest to lowest level. High level one are more
794 # likely to result in small delta
802 # likely to result in small delta
795 floor = None
803 floor = None
796 for idx, snaps in sorted(parents_snaps.items(), reverse=True):
804 for idx, snaps in sorted(parents_snaps.items(), reverse=True):
797 siblings = set()
805 siblings = set()
798 for s in snaps:
806 for s in snaps:
799 siblings.update(snapshots[s])
807 siblings.update(snapshots[s])
800 # Before considering making a new intermediate snapshot, we check
808 # Before considering making a new intermediate snapshot, we check
801 # if an existing snapshot, children of base we consider, would be
809 # if an existing snapshot, children of base we consider, would be
802 # suitable.
810 # suitable.
803 #
811 #
804 # It give a change to reuse a delta chain "unrelated" to the
812 # It give a change to reuse a delta chain "unrelated" to the
805 # current revision instead of starting our own. Without such
813 # current revision instead of starting our own. Without such
806 # re-use, topological branches would keep reopening new chains.
814 # re-use, topological branches would keep reopening new chains.
807 # Creating more and more snapshot as the repository grow.
815 # Creating more and more snapshot as the repository grow.
808
816
809 if floor is not None:
817 if floor is not None:
810 # We only do this for siblings created after the one in our
818 # We only do this for siblings created after the one in our
811 # parent's delta chain. Those created before has less chances
819 # parent's delta chain. Those created before has less chances
812 # to be valid base since our ancestors had to create a new
820 # to be valid base since our ancestors had to create a new
813 # snapshot.
821 # snapshot.
814 siblings = [r for r in siblings if floor < r]
822 siblings = [r for r in siblings if floor < r]
815 yield tuple(sorted(siblings))
823 yield tuple(sorted(siblings))
816 # then test the base from our parent's delta chain.
824 # then test the base from our parent's delta chain.
817 yield tuple(sorted(snaps))
825 yield tuple(sorted(snaps))
818 floor = min(snaps)
826 floor = min(snaps)
819 # No suitable base found in the parent chain, search if any full
827 # No suitable base found in the parent chain, search if any full
820 # snapshots emitted since parent's base would be a suitable base for an
828 # snapshots emitted since parent's base would be a suitable base for an
821 # intermediate snapshot.
829 # intermediate snapshot.
822 #
830 #
823 # It give a chance to reuse a delta chain unrelated to the current
831 # It give a chance to reuse a delta chain unrelated to the current
824 # revisions instead of starting our own. Without such re-use,
832 # revisions instead of starting our own. Without such re-use,
825 # topological branches would keep reopening new full chains. Creating
833 # topological branches would keep reopening new full chains. Creating
826 # more and more snapshot as the repository grow.
834 # more and more snapshot as the repository grow.
827 yield tuple(snapshots[nullrev])
835 yield tuple(snapshots[nullrev])
828
836
829 if not sparse:
837 if not sparse:
830 # other approach failed try against prev to hopefully save us a
838 # other approach failed try against prev to hopefully save us a
831 # fulltext.
839 # fulltext.
832 yield (prev,)
840 yield (prev,)
833
841
834 class deltacomputer(object):
842 class deltacomputer(object):
835 def __init__(self, revlog):
843 def __init__(self, revlog):
836 self.revlog = revlog
844 self.revlog = revlog
837
845
838 def buildtext(self, revinfo, fh):
846 def buildtext(self, revinfo, fh):
839 """Builds a fulltext version of a revision
847 """Builds a fulltext version of a revision
840
848
841 revinfo: _revisioninfo instance that contains all needed info
849 revinfo: _revisioninfo instance that contains all needed info
842 fh: file handle to either the .i or the .d revlog file,
850 fh: file handle to either the .i or the .d revlog file,
843 depending on whether it is inlined or not
851 depending on whether it is inlined or not
844 """
852 """
845 btext = revinfo.btext
853 btext = revinfo.btext
846 if btext[0] is not None:
854 if btext[0] is not None:
847 return btext[0]
855 return btext[0]
848
856
849 revlog = self.revlog
857 revlog = self.revlog
850 cachedelta = revinfo.cachedelta
858 cachedelta = revinfo.cachedelta
851 baserev = cachedelta[0]
859 baserev = cachedelta[0]
852 delta = cachedelta[1]
860 delta = cachedelta[1]
853
861
854 fulltext = btext[0] = _textfromdelta(fh, revlog, baserev, delta,
862 fulltext = btext[0] = _textfromdelta(fh, revlog, baserev, delta,
855 revinfo.p1, revinfo.p2,
863 revinfo.p1, revinfo.p2,
856 revinfo.flags, revinfo.node)
864 revinfo.flags, revinfo.node)
857 return fulltext
865 return fulltext
858
866
859 def _builddeltadiff(self, base, revinfo, fh):
867 def _builddeltadiff(self, base, revinfo, fh):
860 revlog = self.revlog
868 revlog = self.revlog
861 t = self.buildtext(revinfo, fh)
869 t = self.buildtext(revinfo, fh)
862 if revlog.iscensored(base):
870 if revlog.iscensored(base):
863 # deltas based on a censored revision must replace the
871 # deltas based on a censored revision must replace the
864 # full content in one patch, so delta works everywhere
872 # full content in one patch, so delta works everywhere
865 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
873 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
866 delta = header + t
874 delta = header + t
867 else:
875 else:
868 ptext = revlog.revision(base, _df=fh, raw=True)
876 ptext = revlog.revision(base, _df=fh, raw=True)
869 delta = mdiff.textdiff(ptext, t)
877 delta = mdiff.textdiff(ptext, t)
870
878
871 return delta
879 return delta
872
880
873 def _builddeltainfo(self, revinfo, base, fh):
881 def _builddeltainfo(self, revinfo, base, fh):
874 # can we use the cached delta?
882 # can we use the cached delta?
875 delta = None
883 delta = None
876 if revinfo.cachedelta:
884 if revinfo.cachedelta:
877 cachebase, cachediff = revinfo.cachedelta
885 cachebase, cachediff = revinfo.cachedelta
878 #check if the diff still apply
886 #check if the diff still apply
879 currentbase = cachebase
887 currentbase = cachebase
880 while (currentbase != nullrev
888 while (currentbase != nullrev
881 and currentbase != base
889 and currentbase != base
882 and self.revlog.length(currentbase) == 0):
890 and self.revlog.length(currentbase) == 0):
883 currentbase = self.revlog.deltaparent(currentbase)
891 currentbase = self.revlog.deltaparent(currentbase)
884 if currentbase == base:
892 if currentbase == base:
885 delta = revinfo.cachedelta[1]
893 delta = revinfo.cachedelta[1]
886 if delta is None:
894 if delta is None:
887 delta = self._builddeltadiff(base, revinfo, fh)
895 delta = self._builddeltadiff(base, revinfo, fh)
888 revlog = self.revlog
896 revlog = self.revlog
889 header, data = revlog.compress(delta)
897 header, data = revlog.compress(delta)
890 deltalen = len(header) + len(data)
898 deltalen = len(header) + len(data)
891 chainbase = revlog.chainbase(base)
899 chainbase = revlog.chainbase(base)
892 offset = revlog.end(len(revlog) - 1)
900 offset = revlog.end(len(revlog) - 1)
893 dist = deltalen + offset - revlog.start(chainbase)
901 dist = deltalen + offset - revlog.start(chainbase)
894 if revlog._generaldelta:
902 if revlog._generaldelta:
895 deltabase = base
903 deltabase = base
896 else:
904 else:
897 deltabase = chainbase
905 deltabase = chainbase
898 chainlen, compresseddeltalen = revlog._chaininfo(base)
906 chainlen, compresseddeltalen = revlog._chaininfo(base)
899 chainlen += 1
907 chainlen += 1
900 compresseddeltalen += deltalen
908 compresseddeltalen += deltalen
901
909
902 revlog = self.revlog
910 revlog = self.revlog
903 snapshotdepth = None
911 snapshotdepth = None
904 if deltabase == nullrev:
912 if deltabase == nullrev:
905 snapshotdepth = 0
913 snapshotdepth = 0
906 elif revlog._sparserevlog and revlog.issnapshot(deltabase):
914 elif revlog._sparserevlog and revlog.issnapshot(deltabase):
907 # A delta chain should always be one full snapshot,
915 # A delta chain should always be one full snapshot,
908 # zero or more semi-snapshots, and zero or more deltas
916 # zero or more semi-snapshots, and zero or more deltas
909 p1, p2 = revlog.rev(revinfo.p1), revlog.rev(revinfo.p2)
917 p1, p2 = revlog.rev(revinfo.p1), revlog.rev(revinfo.p2)
910 if deltabase not in (p1, p2) and revlog.issnapshot(deltabase):
918 if deltabase not in (p1, p2) and revlog.issnapshot(deltabase):
911 snapshotdepth = len(revlog._deltachain(deltabase)[0])
919 snapshotdepth = len(revlog._deltachain(deltabase)[0])
912
920
913 return _deltainfo(dist, deltalen, (header, data), deltabase,
921 return _deltainfo(dist, deltalen, (header, data), deltabase,
914 chainbase, chainlen, compresseddeltalen,
922 chainbase, chainlen, compresseddeltalen,
915 snapshotdepth)
923 snapshotdepth)
916
924
917 def _fullsnapshotinfo(self, fh, revinfo):
925 def _fullsnapshotinfo(self, fh, revinfo):
918 curr = len(self.revlog)
926 curr = len(self.revlog)
919 rawtext = self.buildtext(revinfo, fh)
927 rawtext = self.buildtext(revinfo, fh)
920 data = self.revlog.compress(rawtext)
928 data = self.revlog.compress(rawtext)
921 compresseddeltalen = deltalen = dist = len(data[1]) + len(data[0])
929 compresseddeltalen = deltalen = dist = len(data[1]) + len(data[0])
922 deltabase = chainbase = curr
930 deltabase = chainbase = curr
923 snapshotdepth = 0
931 snapshotdepth = 0
924 chainlen = 1
932 chainlen = 1
925
933
926 return _deltainfo(dist, deltalen, data, deltabase,
934 return _deltainfo(dist, deltalen, data, deltabase,
927 chainbase, chainlen, compresseddeltalen,
935 chainbase, chainlen, compresseddeltalen,
928 snapshotdepth)
936 snapshotdepth)
929
937
930 def finddeltainfo(self, revinfo, fh):
938 def finddeltainfo(self, revinfo, fh):
931 """Find an acceptable delta against a candidate revision
939 """Find an acceptable delta against a candidate revision
932
940
933 revinfo: information about the revision (instance of _revisioninfo)
941 revinfo: information about the revision (instance of _revisioninfo)
934 fh: file handle to either the .i or the .d revlog file,
942 fh: file handle to either the .i or the .d revlog file,
935 depending on whether it is inlined or not
943 depending on whether it is inlined or not
936
944
937 Returns the first acceptable candidate revision, as ordered by
945 Returns the first acceptable candidate revision, as ordered by
938 _candidategroups
946 _candidategroups
939
947
940 If no suitable deltabase is found, we return delta info for a full
948 If no suitable deltabase is found, we return delta info for a full
941 snapshot.
949 snapshot.
942 """
950 """
943 if not revinfo.textlen:
951 if not revinfo.textlen:
944 return self._fullsnapshotinfo(fh, revinfo)
952 return self._fullsnapshotinfo(fh, revinfo)
945
953
946 # no delta for flag processor revision (see "candelta" for why)
954 # no delta for flag processor revision (see "candelta" for why)
947 # not calling candelta since only one revision needs test, also to
955 # not calling candelta since only one revision needs test, also to
948 # avoid overhead fetching flags again.
956 # avoid overhead fetching flags again.
949 if revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
957 if revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
950 return self._fullsnapshotinfo(fh, revinfo)
958 return self._fullsnapshotinfo(fh, revinfo)
951
959
952 cachedelta = revinfo.cachedelta
960 cachedelta = revinfo.cachedelta
953 p1 = revinfo.p1
961 p1 = revinfo.p1
954 p2 = revinfo.p2
962 p2 = revinfo.p2
955 revlog = self.revlog
963 revlog = self.revlog
956
964
957 deltainfo = None
965 deltainfo = None
958 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
966 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
959 groups = _candidategroups(self.revlog, revinfo.textlen,
967 groups = _candidategroups(self.revlog, revinfo.textlen,
960 p1r, p2r, cachedelta)
968 p1r, p2r, cachedelta)
961 candidaterevs = next(groups)
969 candidaterevs = next(groups)
962 while candidaterevs is not None:
970 while candidaterevs is not None:
963 nominateddeltas = []
971 nominateddeltas = []
964 if deltainfo is not None:
972 if deltainfo is not None:
965 # if we already found a good delta,
973 # if we already found a good delta,
966 # challenge it against refined candidates
974 # challenge it against refined candidates
967 nominateddeltas.append(deltainfo)
975 nominateddeltas.append(deltainfo)
968 for candidaterev in candidaterevs:
976 for candidaterev in candidaterevs:
969 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
977 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
970 if isgooddeltainfo(self.revlog, candidatedelta, revinfo):
978 if isgooddeltainfo(self.revlog, candidatedelta, revinfo):
971 nominateddeltas.append(candidatedelta)
979 nominateddeltas.append(candidatedelta)
972 if nominateddeltas:
980 if nominateddeltas:
973 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
981 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
974 if deltainfo is not None:
982 if deltainfo is not None:
975 candidaterevs = groups.send(deltainfo.base)
983 candidaterevs = groups.send(deltainfo.base)
976 else:
984 else:
977 candidaterevs = next(groups)
985 candidaterevs = next(groups)
978
986
979 if deltainfo is None:
987 if deltainfo is None:
980 deltainfo = self._fullsnapshotinfo(fh, revinfo)
988 deltainfo = self._fullsnapshotinfo(fh, revinfo)
981 return deltainfo
989 return deltainfo
@@ -1,567 +1,567 b''
1 #require serve no-reposimplestore no-chg
1 #require serve no-reposimplestore no-chg
2
2
3 #testcases stream-legacy stream-bundle2
3 #testcases stream-legacy stream-bundle2
4
4
5 #if stream-legacy
5 #if stream-legacy
6 $ cat << EOF >> $HGRCPATH
6 $ cat << EOF >> $HGRCPATH
7 > [server]
7 > [server]
8 > bundle2.stream = no
8 > bundle2.stream = no
9 > EOF
9 > EOF
10 #endif
10 #endif
11
11
12 Initialize repository
12 Initialize repository
13 the status call is to check for issue5130
13 the status call is to check for issue5130
14
14
15 $ hg init server
15 $ hg init server
16 $ cd server
16 $ cd server
17 $ touch foo
17 $ touch foo
18 $ hg -q commit -A -m initial
18 $ hg -q commit -A -m initial
19 >>> for i in range(1024):
19 >>> for i in range(1024):
20 ... with open(str(i), 'wb') as fh:
20 ... with open(str(i), 'wb') as fh:
21 ... fh.write(b"%d" % i) and None
21 ... fh.write(b"%d" % i) and None
22 $ hg -q commit -A -m 'add a lot of files'
22 $ hg -q commit -A -m 'add a lot of files'
23 $ hg st
23 $ hg st
24 $ hg --config server.uncompressed=false serve -p $HGPORT -d --pid-file=hg.pid
24 $ hg --config server.uncompressed=false serve -p $HGPORT -d --pid-file=hg.pid
25 $ cat hg.pid > $DAEMON_PIDS
25 $ cat hg.pid > $DAEMON_PIDS
26 $ cd ..
26 $ cd ..
27
27
28 Cannot stream clone when server.uncompressed is set
28 Cannot stream clone when server.uncompressed is set
29
29
30 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=stream_out'
30 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=stream_out'
31 200 Script output follows
31 200 Script output follows
32
32
33 1
33 1
34
34
35 #if stream-legacy
35 #if stream-legacy
36 $ hg debugcapabilities http://localhost:$HGPORT
36 $ hg debugcapabilities http://localhost:$HGPORT
37 Main capabilities:
37 Main capabilities:
38 batch
38 batch
39 branchmap
39 branchmap
40 $USUAL_BUNDLE2_CAPS_SERVER$
40 $USUAL_BUNDLE2_CAPS_SERVER$
41 changegroupsubset
41 changegroupsubset
42 compression=$BUNDLE2_COMPRESSIONS$
42 compression=$BUNDLE2_COMPRESSIONS$
43 getbundle
43 getbundle
44 httpheader=1024
44 httpheader=1024
45 httpmediatype=0.1rx,0.1tx,0.2tx
45 httpmediatype=0.1rx,0.1tx,0.2tx
46 known
46 known
47 lookup
47 lookup
48 pushkey
48 pushkey
49 unbundle=HG10GZ,HG10BZ,HG10UN
49 unbundle=HG10GZ,HG10BZ,HG10UN
50 unbundlehash
50 unbundlehash
51 Bundle2 capabilities:
51 Bundle2 capabilities:
52 HG20
52 HG20
53 bookmarks
53 bookmarks
54 changegroup
54 changegroup
55 01
55 01
56 02
56 02
57 digests
57 digests
58 md5
58 md5
59 sha1
59 sha1
60 sha512
60 sha512
61 error
61 error
62 abort
62 abort
63 unsupportedcontent
63 unsupportedcontent
64 pushraced
64 pushraced
65 pushkey
65 pushkey
66 hgtagsfnodes
66 hgtagsfnodes
67 listkeys
67 listkeys
68 phases
68 phases
69 heads
69 heads
70 pushkey
70 pushkey
71 remote-changegroup
71 remote-changegroup
72 http
72 http
73 https
73 https
74 rev-branch-cache
74 rev-branch-cache
75
75
76 $ hg clone --stream -U http://localhost:$HGPORT server-disabled
76 $ hg clone --stream -U http://localhost:$HGPORT server-disabled
77 warning: stream clone requested but server has them disabled
77 warning: stream clone requested but server has them disabled
78 requesting all changes
78 requesting all changes
79 adding changesets
79 adding changesets
80 adding manifests
80 adding manifests
81 adding file changes
81 adding file changes
82 added 2 changesets with 1025 changes to 1025 files
82 added 2 changesets with 1025 changes to 1025 files
83 new changesets 96ee1d7354c4:c17445101a72
83 new changesets 96ee1d7354c4:c17445101a72
84
84
85 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle' content-type --bodyfile body --hgproto 0.2 --requestheader "x-hgarg-1=bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=0000000000000000000000000000000000000000&heads=c17445101a72edac06facd130d14808dfbd5c7c2&stream=1"
85 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle' content-type --bodyfile body --hgproto 0.2 --requestheader "x-hgarg-1=bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=0000000000000000000000000000000000000000&heads=c17445101a72edac06facd130d14808dfbd5c7c2&stream=1"
86 200 Script output follows
86 200 Script output follows
87 content-type: application/mercurial-0.2
87 content-type: application/mercurial-0.2
88
88
89
89
90 $ f --size body --hexdump --bytes 100
90 $ f --size body --hexdump --bytes 100
91 body: size=232
91 body: size=232
92 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
92 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
93 0010: cf 0b 45 52 52 4f 52 3a 41 42 4f 52 54 00 00 00 |..ERROR:ABORT...|
93 0010: cf 0b 45 52 52 4f 52 3a 41 42 4f 52 54 00 00 00 |..ERROR:ABORT...|
94 0020: 00 01 01 07 3c 04 72 6d 65 73 73 61 67 65 73 74 |....<.rmessagest|
94 0020: 00 01 01 07 3c 04 72 6d 65 73 73 61 67 65 73 74 |....<.rmessagest|
95 0030: 72 65 61 6d 20 64 61 74 61 20 72 65 71 75 65 73 |ream data reques|
95 0030: 72 65 61 6d 20 64 61 74 61 20 72 65 71 75 65 73 |ream data reques|
96 0040: 74 65 64 20 62 75 74 20 73 65 72 76 65 72 20 64 |ted but server d|
96 0040: 74 65 64 20 62 75 74 20 73 65 72 76 65 72 20 64 |ted but server d|
97 0050: 6f 65 73 20 6e 6f 74 20 61 6c 6c 6f 77 20 74 68 |oes not allow th|
97 0050: 6f 65 73 20 6e 6f 74 20 61 6c 6c 6f 77 20 74 68 |oes not allow th|
98 0060: 69 73 20 66 |is f|
98 0060: 69 73 20 66 |is f|
99
99
100 #endif
100 #endif
101 #if stream-bundle2
101 #if stream-bundle2
102 $ hg debugcapabilities http://localhost:$HGPORT
102 $ hg debugcapabilities http://localhost:$HGPORT
103 Main capabilities:
103 Main capabilities:
104 batch
104 batch
105 branchmap
105 branchmap
106 $USUAL_BUNDLE2_CAPS_SERVER$
106 $USUAL_BUNDLE2_CAPS_SERVER$
107 changegroupsubset
107 changegroupsubset
108 compression=$BUNDLE2_COMPRESSIONS$
108 compression=$BUNDLE2_COMPRESSIONS$
109 getbundle
109 getbundle
110 httpheader=1024
110 httpheader=1024
111 httpmediatype=0.1rx,0.1tx,0.2tx
111 httpmediatype=0.1rx,0.1tx,0.2tx
112 known
112 known
113 lookup
113 lookup
114 pushkey
114 pushkey
115 unbundle=HG10GZ,HG10BZ,HG10UN
115 unbundle=HG10GZ,HG10BZ,HG10UN
116 unbundlehash
116 unbundlehash
117 Bundle2 capabilities:
117 Bundle2 capabilities:
118 HG20
118 HG20
119 bookmarks
119 bookmarks
120 changegroup
120 changegroup
121 01
121 01
122 02
122 02
123 digests
123 digests
124 md5
124 md5
125 sha1
125 sha1
126 sha512
126 sha512
127 error
127 error
128 abort
128 abort
129 unsupportedcontent
129 unsupportedcontent
130 pushraced
130 pushraced
131 pushkey
131 pushkey
132 hgtagsfnodes
132 hgtagsfnodes
133 listkeys
133 listkeys
134 phases
134 phases
135 heads
135 heads
136 pushkey
136 pushkey
137 remote-changegroup
137 remote-changegroup
138 http
138 http
139 https
139 https
140 rev-branch-cache
140 rev-branch-cache
141
141
142 $ hg clone --stream -U http://localhost:$HGPORT server-disabled
142 $ hg clone --stream -U http://localhost:$HGPORT server-disabled
143 warning: stream clone requested but server has them disabled
143 warning: stream clone requested but server has them disabled
144 requesting all changes
144 requesting all changes
145 adding changesets
145 adding changesets
146 adding manifests
146 adding manifests
147 adding file changes
147 adding file changes
148 added 2 changesets with 1025 changes to 1025 files
148 added 2 changesets with 1025 changes to 1025 files
149 new changesets 96ee1d7354c4:c17445101a72
149 new changesets 96ee1d7354c4:c17445101a72
150
150
151 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle' content-type --bodyfile body --hgproto 0.2 --requestheader "x-hgarg-1=bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=0000000000000000000000000000000000000000&heads=c17445101a72edac06facd130d14808dfbd5c7c2&stream=1"
151 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle' content-type --bodyfile body --hgproto 0.2 --requestheader "x-hgarg-1=bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=0000000000000000000000000000000000000000&heads=c17445101a72edac06facd130d14808dfbd5c7c2&stream=1"
152 200 Script output follows
152 200 Script output follows
153 content-type: application/mercurial-0.2
153 content-type: application/mercurial-0.2
154
154
155
155
156 $ f --size body --hexdump --bytes 100
156 $ f --size body --hexdump --bytes 100
157 body: size=232
157 body: size=232
158 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
158 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
159 0010: cf 0b 45 52 52 4f 52 3a 41 42 4f 52 54 00 00 00 |..ERROR:ABORT...|
159 0010: cf 0b 45 52 52 4f 52 3a 41 42 4f 52 54 00 00 00 |..ERROR:ABORT...|
160 0020: 00 01 01 07 3c 04 72 6d 65 73 73 61 67 65 73 74 |....<.rmessagest|
160 0020: 00 01 01 07 3c 04 72 6d 65 73 73 61 67 65 73 74 |....<.rmessagest|
161 0030: 72 65 61 6d 20 64 61 74 61 20 72 65 71 75 65 73 |ream data reques|
161 0030: 72 65 61 6d 20 64 61 74 61 20 72 65 71 75 65 73 |ream data reques|
162 0040: 74 65 64 20 62 75 74 20 73 65 72 76 65 72 20 64 |ted but server d|
162 0040: 74 65 64 20 62 75 74 20 73 65 72 76 65 72 20 64 |ted but server d|
163 0050: 6f 65 73 20 6e 6f 74 20 61 6c 6c 6f 77 20 74 68 |oes not allow th|
163 0050: 6f 65 73 20 6e 6f 74 20 61 6c 6c 6f 77 20 74 68 |oes not allow th|
164 0060: 69 73 20 66 |is f|
164 0060: 69 73 20 66 |is f|
165
165
166 #endif
166 #endif
167
167
168 $ killdaemons.py
168 $ killdaemons.py
169 $ cd server
169 $ cd server
170 $ hg serve -p $HGPORT -d --pid-file=hg.pid
170 $ hg serve -p $HGPORT -d --pid-file=hg.pid
171 $ cat hg.pid > $DAEMON_PIDS
171 $ cat hg.pid > $DAEMON_PIDS
172 $ cd ..
172 $ cd ..
173
173
174 Basic clone
174 Basic clone
175
175
176 #if stream-legacy
176 #if stream-legacy
177 $ hg clone --stream -U http://localhost:$HGPORT clone1
177 $ hg clone --stream -U http://localhost:$HGPORT clone1
178 streaming all changes
178 streaming all changes
179 1027 files to transfer, 96.3 KB of data
179 1027 files to transfer, 96.3 KB of data
180 transferred 96.3 KB in * seconds (*/sec) (glob)
180 transferred 96.3 KB in * seconds (*/sec) (glob)
181 searching for changes
181 searching for changes
182 no changes found
182 no changes found
183 #endif
183 #endif
184 #if stream-bundle2
184 #if stream-bundle2
185 $ hg clone --stream -U http://localhost:$HGPORT clone1
185 $ hg clone --stream -U http://localhost:$HGPORT clone1
186 streaming all changes
186 streaming all changes
187 1030 files to transfer, 96.4 KB of data
187 1030 files to transfer, 96.5 KB of data
188 transferred 96.4 KB in * seconds (* */sec) (glob)
188 transferred 96.5 KB in * seconds (* */sec) (glob)
189
189
190 $ ls -1 clone1/.hg/cache
190 $ ls -1 clone1/.hg/cache
191 branch2-served
191 branch2-served
192 rbc-names-v1
192 rbc-names-v1
193 rbc-revs-v1
193 rbc-revs-v1
194 #endif
194 #endif
195
195
196 getbundle requests with stream=1 are uncompressed
196 getbundle requests with stream=1 are uncompressed
197
197
198 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle' content-type --bodyfile body --hgproto '0.1 0.2 comp=zlib,none' --requestheader "x-hgarg-1=bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=0000000000000000000000000000000000000000&heads=c17445101a72edac06facd130d14808dfbd5c7c2&stream=1"
198 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle' content-type --bodyfile body --hgproto '0.1 0.2 comp=zlib,none' --requestheader "x-hgarg-1=bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=0000000000000000000000000000000000000000&heads=c17445101a72edac06facd130d14808dfbd5c7c2&stream=1"
199 200 Script output follows
199 200 Script output follows
200 content-type: application/mercurial-0.2
200 content-type: application/mercurial-0.2
201
201
202
202
203 $ f --size --hex --bytes 256 body
203 $ f --size --hex --bytes 256 body
204 body: size=112245
204 body: size=112262
205 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
205 0000: 04 6e 6f 6e 65 48 47 32 30 00 00 00 00 00 00 00 |.noneHG20.......|
206 0010: 7f 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2.......|
206 0010: 7f 07 53 54 52 45 41 4d 32 00 00 00 00 03 00 09 |..STREAM2.......|
207 0020: 05 09 04 0c 44 62 79 74 65 63 6f 75 6e 74 39 38 |....Dbytecount98|
207 0020: 05 09 04 0c 44 62 79 74 65 63 6f 75 6e 74 39 38 |....Dbytecount98|
208 0030: 37 35 38 66 69 6c 65 63 6f 75 6e 74 31 30 33 30 |758filecount1030|
208 0030: 37 37 35 66 69 6c 65 63 6f 75 6e 74 31 30 33 30 |775filecount1030|
209 0040: 72 65 71 75 69 72 65 6d 65 6e 74 73 64 6f 74 65 |requirementsdote|
209 0040: 72 65 71 75 69 72 65 6d 65 6e 74 73 64 6f 74 65 |requirementsdote|
210 0050: 6e 63 6f 64 65 25 32 43 66 6e 63 61 63 68 65 25 |ncode%2Cfncache%|
210 0050: 6e 63 6f 64 65 25 32 43 66 6e 63 61 63 68 65 25 |ncode%2Cfncache%|
211 0060: 32 43 67 65 6e 65 72 61 6c 64 65 6c 74 61 25 32 |2Cgeneraldelta%2|
211 0060: 32 43 67 65 6e 65 72 61 6c 64 65 6c 74 61 25 32 |2Cgeneraldelta%2|
212 0070: 43 72 65 76 6c 6f 67 76 31 25 32 43 73 70 61 72 |Crevlogv1%2Cspar|
212 0070: 43 72 65 76 6c 6f 67 76 31 25 32 43 73 70 61 72 |Crevlogv1%2Cspar|
213 0080: 73 65 72 65 76 6c 6f 67 25 32 43 73 74 6f 72 65 |serevlog%2Cstore|
213 0080: 73 65 72 65 76 6c 6f 67 25 32 43 73 74 6f 72 65 |serevlog%2Cstore|
214 0090: 00 00 80 00 73 08 42 64 61 74 61 2f 30 2e 69 00 |....s.Bdata/0.i.|
214 0090: 00 00 80 00 73 08 42 64 61 74 61 2f 30 2e 69 00 |....s.Bdata/0.i.|
215 00a0: 03 00 01 00 00 00 00 00 00 00 02 00 00 00 01 00 |................|
215 00a0: 03 00 01 00 00 00 00 00 00 00 02 00 00 00 01 00 |................|
216 00b0: 00 00 00 00 00 00 01 ff ff ff ff ff ff ff ff 80 |................|
216 00b0: 00 00 00 00 00 00 01 ff ff ff ff ff ff ff ff 80 |................|
217 00c0: 29 63 a0 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 |)c.I.#....Vg.g,i|
217 00c0: 29 63 a0 49 d3 23 87 bf ce fe 56 67 92 67 2c 69 |)c.I.#....Vg.g,i|
218 00d0: d1 ec 39 00 00 00 00 00 00 00 00 00 00 00 00 75 |..9............u|
218 00d0: d1 ec 39 00 00 00 00 00 00 00 00 00 00 00 00 75 |..9............u|
219 00e0: 30 73 08 42 64 61 74 61 2f 31 2e 69 00 03 00 01 |0s.Bdata/1.i....|
219 00e0: 30 73 08 42 64 61 74 61 2f 31 2e 69 00 03 00 01 |0s.Bdata/1.i....|
220 00f0: 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 00 |................|
220 00f0: 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 00 |................|
221
221
222 --uncompressed is an alias to --stream
222 --uncompressed is an alias to --stream
223
223
224 #if stream-legacy
224 #if stream-legacy
225 $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed
225 $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed
226 streaming all changes
226 streaming all changes
227 1027 files to transfer, 96.3 KB of data
227 1027 files to transfer, 96.3 KB of data
228 transferred 96.3 KB in * seconds (*/sec) (glob)
228 transferred 96.3 KB in * seconds (*/sec) (glob)
229 searching for changes
229 searching for changes
230 no changes found
230 no changes found
231 #endif
231 #endif
232 #if stream-bundle2
232 #if stream-bundle2
233 $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed
233 $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed
234 streaming all changes
234 streaming all changes
235 1030 files to transfer, 96.4 KB of data
235 1030 files to transfer, 96.5 KB of data
236 transferred 96.4 KB in * seconds (* */sec) (glob)
236 transferred 96.5 KB in * seconds (* */sec) (glob)
237 #endif
237 #endif
238
238
239 Clone with background file closing enabled
239 Clone with background file closing enabled
240
240
241 #if stream-legacy
241 #if stream-legacy
242 $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --stream -U http://localhost:$HGPORT clone-background | grep -v adding
242 $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --stream -U http://localhost:$HGPORT clone-background | grep -v adding
243 using http://localhost:$HGPORT/
243 using http://localhost:$HGPORT/
244 sending capabilities command
244 sending capabilities command
245 sending branchmap command
245 sending branchmap command
246 streaming all changes
246 streaming all changes
247 sending stream_out command
247 sending stream_out command
248 1027 files to transfer, 96.3 KB of data
248 1027 files to transfer, 96.3 KB of data
249 starting 4 threads for background file closing
249 starting 4 threads for background file closing
250 updating the branch cache
250 updating the branch cache
251 transferred 96.3 KB in * seconds (*/sec) (glob)
251 transferred 96.3 KB in * seconds (*/sec) (glob)
252 query 1; heads
252 query 1; heads
253 sending batch command
253 sending batch command
254 searching for changes
254 searching for changes
255 all remote heads known locally
255 all remote heads known locally
256 no changes found
256 no changes found
257 sending getbundle command
257 sending getbundle command
258 bundle2-input-bundle: with-transaction
258 bundle2-input-bundle: with-transaction
259 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
259 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
260 bundle2-input-part: "phase-heads" supported
260 bundle2-input-part: "phase-heads" supported
261 bundle2-input-part: total payload size 24
261 bundle2-input-part: total payload size 24
262 bundle2-input-bundle: 1 parts total
262 bundle2-input-bundle: 1 parts total
263 checking for updated bookmarks
263 checking for updated bookmarks
264 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
264 (sent 5 HTTP requests and * bytes; received * bytes in responses) (glob)
265 #endif
265 #endif
266 #if stream-bundle2
266 #if stream-bundle2
267 $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --stream -U http://localhost:$HGPORT clone-background | grep -v adding
267 $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --stream -U http://localhost:$HGPORT clone-background | grep -v adding
268 using http://localhost:$HGPORT/
268 using http://localhost:$HGPORT/
269 sending capabilities command
269 sending capabilities command
270 query 1; heads
270 query 1; heads
271 sending batch command
271 sending batch command
272 streaming all changes
272 streaming all changes
273 sending getbundle command
273 sending getbundle command
274 bundle2-input-bundle: with-transaction
274 bundle2-input-bundle: with-transaction
275 bundle2-input-part: "stream2" (params: 3 mandatory) supported
275 bundle2-input-part: "stream2" (params: 3 mandatory) supported
276 applying stream bundle
276 applying stream bundle
277 1030 files to transfer, 96.4 KB of data
277 1030 files to transfer, 96.5 KB of data
278 starting 4 threads for background file closing
278 starting 4 threads for background file closing
279 starting 4 threads for background file closing
279 starting 4 threads for background file closing
280 updating the branch cache
280 updating the branch cache
281 transferred 96.4 KB in * seconds (* */sec) (glob)
281 transferred 96.5 KB in * seconds (* */sec) (glob)
282 bundle2-input-part: total payload size 112077
282 bundle2-input-part: total payload size 112094
283 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
283 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
284 bundle2-input-bundle: 1 parts total
284 bundle2-input-bundle: 1 parts total
285 checking for updated bookmarks
285 checking for updated bookmarks
286 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
286 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
287 #endif
287 #endif
288
288
289 Cannot stream clone when there are secret changesets
289 Cannot stream clone when there are secret changesets
290
290
291 $ hg -R server phase --force --secret -r tip
291 $ hg -R server phase --force --secret -r tip
292 $ hg clone --stream -U http://localhost:$HGPORT secret-denied
292 $ hg clone --stream -U http://localhost:$HGPORT secret-denied
293 warning: stream clone requested but server has them disabled
293 warning: stream clone requested but server has them disabled
294 requesting all changes
294 requesting all changes
295 adding changesets
295 adding changesets
296 adding manifests
296 adding manifests
297 adding file changes
297 adding file changes
298 added 1 changesets with 1 changes to 1 files
298 added 1 changesets with 1 changes to 1 files
299 new changesets 96ee1d7354c4
299 new changesets 96ee1d7354c4
300
300
301 $ killdaemons.py
301 $ killdaemons.py
302
302
303 Streaming of secrets can be overridden by server config
303 Streaming of secrets can be overridden by server config
304
304
305 $ cd server
305 $ cd server
306 $ hg serve --config server.uncompressedallowsecret=true -p $HGPORT -d --pid-file=hg.pid
306 $ hg serve --config server.uncompressedallowsecret=true -p $HGPORT -d --pid-file=hg.pid
307 $ cat hg.pid > $DAEMON_PIDS
307 $ cat hg.pid > $DAEMON_PIDS
308 $ cd ..
308 $ cd ..
309
309
310 #if stream-legacy
310 #if stream-legacy
311 $ hg clone --stream -U http://localhost:$HGPORT secret-allowed
311 $ hg clone --stream -U http://localhost:$HGPORT secret-allowed
312 streaming all changes
312 streaming all changes
313 1027 files to transfer, 96.3 KB of data
313 1027 files to transfer, 96.3 KB of data
314 transferred 96.3 KB in * seconds (*/sec) (glob)
314 transferred 96.3 KB in * seconds (*/sec) (glob)
315 searching for changes
315 searching for changes
316 no changes found
316 no changes found
317 #endif
317 #endif
318 #if stream-bundle2
318 #if stream-bundle2
319 $ hg clone --stream -U http://localhost:$HGPORT secret-allowed
319 $ hg clone --stream -U http://localhost:$HGPORT secret-allowed
320 streaming all changes
320 streaming all changes
321 1030 files to transfer, 96.4 KB of data
321 1030 files to transfer, 96.5 KB of data
322 transferred 96.4 KB in * seconds (* */sec) (glob)
322 transferred 96.5 KB in * seconds (* */sec) (glob)
323 #endif
323 #endif
324
324
325 $ killdaemons.py
325 $ killdaemons.py
326
326
327 Verify interaction between preferuncompressed and secret presence
327 Verify interaction between preferuncompressed and secret presence
328
328
329 $ cd server
329 $ cd server
330 $ hg serve --config server.preferuncompressed=true -p $HGPORT -d --pid-file=hg.pid
330 $ hg serve --config server.preferuncompressed=true -p $HGPORT -d --pid-file=hg.pid
331 $ cat hg.pid > $DAEMON_PIDS
331 $ cat hg.pid > $DAEMON_PIDS
332 $ cd ..
332 $ cd ..
333
333
334 $ hg clone -U http://localhost:$HGPORT preferuncompressed-secret
334 $ hg clone -U http://localhost:$HGPORT preferuncompressed-secret
335 requesting all changes
335 requesting all changes
336 adding changesets
336 adding changesets
337 adding manifests
337 adding manifests
338 adding file changes
338 adding file changes
339 added 1 changesets with 1 changes to 1 files
339 added 1 changesets with 1 changes to 1 files
340 new changesets 96ee1d7354c4
340 new changesets 96ee1d7354c4
341
341
342 $ killdaemons.py
342 $ killdaemons.py
343
343
344 Clone not allowed when full bundles disabled and can't serve secrets
344 Clone not allowed when full bundles disabled and can't serve secrets
345
345
346 $ cd server
346 $ cd server
347 $ hg serve --config server.disablefullbundle=true -p $HGPORT -d --pid-file=hg.pid
347 $ hg serve --config server.disablefullbundle=true -p $HGPORT -d --pid-file=hg.pid
348 $ cat hg.pid > $DAEMON_PIDS
348 $ cat hg.pid > $DAEMON_PIDS
349 $ cd ..
349 $ cd ..
350
350
351 $ hg clone --stream http://localhost:$HGPORT secret-full-disabled
351 $ hg clone --stream http://localhost:$HGPORT secret-full-disabled
352 warning: stream clone requested but server has them disabled
352 warning: stream clone requested but server has them disabled
353 requesting all changes
353 requesting all changes
354 remote: abort: server has pull-based clones disabled
354 remote: abort: server has pull-based clones disabled
355 abort: pull failed on remote
355 abort: pull failed on remote
356 (remove --pull if specified or upgrade Mercurial)
356 (remove --pull if specified or upgrade Mercurial)
357 [255]
357 [255]
358
358
359 Local stream clone with secrets involved
359 Local stream clone with secrets involved
360 (This is just a test over behavior: if you have access to the repo's files,
360 (This is just a test over behavior: if you have access to the repo's files,
361 there is no security so it isn't important to prevent a clone here.)
361 there is no security so it isn't important to prevent a clone here.)
362
362
363 $ hg clone -U --stream server local-secret
363 $ hg clone -U --stream server local-secret
364 warning: stream clone requested but server has them disabled
364 warning: stream clone requested but server has them disabled
365 requesting all changes
365 requesting all changes
366 adding changesets
366 adding changesets
367 adding manifests
367 adding manifests
368 adding file changes
368 adding file changes
369 added 1 changesets with 1 changes to 1 files
369 added 1 changesets with 1 changes to 1 files
370 new changesets 96ee1d7354c4
370 new changesets 96ee1d7354c4
371
371
372 Stream clone while repo is changing:
372 Stream clone while repo is changing:
373
373
374 $ mkdir changing
374 $ mkdir changing
375 $ cd changing
375 $ cd changing
376
376
377 extension for delaying the server process so we reliably can modify the repo
377 extension for delaying the server process so we reliably can modify the repo
378 while cloning
378 while cloning
379
379
380 $ cat > delayer.py <<EOF
380 $ cat > delayer.py <<EOF
381 > import time
381 > import time
382 > from mercurial import extensions, vfs
382 > from mercurial import extensions, vfs
383 > def __call__(orig, self, path, *args, **kwargs):
383 > def __call__(orig, self, path, *args, **kwargs):
384 > if path == 'data/f1.i':
384 > if path == 'data/f1.i':
385 > time.sleep(2)
385 > time.sleep(2)
386 > return orig(self, path, *args, **kwargs)
386 > return orig(self, path, *args, **kwargs)
387 > extensions.wrapfunction(vfs.vfs, '__call__', __call__)
387 > extensions.wrapfunction(vfs.vfs, '__call__', __call__)
388 > EOF
388 > EOF
389
389
390 prepare repo with small and big file to cover both code paths in emitrevlogdata
390 prepare repo with small and big file to cover both code paths in emitrevlogdata
391
391
392 $ hg init repo
392 $ hg init repo
393 $ touch repo/f1
393 $ touch repo/f1
394 $ $TESTDIR/seq.py 50000 > repo/f2
394 $ $TESTDIR/seq.py 50000 > repo/f2
395 $ hg -R repo ci -Aqm "0"
395 $ hg -R repo ci -Aqm "0"
396 $ hg serve -R repo -p $HGPORT1 -d --pid-file=hg.pid --config extensions.delayer=delayer.py
396 $ hg serve -R repo -p $HGPORT1 -d --pid-file=hg.pid --config extensions.delayer=delayer.py
397 $ cat hg.pid >> $DAEMON_PIDS
397 $ cat hg.pid >> $DAEMON_PIDS
398
398
399 clone while modifying the repo between stating file with write lock and
399 clone while modifying the repo between stating file with write lock and
400 actually serving file content
400 actually serving file content
401
401
402 $ hg clone -q --stream -U http://localhost:$HGPORT1 clone &
402 $ hg clone -q --stream -U http://localhost:$HGPORT1 clone &
403 $ sleep 1
403 $ sleep 1
404 $ echo >> repo/f1
404 $ echo >> repo/f1
405 $ echo >> repo/f2
405 $ echo >> repo/f2
406 $ hg -R repo ci -m "1"
406 $ hg -R repo ci -m "1"
407 $ wait
407 $ wait
408 $ hg -R clone id
408 $ hg -R clone id
409 000000000000
409 000000000000
410 $ cd ..
410 $ cd ..
411
411
412 Stream repository with bookmarks
412 Stream repository with bookmarks
413 --------------------------------
413 --------------------------------
414
414
415 (revert introduction of secret changeset)
415 (revert introduction of secret changeset)
416
416
417 $ hg -R server phase --draft 'secret()'
417 $ hg -R server phase --draft 'secret()'
418
418
419 add a bookmark
419 add a bookmark
420
420
421 $ hg -R server bookmark -r tip some-bookmark
421 $ hg -R server bookmark -r tip some-bookmark
422
422
423 clone it
423 clone it
424
424
425 #if stream-legacy
425 #if stream-legacy
426 $ hg clone --stream http://localhost:$HGPORT with-bookmarks
426 $ hg clone --stream http://localhost:$HGPORT with-bookmarks
427 streaming all changes
427 streaming all changes
428 1027 files to transfer, 96.3 KB of data
428 1027 files to transfer, 96.3 KB of data
429 transferred 96.3 KB in * seconds (*) (glob)
429 transferred 96.3 KB in * seconds (*) (glob)
430 searching for changes
430 searching for changes
431 no changes found
431 no changes found
432 updating to branch default
432 updating to branch default
433 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
433 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
434 #endif
434 #endif
435 #if stream-bundle2
435 #if stream-bundle2
436 $ hg clone --stream http://localhost:$HGPORT with-bookmarks
436 $ hg clone --stream http://localhost:$HGPORT with-bookmarks
437 streaming all changes
437 streaming all changes
438 1033 files to transfer, 96.6 KB of data
438 1033 files to transfer, 96.6 KB of data
439 transferred 96.6 KB in * seconds (* */sec) (glob)
439 transferred 96.6 KB in * seconds (* */sec) (glob)
440 updating to branch default
440 updating to branch default
441 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
441 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
442 #endif
442 #endif
443 $ hg -R with-bookmarks bookmarks
443 $ hg -R with-bookmarks bookmarks
444 some-bookmark 1:c17445101a72
444 some-bookmark 1:c17445101a72
445
445
446 Stream repository with phases
446 Stream repository with phases
447 -----------------------------
447 -----------------------------
448
448
449 Clone as publishing
449 Clone as publishing
450
450
451 $ hg -R server phase -r 'all()'
451 $ hg -R server phase -r 'all()'
452 0: draft
452 0: draft
453 1: draft
453 1: draft
454
454
455 #if stream-legacy
455 #if stream-legacy
456 $ hg clone --stream http://localhost:$HGPORT phase-publish
456 $ hg clone --stream http://localhost:$HGPORT phase-publish
457 streaming all changes
457 streaming all changes
458 1027 files to transfer, 96.3 KB of data
458 1027 files to transfer, 96.3 KB of data
459 transferred 96.3 KB in * seconds (*) (glob)
459 transferred 96.3 KB in * seconds (*) (glob)
460 searching for changes
460 searching for changes
461 no changes found
461 no changes found
462 updating to branch default
462 updating to branch default
463 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
463 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
464 #endif
464 #endif
465 #if stream-bundle2
465 #if stream-bundle2
466 $ hg clone --stream http://localhost:$HGPORT phase-publish
466 $ hg clone --stream http://localhost:$HGPORT phase-publish
467 streaming all changes
467 streaming all changes
468 1033 files to transfer, 96.6 KB of data
468 1033 files to transfer, 96.6 KB of data
469 transferred 96.6 KB in * seconds (* */sec) (glob)
469 transferred 96.6 KB in * seconds (* */sec) (glob)
470 updating to branch default
470 updating to branch default
471 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
471 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
472 #endif
472 #endif
473 $ hg -R phase-publish phase -r 'all()'
473 $ hg -R phase-publish phase -r 'all()'
474 0: public
474 0: public
475 1: public
475 1: public
476
476
477 Clone as non publishing
477 Clone as non publishing
478
478
479 $ cat << EOF >> server/.hg/hgrc
479 $ cat << EOF >> server/.hg/hgrc
480 > [phases]
480 > [phases]
481 > publish = False
481 > publish = False
482 > EOF
482 > EOF
483 $ killdaemons.py
483 $ killdaemons.py
484 $ hg -R server serve -p $HGPORT -d --pid-file=hg.pid
484 $ hg -R server serve -p $HGPORT -d --pid-file=hg.pid
485 $ cat hg.pid > $DAEMON_PIDS
485 $ cat hg.pid > $DAEMON_PIDS
486
486
487 #if stream-legacy
487 #if stream-legacy
488
488
489 With v1 of the stream protocol, changeset are always cloned as public. It make
489 With v1 of the stream protocol, changeset are always cloned as public. It make
490 stream v1 unsuitable for non-publishing repository.
490 stream v1 unsuitable for non-publishing repository.
491
491
492 $ hg clone --stream http://localhost:$HGPORT phase-no-publish
492 $ hg clone --stream http://localhost:$HGPORT phase-no-publish
493 streaming all changes
493 streaming all changes
494 1027 files to transfer, 96.3 KB of data
494 1027 files to transfer, 96.3 KB of data
495 transferred 96.3 KB in * seconds (*) (glob)
495 transferred 96.3 KB in * seconds (*) (glob)
496 searching for changes
496 searching for changes
497 no changes found
497 no changes found
498 updating to branch default
498 updating to branch default
499 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
499 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
500 $ hg -R phase-no-publish phase -r 'all()'
500 $ hg -R phase-no-publish phase -r 'all()'
501 0: public
501 0: public
502 1: public
502 1: public
503 #endif
503 #endif
504 #if stream-bundle2
504 #if stream-bundle2
505 $ hg clone --stream http://localhost:$HGPORT phase-no-publish
505 $ hg clone --stream http://localhost:$HGPORT phase-no-publish
506 streaming all changes
506 streaming all changes
507 1034 files to transfer, 96.7 KB of data
507 1034 files to transfer, 96.7 KB of data
508 transferred 96.7 KB in * seconds (* */sec) (glob)
508 transferred 96.7 KB in * seconds (* */sec) (glob)
509 updating to branch default
509 updating to branch default
510 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
510 1025 files updated, 0 files merged, 0 files removed, 0 files unresolved
511 $ hg -R phase-no-publish phase -r 'all()'
511 $ hg -R phase-no-publish phase -r 'all()'
512 0: draft
512 0: draft
513 1: draft
513 1: draft
514 #endif
514 #endif
515
515
516 $ killdaemons.py
516 $ killdaemons.py
517
517
518 #if stream-legacy
518 #if stream-legacy
519
519
520 With v1 of the stream protocol, changeset are always cloned as public. There's
520 With v1 of the stream protocol, changeset are always cloned as public. There's
521 no obsolescence markers exchange in stream v1.
521 no obsolescence markers exchange in stream v1.
522
522
523 #endif
523 #endif
524 #if stream-bundle2
524 #if stream-bundle2
525
525
526 Stream repository with obsolescence
526 Stream repository with obsolescence
527 -----------------------------------
527 -----------------------------------
528
528
529 Clone non-publishing with obsolescence
529 Clone non-publishing with obsolescence
530
530
531 $ cat >> $HGRCPATH << EOF
531 $ cat >> $HGRCPATH << EOF
532 > [experimental]
532 > [experimental]
533 > evolution=all
533 > evolution=all
534 > EOF
534 > EOF
535
535
536 $ cd server
536 $ cd server
537 $ echo foo > foo
537 $ echo foo > foo
538 $ hg -q commit -m 'about to be pruned'
538 $ hg -q commit -m 'about to be pruned'
539 $ hg debugobsolete `hg log -r . -T '{node}'` -d '0 0' -u test --record-parents
539 $ hg debugobsolete `hg log -r . -T '{node}'` -d '0 0' -u test --record-parents
540 obsoleted 1 changesets
540 obsoleted 1 changesets
541 $ hg up null -q
541 $ hg up null -q
542 $ hg log -T '{rev}: {phase}\n'
542 $ hg log -T '{rev}: {phase}\n'
543 1: draft
543 1: draft
544 0: draft
544 0: draft
545 $ hg serve -p $HGPORT -d --pid-file=hg.pid
545 $ hg serve -p $HGPORT -d --pid-file=hg.pid
546 $ cat hg.pid > $DAEMON_PIDS
546 $ cat hg.pid > $DAEMON_PIDS
547 $ cd ..
547 $ cd ..
548
548
549 $ hg clone -U --stream http://localhost:$HGPORT with-obsolescence
549 $ hg clone -U --stream http://localhost:$HGPORT with-obsolescence
550 streaming all changes
550 streaming all changes
551 1035 files to transfer, 97.1 KB of data
551 1035 files to transfer, 97.1 KB of data
552 transferred 97.1 KB in * seconds (* */sec) (glob)
552 transferred 97.1 KB in * seconds (* */sec) (glob)
553 $ hg -R with-obsolescence log -T '{rev}: {phase}\n'
553 $ hg -R with-obsolescence log -T '{rev}: {phase}\n'
554 1: draft
554 1: draft
555 0: draft
555 0: draft
556 $ hg debugobsolete -R with-obsolescence
556 $ hg debugobsolete -R with-obsolescence
557 50382b884f66690b7045cac93a540cba4d4c906f 0 {c17445101a72edac06facd130d14808dfbd5c7c2} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
557 50382b884f66690b7045cac93a540cba4d4c906f 0 {c17445101a72edac06facd130d14808dfbd5c7c2} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
558
558
559 $ hg clone -U --stream --config experimental.evolution=0 http://localhost:$HGPORT with-obsolescence-no-evolution
559 $ hg clone -U --stream --config experimental.evolution=0 http://localhost:$HGPORT with-obsolescence-no-evolution
560 streaming all changes
560 streaming all changes
561 remote: abort: server has obsolescence markers, but client cannot receive them via stream clone
561 remote: abort: server has obsolescence markers, but client cannot receive them via stream clone
562 abort: pull failed on remote
562 abort: pull failed on remote
563 [255]
563 [255]
564
564
565 $ killdaemons.py
565 $ killdaemons.py
566
566
567 #endif
567 #endif
General Comments 0
You need to be logged in to leave comments. Login now