##// END OF EJS Templates
revlogv2: make sure bundling pick a compatible bundle format...
marmoute -
r48133:dc380056 default
parent child Browse files
Show More
@@ -1,425 +1,427 b''
1 # bundlecaches.py - utility to deal with pre-computed bundle for servers
1 # bundlecaches.py - utility to deal with pre-computed bundle for servers
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 from .i18n import _
6 from .i18n import _
7
7
8 from .thirdparty import attr
8 from .thirdparty import attr
9
9
10 from . import (
10 from . import (
11 error,
11 error,
12 requirements as requirementsmod,
12 requirements as requirementsmod,
13 sslutil,
13 sslutil,
14 util,
14 util,
15 )
15 )
16 from .utils import stringutil
16 from .utils import stringutil
17
17
18 urlreq = util.urlreq
18 urlreq = util.urlreq
19
19
20 CB_MANIFEST_FILE = b'clonebundles.manifest'
20 CB_MANIFEST_FILE = b'clonebundles.manifest'
21
21
22
22
23 @attr.s
23 @attr.s
24 class bundlespec(object):
24 class bundlespec(object):
25 compression = attr.ib()
25 compression = attr.ib()
26 wirecompression = attr.ib()
26 wirecompression = attr.ib()
27 version = attr.ib()
27 version = attr.ib()
28 wireversion = attr.ib()
28 wireversion = attr.ib()
29 params = attr.ib()
29 params = attr.ib()
30 contentopts = attr.ib()
30 contentopts = attr.ib()
31
31
32
32
33 # Maps bundle version human names to changegroup versions.
33 # Maps bundle version human names to changegroup versions.
34 _bundlespeccgversions = {
34 _bundlespeccgversions = {
35 b'v1': b'01',
35 b'v1': b'01',
36 b'v2': b'02',
36 b'v2': b'02',
37 b'packed1': b's1',
37 b'packed1': b's1',
38 b'bundle2': b'02', # legacy
38 b'bundle2': b'02', # legacy
39 }
39 }
40
40
41 # Maps bundle version with content opts to choose which part to bundle
41 # Maps bundle version with content opts to choose which part to bundle
42 _bundlespeccontentopts = {
42 _bundlespeccontentopts = {
43 b'v1': {
43 b'v1': {
44 b'changegroup': True,
44 b'changegroup': True,
45 b'cg.version': b'01',
45 b'cg.version': b'01',
46 b'obsolescence': False,
46 b'obsolescence': False,
47 b'phases': False,
47 b'phases': False,
48 b'tagsfnodescache': False,
48 b'tagsfnodescache': False,
49 b'revbranchcache': False,
49 b'revbranchcache': False,
50 },
50 },
51 b'v2': {
51 b'v2': {
52 b'changegroup': True,
52 b'changegroup': True,
53 b'cg.version': b'02',
53 b'cg.version': b'02',
54 b'obsolescence': False,
54 b'obsolescence': False,
55 b'phases': False,
55 b'phases': False,
56 b'tagsfnodescache': True,
56 b'tagsfnodescache': True,
57 b'revbranchcache': True,
57 b'revbranchcache': True,
58 },
58 },
59 b'packed1': {b'cg.version': b's1'},
59 b'packed1': {b'cg.version': b's1'},
60 }
60 }
61 _bundlespeccontentopts[b'bundle2'] = _bundlespeccontentopts[b'v2']
61 _bundlespeccontentopts[b'bundle2'] = _bundlespeccontentopts[b'v2']
62
62
63 _bundlespecvariants = {
63 _bundlespecvariants = {
64 b"streamv2": {
64 b"streamv2": {
65 b"changegroup": False,
65 b"changegroup": False,
66 b"streamv2": True,
66 b"streamv2": True,
67 b"tagsfnodescache": False,
67 b"tagsfnodescache": False,
68 b"revbranchcache": False,
68 b"revbranchcache": False,
69 }
69 }
70 }
70 }
71
71
72 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
72 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
73 _bundlespecv1compengines = {b'gzip', b'bzip2', b'none'}
73 _bundlespecv1compengines = {b'gzip', b'bzip2', b'none'}
74
74
75
75
76 def parsebundlespec(repo, spec, strict=True):
76 def parsebundlespec(repo, spec, strict=True):
77 """Parse a bundle string specification into parts.
77 """Parse a bundle string specification into parts.
78
78
79 Bundle specifications denote a well-defined bundle/exchange format.
79 Bundle specifications denote a well-defined bundle/exchange format.
80 The content of a given specification should not change over time in
80 The content of a given specification should not change over time in
81 order to ensure that bundles produced by a newer version of Mercurial are
81 order to ensure that bundles produced by a newer version of Mercurial are
82 readable from an older version.
82 readable from an older version.
83
83
84 The string currently has the form:
84 The string currently has the form:
85
85
86 <compression>-<type>[;<parameter0>[;<parameter1>]]
86 <compression>-<type>[;<parameter0>[;<parameter1>]]
87
87
88 Where <compression> is one of the supported compression formats
88 Where <compression> is one of the supported compression formats
89 and <type> is (currently) a version string. A ";" can follow the type and
89 and <type> is (currently) a version string. A ";" can follow the type and
90 all text afterwards is interpreted as URI encoded, ";" delimited key=value
90 all text afterwards is interpreted as URI encoded, ";" delimited key=value
91 pairs.
91 pairs.
92
92
93 If ``strict`` is True (the default) <compression> is required. Otherwise,
93 If ``strict`` is True (the default) <compression> is required. Otherwise,
94 it is optional.
94 it is optional.
95
95
96 Returns a bundlespec object of (compression, version, parameters).
96 Returns a bundlespec object of (compression, version, parameters).
97 Compression will be ``None`` if not in strict mode and a compression isn't
97 Compression will be ``None`` if not in strict mode and a compression isn't
98 defined.
98 defined.
99
99
100 An ``InvalidBundleSpecification`` is raised when the specification is
100 An ``InvalidBundleSpecification`` is raised when the specification is
101 not syntactically well formed.
101 not syntactically well formed.
102
102
103 An ``UnsupportedBundleSpecification`` is raised when the compression or
103 An ``UnsupportedBundleSpecification`` is raised when the compression or
104 bundle type/version is not recognized.
104 bundle type/version is not recognized.
105
105
106 Note: this function will likely eventually return a more complex data
106 Note: this function will likely eventually return a more complex data
107 structure, including bundle2 part information.
107 structure, including bundle2 part information.
108 """
108 """
109
109
110 def parseparams(s):
110 def parseparams(s):
111 if b';' not in s:
111 if b';' not in s:
112 return s, {}
112 return s, {}
113
113
114 params = {}
114 params = {}
115 version, paramstr = s.split(b';', 1)
115 version, paramstr = s.split(b';', 1)
116
116
117 for p in paramstr.split(b';'):
117 for p in paramstr.split(b';'):
118 if b'=' not in p:
118 if b'=' not in p:
119 raise error.InvalidBundleSpecification(
119 raise error.InvalidBundleSpecification(
120 _(
120 _(
121 b'invalid bundle specification: '
121 b'invalid bundle specification: '
122 b'missing "=" in parameter: %s'
122 b'missing "=" in parameter: %s'
123 )
123 )
124 % p
124 % p
125 )
125 )
126
126
127 key, value = p.split(b'=', 1)
127 key, value = p.split(b'=', 1)
128 key = urlreq.unquote(key)
128 key = urlreq.unquote(key)
129 value = urlreq.unquote(value)
129 value = urlreq.unquote(value)
130 params[key] = value
130 params[key] = value
131
131
132 return version, params
132 return version, params
133
133
134 if strict and b'-' not in spec:
134 if strict and b'-' not in spec:
135 raise error.InvalidBundleSpecification(
135 raise error.InvalidBundleSpecification(
136 _(
136 _(
137 b'invalid bundle specification; '
137 b'invalid bundle specification; '
138 b'must be prefixed with compression: %s'
138 b'must be prefixed with compression: %s'
139 )
139 )
140 % spec
140 % spec
141 )
141 )
142
142
143 if b'-' in spec:
143 if b'-' in spec:
144 compression, version = spec.split(b'-', 1)
144 compression, version = spec.split(b'-', 1)
145
145
146 if compression not in util.compengines.supportedbundlenames:
146 if compression not in util.compengines.supportedbundlenames:
147 raise error.UnsupportedBundleSpecification(
147 raise error.UnsupportedBundleSpecification(
148 _(b'%s compression is not supported') % compression
148 _(b'%s compression is not supported') % compression
149 )
149 )
150
150
151 version, params = parseparams(version)
151 version, params = parseparams(version)
152
152
153 if version not in _bundlespeccgversions:
153 if version not in _bundlespeccgversions:
154 raise error.UnsupportedBundleSpecification(
154 raise error.UnsupportedBundleSpecification(
155 _(b'%s is not a recognized bundle version') % version
155 _(b'%s is not a recognized bundle version') % version
156 )
156 )
157 else:
157 else:
158 # Value could be just the compression or just the version, in which
158 # Value could be just the compression or just the version, in which
159 # case some defaults are assumed (but only when not in strict mode).
159 # case some defaults are assumed (but only when not in strict mode).
160 assert not strict
160 assert not strict
161
161
162 spec, params = parseparams(spec)
162 spec, params = parseparams(spec)
163
163
164 if spec in util.compengines.supportedbundlenames:
164 if spec in util.compengines.supportedbundlenames:
165 compression = spec
165 compression = spec
166 version = b'v1'
166 version = b'v1'
167 # Generaldelta repos require v2.
167 # Generaldelta repos require v2.
168 if requirementsmod.GENERALDELTA_REQUIREMENT in repo.requirements:
168 if requirementsmod.GENERALDELTA_REQUIREMENT in repo.requirements:
169 version = b'v2'
169 version = b'v2'
170 elif requirementsmod.REVLOGV2_REQUIREMENT in repo.requirements:
171 version = b'v2'
170 # Modern compression engines require v2.
172 # Modern compression engines require v2.
171 if compression not in _bundlespecv1compengines:
173 if compression not in _bundlespecv1compengines:
172 version = b'v2'
174 version = b'v2'
173 elif spec in _bundlespeccgversions:
175 elif spec in _bundlespeccgversions:
174 if spec == b'packed1':
176 if spec == b'packed1':
175 compression = b'none'
177 compression = b'none'
176 else:
178 else:
177 compression = b'bzip2'
179 compression = b'bzip2'
178 version = spec
180 version = spec
179 else:
181 else:
180 raise error.UnsupportedBundleSpecification(
182 raise error.UnsupportedBundleSpecification(
181 _(b'%s is not a recognized bundle specification') % spec
183 _(b'%s is not a recognized bundle specification') % spec
182 )
184 )
183
185
184 # Bundle version 1 only supports a known set of compression engines.
186 # Bundle version 1 only supports a known set of compression engines.
185 if version == b'v1' and compression not in _bundlespecv1compengines:
187 if version == b'v1' and compression not in _bundlespecv1compengines:
186 raise error.UnsupportedBundleSpecification(
188 raise error.UnsupportedBundleSpecification(
187 _(b'compression engine %s is not supported on v1 bundles')
189 _(b'compression engine %s is not supported on v1 bundles')
188 % compression
190 % compression
189 )
191 )
190
192
191 # The specification for packed1 can optionally declare the data formats
193 # The specification for packed1 can optionally declare the data formats
192 # required to apply it. If we see this metadata, compare against what the
194 # required to apply it. If we see this metadata, compare against what the
193 # repo supports and error if the bundle isn't compatible.
195 # repo supports and error if the bundle isn't compatible.
194 if version == b'packed1' and b'requirements' in params:
196 if version == b'packed1' and b'requirements' in params:
195 requirements = set(params[b'requirements'].split(b','))
197 requirements = set(params[b'requirements'].split(b','))
196 missingreqs = requirements - repo.supportedformats
198 missingreqs = requirements - repo.supportedformats
197 if missingreqs:
199 if missingreqs:
198 raise error.UnsupportedBundleSpecification(
200 raise error.UnsupportedBundleSpecification(
199 _(b'missing support for repository features: %s')
201 _(b'missing support for repository features: %s')
200 % b', '.join(sorted(missingreqs))
202 % b', '.join(sorted(missingreqs))
201 )
203 )
202
204
203 # Compute contentopts based on the version
205 # Compute contentopts based on the version
204 contentopts = _bundlespeccontentopts.get(version, {}).copy()
206 contentopts = _bundlespeccontentopts.get(version, {}).copy()
205
207
206 # Process the variants
208 # Process the variants
207 if b"stream" in params and params[b"stream"] == b"v2":
209 if b"stream" in params and params[b"stream"] == b"v2":
208 variant = _bundlespecvariants[b"streamv2"]
210 variant = _bundlespecvariants[b"streamv2"]
209 contentopts.update(variant)
211 contentopts.update(variant)
210
212
211 engine = util.compengines.forbundlename(compression)
213 engine = util.compengines.forbundlename(compression)
212 compression, wirecompression = engine.bundletype()
214 compression, wirecompression = engine.bundletype()
213 wireversion = _bundlespeccgversions[version]
215 wireversion = _bundlespeccgversions[version]
214
216
215 return bundlespec(
217 return bundlespec(
216 compression, wirecompression, version, wireversion, params, contentopts
218 compression, wirecompression, version, wireversion, params, contentopts
217 )
219 )
218
220
219
221
220 def parseclonebundlesmanifest(repo, s):
222 def parseclonebundlesmanifest(repo, s):
221 """Parses the raw text of a clone bundles manifest.
223 """Parses the raw text of a clone bundles manifest.
222
224
223 Returns a list of dicts. The dicts have a ``URL`` key corresponding
225 Returns a list of dicts. The dicts have a ``URL`` key corresponding
224 to the URL and other keys are the attributes for the entry.
226 to the URL and other keys are the attributes for the entry.
225 """
227 """
226 m = []
228 m = []
227 for line in s.splitlines():
229 for line in s.splitlines():
228 fields = line.split()
230 fields = line.split()
229 if not fields:
231 if not fields:
230 continue
232 continue
231 attrs = {b'URL': fields[0]}
233 attrs = {b'URL': fields[0]}
232 for rawattr in fields[1:]:
234 for rawattr in fields[1:]:
233 key, value = rawattr.split(b'=', 1)
235 key, value = rawattr.split(b'=', 1)
234 key = util.urlreq.unquote(key)
236 key = util.urlreq.unquote(key)
235 value = util.urlreq.unquote(value)
237 value = util.urlreq.unquote(value)
236 attrs[key] = value
238 attrs[key] = value
237
239
238 # Parse BUNDLESPEC into components. This makes client-side
240 # Parse BUNDLESPEC into components. This makes client-side
239 # preferences easier to specify since you can prefer a single
241 # preferences easier to specify since you can prefer a single
240 # component of the BUNDLESPEC.
242 # component of the BUNDLESPEC.
241 if key == b'BUNDLESPEC':
243 if key == b'BUNDLESPEC':
242 try:
244 try:
243 bundlespec = parsebundlespec(repo, value)
245 bundlespec = parsebundlespec(repo, value)
244 attrs[b'COMPRESSION'] = bundlespec.compression
246 attrs[b'COMPRESSION'] = bundlespec.compression
245 attrs[b'VERSION'] = bundlespec.version
247 attrs[b'VERSION'] = bundlespec.version
246 except error.InvalidBundleSpecification:
248 except error.InvalidBundleSpecification:
247 pass
249 pass
248 except error.UnsupportedBundleSpecification:
250 except error.UnsupportedBundleSpecification:
249 pass
251 pass
250
252
251 m.append(attrs)
253 m.append(attrs)
252
254
253 return m
255 return m
254
256
255
257
256 def isstreamclonespec(bundlespec):
258 def isstreamclonespec(bundlespec):
257 # Stream clone v1
259 # Stream clone v1
258 if bundlespec.wirecompression == b'UN' and bundlespec.wireversion == b's1':
260 if bundlespec.wirecompression == b'UN' and bundlespec.wireversion == b's1':
259 return True
261 return True
260
262
261 # Stream clone v2
263 # Stream clone v2
262 if (
264 if (
263 bundlespec.wirecompression == b'UN'
265 bundlespec.wirecompression == b'UN'
264 and bundlespec.wireversion == b'02'
266 and bundlespec.wireversion == b'02'
265 and bundlespec.contentopts.get(b'streamv2')
267 and bundlespec.contentopts.get(b'streamv2')
266 ):
268 ):
267 return True
269 return True
268
270
269 return False
271 return False
270
272
271
273
272 def filterclonebundleentries(repo, entries, streamclonerequested=False):
274 def filterclonebundleentries(repo, entries, streamclonerequested=False):
273 """Remove incompatible clone bundle manifest entries.
275 """Remove incompatible clone bundle manifest entries.
274
276
275 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
277 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
276 and returns a new list consisting of only the entries that this client
278 and returns a new list consisting of only the entries that this client
277 should be able to apply.
279 should be able to apply.
278
280
279 There is no guarantee we'll be able to apply all returned entries because
281 There is no guarantee we'll be able to apply all returned entries because
280 the metadata we use to filter on may be missing or wrong.
282 the metadata we use to filter on may be missing or wrong.
281 """
283 """
282 newentries = []
284 newentries = []
283 for entry in entries:
285 for entry in entries:
284 spec = entry.get(b'BUNDLESPEC')
286 spec = entry.get(b'BUNDLESPEC')
285 if spec:
287 if spec:
286 try:
288 try:
287 bundlespec = parsebundlespec(repo, spec, strict=True)
289 bundlespec = parsebundlespec(repo, spec, strict=True)
288
290
289 # If a stream clone was requested, filter out non-streamclone
291 # If a stream clone was requested, filter out non-streamclone
290 # entries.
292 # entries.
291 if streamclonerequested and not isstreamclonespec(bundlespec):
293 if streamclonerequested and not isstreamclonespec(bundlespec):
292 repo.ui.debug(
294 repo.ui.debug(
293 b'filtering %s because not a stream clone\n'
295 b'filtering %s because not a stream clone\n'
294 % entry[b'URL']
296 % entry[b'URL']
295 )
297 )
296 continue
298 continue
297
299
298 except error.InvalidBundleSpecification as e:
300 except error.InvalidBundleSpecification as e:
299 repo.ui.debug(stringutil.forcebytestr(e) + b'\n')
301 repo.ui.debug(stringutil.forcebytestr(e) + b'\n')
300 continue
302 continue
301 except error.UnsupportedBundleSpecification as e:
303 except error.UnsupportedBundleSpecification as e:
302 repo.ui.debug(
304 repo.ui.debug(
303 b'filtering %s because unsupported bundle '
305 b'filtering %s because unsupported bundle '
304 b'spec: %s\n' % (entry[b'URL'], stringutil.forcebytestr(e))
306 b'spec: %s\n' % (entry[b'URL'], stringutil.forcebytestr(e))
305 )
307 )
306 continue
308 continue
307 # If we don't have a spec and requested a stream clone, we don't know
309 # If we don't have a spec and requested a stream clone, we don't know
308 # what the entry is so don't attempt to apply it.
310 # what the entry is so don't attempt to apply it.
309 elif streamclonerequested:
311 elif streamclonerequested:
310 repo.ui.debug(
312 repo.ui.debug(
311 b'filtering %s because cannot determine if a stream '
313 b'filtering %s because cannot determine if a stream '
312 b'clone bundle\n' % entry[b'URL']
314 b'clone bundle\n' % entry[b'URL']
313 )
315 )
314 continue
316 continue
315
317
316 if b'REQUIRESNI' in entry and not sslutil.hassni:
318 if b'REQUIRESNI' in entry and not sslutil.hassni:
317 repo.ui.debug(
319 repo.ui.debug(
318 b'filtering %s because SNI not supported\n' % entry[b'URL']
320 b'filtering %s because SNI not supported\n' % entry[b'URL']
319 )
321 )
320 continue
322 continue
321
323
322 if b'REQUIREDRAM' in entry:
324 if b'REQUIREDRAM' in entry:
323 try:
325 try:
324 requiredram = util.sizetoint(entry[b'REQUIREDRAM'])
326 requiredram = util.sizetoint(entry[b'REQUIREDRAM'])
325 except error.ParseError:
327 except error.ParseError:
326 repo.ui.debug(
328 repo.ui.debug(
327 b'filtering %s due to a bad REQUIREDRAM attribute\n'
329 b'filtering %s due to a bad REQUIREDRAM attribute\n'
328 % entry[b'URL']
330 % entry[b'URL']
329 )
331 )
330 continue
332 continue
331 actualram = repo.ui.estimatememory()
333 actualram = repo.ui.estimatememory()
332 if actualram is not None and actualram * 0.66 < requiredram:
334 if actualram is not None and actualram * 0.66 < requiredram:
333 repo.ui.debug(
335 repo.ui.debug(
334 b'filtering %s as it needs more than 2/3 of system memory\n'
336 b'filtering %s as it needs more than 2/3 of system memory\n'
335 % entry[b'URL']
337 % entry[b'URL']
336 )
338 )
337 continue
339 continue
338
340
339 newentries.append(entry)
341 newentries.append(entry)
340
342
341 return newentries
343 return newentries
342
344
343
345
344 class clonebundleentry(object):
346 class clonebundleentry(object):
345 """Represents an item in a clone bundles manifest.
347 """Represents an item in a clone bundles manifest.
346
348
347 This rich class is needed to support sorting since sorted() in Python 3
349 This rich class is needed to support sorting since sorted() in Python 3
348 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
350 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
349 won't work.
351 won't work.
350 """
352 """
351
353
352 def __init__(self, value, prefers):
354 def __init__(self, value, prefers):
353 self.value = value
355 self.value = value
354 self.prefers = prefers
356 self.prefers = prefers
355
357
356 def _cmp(self, other):
358 def _cmp(self, other):
357 for prefkey, prefvalue in self.prefers:
359 for prefkey, prefvalue in self.prefers:
358 avalue = self.value.get(prefkey)
360 avalue = self.value.get(prefkey)
359 bvalue = other.value.get(prefkey)
361 bvalue = other.value.get(prefkey)
360
362
361 # Special case for b missing attribute and a matches exactly.
363 # Special case for b missing attribute and a matches exactly.
362 if avalue is not None and bvalue is None and avalue == prefvalue:
364 if avalue is not None and bvalue is None and avalue == prefvalue:
363 return -1
365 return -1
364
366
365 # Special case for a missing attribute and b matches exactly.
367 # Special case for a missing attribute and b matches exactly.
366 if bvalue is not None and avalue is None and bvalue == prefvalue:
368 if bvalue is not None and avalue is None and bvalue == prefvalue:
367 return 1
369 return 1
368
370
369 # We can't compare unless attribute present on both.
371 # We can't compare unless attribute present on both.
370 if avalue is None or bvalue is None:
372 if avalue is None or bvalue is None:
371 continue
373 continue
372
374
373 # Same values should fall back to next attribute.
375 # Same values should fall back to next attribute.
374 if avalue == bvalue:
376 if avalue == bvalue:
375 continue
377 continue
376
378
377 # Exact matches come first.
379 # Exact matches come first.
378 if avalue == prefvalue:
380 if avalue == prefvalue:
379 return -1
381 return -1
380 if bvalue == prefvalue:
382 if bvalue == prefvalue:
381 return 1
383 return 1
382
384
383 # Fall back to next attribute.
385 # Fall back to next attribute.
384 continue
386 continue
385
387
386 # If we got here we couldn't sort by attributes and prefers. Fall
388 # If we got here we couldn't sort by attributes and prefers. Fall
387 # back to index order.
389 # back to index order.
388 return 0
390 return 0
389
391
390 def __lt__(self, other):
392 def __lt__(self, other):
391 return self._cmp(other) < 0
393 return self._cmp(other) < 0
392
394
393 def __gt__(self, other):
395 def __gt__(self, other):
394 return self._cmp(other) > 0
396 return self._cmp(other) > 0
395
397
396 def __eq__(self, other):
398 def __eq__(self, other):
397 return self._cmp(other) == 0
399 return self._cmp(other) == 0
398
400
399 def __le__(self, other):
401 def __le__(self, other):
400 return self._cmp(other) <= 0
402 return self._cmp(other) <= 0
401
403
402 def __ge__(self, other):
404 def __ge__(self, other):
403 return self._cmp(other) >= 0
405 return self._cmp(other) >= 0
404
406
405 def __ne__(self, other):
407 def __ne__(self, other):
406 return self._cmp(other) != 0
408 return self._cmp(other) != 0
407
409
408
410
409 def sortclonebundleentries(ui, entries):
411 def sortclonebundleentries(ui, entries):
410 prefers = ui.configlist(b'ui', b'clonebundleprefers')
412 prefers = ui.configlist(b'ui', b'clonebundleprefers')
411 if not prefers:
413 if not prefers:
412 return list(entries)
414 return list(entries)
413
415
414 def _split(p):
416 def _split(p):
415 if b'=' not in p:
417 if b'=' not in p:
416 hint = _(b"each comma separated item should be key=value pairs")
418 hint = _(b"each comma separated item should be key=value pairs")
417 raise error.Abort(
419 raise error.Abort(
418 _(b"invalid ui.clonebundleprefers item: %s") % p, hint=hint
420 _(b"invalid ui.clonebundleprefers item: %s") % p, hint=hint
419 )
421 )
420 return p.split(b'=', 1)
422 return p.split(b'=', 1)
421
423
422 prefers = [_split(p) for p in prefers]
424 prefers = [_split(p) for p in prefers]
423
425
424 items = sorted(clonebundleentry(v, prefers) for v in entries)
426 items = sorted(clonebundleentry(v, prefers) for v in entries)
425 return [i.value for i in items]
427 return [i.value for i in items]
@@ -1,86 +1,94 b''
1 #require reporevlogstore
1 #require reporevlogstore
2
2
3 A repo with unknown revlogv2 requirement string cannot be opened
3 A repo with unknown revlogv2 requirement string cannot be opened
4
4
5 $ hg init invalidreq
5 $ hg init invalidreq
6 $ cd invalidreq
6 $ cd invalidreq
7 $ echo exp-revlogv2.unknown >> .hg/requires
7 $ echo exp-revlogv2.unknown >> .hg/requires
8 $ hg log
8 $ hg log
9 abort: repository requires features unknown to this Mercurial: exp-revlogv2.unknown
9 abort: repository requires features unknown to this Mercurial: exp-revlogv2.unknown
10 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
10 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
11 [255]
11 [255]
12 $ cd ..
12 $ cd ..
13
13
14 Can create and open repo with revlog v2 requirement
14 Can create and open repo with revlog v2 requirement
15
15
16 $ cat >> $HGRCPATH << EOF
16 $ cat >> $HGRCPATH << EOF
17 > [experimental]
17 > [experimental]
18 > revlogv2 = enable-unstable-format-and-corrupt-my-data
18 > revlogv2 = enable-unstable-format-and-corrupt-my-data
19 > EOF
19 > EOF
20
20
21 $ hg init empty-repo
21 $ hg init empty-repo
22 $ cd empty-repo
22 $ cd empty-repo
23 $ cat .hg/requires
23 $ cat .hg/requires
24 dotencode
24 dotencode
25 exp-dirstate-v2 (dirstate-v2 !)
25 exp-dirstate-v2 (dirstate-v2 !)
26 exp-revlogv2.2
26 exp-revlogv2.2
27 fncache
27 fncache
28 generaldelta
28 generaldelta
29 persistent-nodemap (rust !)
29 persistent-nodemap (rust !)
30 revlog-compression-zstd (zstd !)
30 revlog-compression-zstd (zstd !)
31 sparserevlog
31 sparserevlog
32 store
32 store
33
33
34 $ hg log
34 $ hg log
35
35
36 Unknown flags to revlog are rejected
36 Unknown flags to revlog are rejected
37
37
38 >>> with open('.hg/store/00changelog.i', 'wb') as fh:
38 >>> with open('.hg/store/00changelog.i', 'wb') as fh:
39 ... fh.write(b'\xff\x00\xde\xad') and None
39 ... fh.write(b'\xff\x00\xde\xad') and None
40
40
41 $ hg log
41 $ hg log
42 abort: unknown flags (0xff00) in version 57005 revlog 00changelog
42 abort: unknown flags (0xff00) in version 57005 revlog 00changelog
43 [50]
43 [50]
44
44
45 $ cd ..
45 $ cd ..
46
46
47 Writing a simple revlog v2 works
47 Writing a simple revlog v2 works
48
48
49 $ hg init simple
49 $ hg init simple
50 $ cd simple
50 $ cd simple
51 $ touch foo
51 $ touch foo
52 $ hg -q commit -A -m initial
52 $ hg -q commit -A -m initial
53
53
54 $ hg log
54 $ hg log
55 changeset: 0:96ee1d7354c4
55 changeset: 0:96ee1d7354c4
56 tag: tip
56 tag: tip
57 user: test
57 user: test
58 date: Thu Jan 01 00:00:00 1970 +0000
58 date: Thu Jan 01 00:00:00 1970 +0000
59 summary: initial
59 summary: initial
60
60
61
61
62 Header written as expected
62 Header written as expected
63
63
64 $ f --hexdump --bytes 4 .hg/store/00changelog.i
64 $ f --hexdump --bytes 4 .hg/store/00changelog.i
65 .hg/store/00changelog.i:
65 .hg/store/00changelog.i:
66 0000: 00 00 de ad |....|
66 0000: 00 00 de ad |....|
67
67
68 $ f --hexdump --bytes 4 .hg/store/data/foo.i
68 $ f --hexdump --bytes 4 .hg/store/data/foo.i
69 .hg/store/data/foo.i:
69 .hg/store/data/foo.i:
70 0000: 00 00 de ad |....|
70 0000: 00 00 de ad |....|
71
71
72 Bundle use a compatible changegroup format
73 ------------------------------------------
74
75 $ hg bundle --all ../basic.hg
76 1 changesets found
77 $ hg debugbundle --spec ../basic.hg
78 bzip2-v2
79
72 The expected files are generated
80 The expected files are generated
73 --------------------------------
81 --------------------------------
74
82
75 We should have have:
83 We should have have:
76 - a docket
84 - a docket
77 - a index file with a unique name
85 - a index file with a unique name
78 - a data file
86 - a data file
79
87
80 $ ls .hg/store/00changelog* .hg/store/00manifest*
88 $ ls .hg/store/00changelog* .hg/store/00manifest*
81 .hg/store/00changelog-6b8ab34b.dat
89 .hg/store/00changelog-6b8ab34b.dat
82 .hg/store/00changelog-88698448.idx
90 .hg/store/00changelog-88698448.idx
83 .hg/store/00changelog.i
91 .hg/store/00changelog.i
84 .hg/store/00manifest-1335303a.dat
92 .hg/store/00manifest-1335303a.dat
85 .hg/store/00manifest-b875dfc5.idx
93 .hg/store/00manifest-b875dfc5.idx
86 .hg/store/00manifest.i
94 .hg/store/00manifest.i
General Comments 0
You need to be logged in to leave comments. Login now