##// END OF EJS Templates
streamclone: new server config and some API changes for narrow stream clones...
Pulkit Goyal -
r40374:af62936c default
parent child Browse files
Show More
@@ -0,0 +1,39
1 Tests narrow stream clones
2
3 $ . "$TESTDIR/narrow-library.sh"
4
5 Server setup
6
7 $ hg init master
8 $ cd master
9 $ mkdir dir
10 $ mkdir dir/src
11 $ cd dir/src
12 $ for x in `$TESTDIR/seq.py 20`; do echo $x > "f$x"; hg add "f$x"; hg commit -m "Commit src $x"; done
13
14 $ cd ..
15 $ mkdir tests
16 $ cd tests
17 $ for x in `$TESTDIR/seq.py 20`; do echo $x > "f$x"; hg add "f$x"; hg commit -m "Commit src $x"; done
18 $ cd ../../..
19
20 Trying to stream clone when the server does not support it
21
22 $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/f10" --stream
23 streaming all changes
24 remote: abort: server does not support narrow stream clones
25 abort: pull failed on remote
26 [255]
27
28 Enable stream clone on the server
29
30 $ echo "[server]" >> master/.hg/hgrc
31 $ echo "stream-narrow-clones=True" >> master/.hg/hgrc
32
33 Cloning a specific file when stream clone is supported
34
35 $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/f10" --stream
36 streaming all changes
37 remote: abort: server does not support narrow stream clones
38 abort: pull failed on remote
39 [255]
@@ -1,2302 +1,2313
1 1 # bundle2.py - generic container format to transmit arbitrary data.
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """Handling of the new bundle2 format
8 8
9 9 The goal of bundle2 is to act as an atomically packet to transmit a set of
10 10 payloads in an application agnostic way. It consist in a sequence of "parts"
11 11 that will be handed to and processed by the application layer.
12 12
13 13
14 14 General format architecture
15 15 ===========================
16 16
17 17 The format is architectured as follow
18 18
19 19 - magic string
20 20 - stream level parameters
21 21 - payload parts (any number)
22 22 - end of stream marker.
23 23
24 24 the Binary format
25 25 ============================
26 26
27 27 All numbers are unsigned and big-endian.
28 28
29 29 stream level parameters
30 30 ------------------------
31 31
32 32 Binary format is as follow
33 33
34 34 :params size: int32
35 35
36 36 The total number of Bytes used by the parameters
37 37
38 38 :params value: arbitrary number of Bytes
39 39
40 40 A blob of `params size` containing the serialized version of all stream level
41 41 parameters.
42 42
43 43 The blob contains a space separated list of parameters. Parameters with value
44 44 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
45 45
46 46 Empty name are obviously forbidden.
47 47
48 48 Name MUST start with a letter. If this first letter is lower case, the
49 49 parameter is advisory and can be safely ignored. However when the first
50 50 letter is capital, the parameter is mandatory and the bundling process MUST
51 51 stop if he is not able to proceed it.
52 52
53 53 Stream parameters use a simple textual format for two main reasons:
54 54
55 55 - Stream level parameters should remain simple and we want to discourage any
56 56 crazy usage.
57 57 - Textual data allow easy human inspection of a bundle2 header in case of
58 58 troubles.
59 59
60 60 Any Applicative level options MUST go into a bundle2 part instead.
61 61
62 62 Payload part
63 63 ------------------------
64 64
65 65 Binary format is as follow
66 66
67 67 :header size: int32
68 68
69 69 The total number of Bytes used by the part header. When the header is empty
70 70 (size = 0) this is interpreted as the end of stream marker.
71 71
72 72 :header:
73 73
74 74 The header defines how to interpret the part. It contains two piece of
75 75 data: the part type, and the part parameters.
76 76
77 77 The part type is used to route an application level handler, that can
78 78 interpret payload.
79 79
80 80 Part parameters are passed to the application level handler. They are
81 81 meant to convey information that will help the application level object to
82 82 interpret the part payload.
83 83
84 84 The binary format of the header is has follow
85 85
86 86 :typesize: (one byte)
87 87
88 88 :parttype: alphanumerical part name (restricted to [a-zA-Z0-9_:-]*)
89 89
90 90 :partid: A 32bits integer (unique in the bundle) that can be used to refer
91 91 to this part.
92 92
93 93 :parameters:
94 94
95 95 Part's parameter may have arbitrary content, the binary structure is::
96 96
97 97 <mandatory-count><advisory-count><param-sizes><param-data>
98 98
99 99 :mandatory-count: 1 byte, number of mandatory parameters
100 100
101 101 :advisory-count: 1 byte, number of advisory parameters
102 102
103 103 :param-sizes:
104 104
105 105 N couple of bytes, where N is the total number of parameters. Each
106 106 couple contains (<size-of-key>, <size-of-value) for one parameter.
107 107
108 108 :param-data:
109 109
110 110 A blob of bytes from which each parameter key and value can be
111 111 retrieved using the list of size couples stored in the previous
112 112 field.
113 113
114 114 Mandatory parameters comes first, then the advisory ones.
115 115
116 116 Each parameter's key MUST be unique within the part.
117 117
118 118 :payload:
119 119
120 120 payload is a series of `<chunksize><chunkdata>`.
121 121
122 122 `chunksize` is an int32, `chunkdata` are plain bytes (as much as
123 123 `chunksize` says)` The payload part is concluded by a zero size chunk.
124 124
125 125 The current implementation always produces either zero or one chunk.
126 126 This is an implementation limitation that will ultimately be lifted.
127 127
128 128 `chunksize` can be negative to trigger special case processing. No such
129 129 processing is in place yet.
130 130
131 131 Bundle processing
132 132 ============================
133 133
134 134 Each part is processed in order using a "part handler". Handler are registered
135 135 for a certain part type.
136 136
137 137 The matching of a part to its handler is case insensitive. The case of the
138 138 part type is used to know if a part is mandatory or advisory. If the Part type
139 139 contains any uppercase char it is considered mandatory. When no handler is
140 140 known for a Mandatory part, the process is aborted and an exception is raised.
141 141 If the part is advisory and no handler is known, the part is ignored. When the
142 142 process is aborted, the full bundle is still read from the stream to keep the
143 143 channel usable. But none of the part read from an abort are processed. In the
144 144 future, dropping the stream may become an option for channel we do not care to
145 145 preserve.
146 146 """
147 147
148 148 from __future__ import absolute_import, division
149 149
150 150 import collections
151 151 import errno
152 152 import os
153 153 import re
154 154 import string
155 155 import struct
156 156 import sys
157 157
158 158 from .i18n import _
159 159 from . import (
160 160 bookmarks,
161 161 changegroup,
162 162 encoding,
163 163 error,
164 164 node as nodemod,
165 165 obsolete,
166 166 phases,
167 167 pushkey,
168 168 pycompat,
169 169 streamclone,
170 170 tags,
171 171 url,
172 172 util,
173 173 )
174 174 from .utils import (
175 175 stringutil,
176 176 )
177 177
178 178 urlerr = util.urlerr
179 179 urlreq = util.urlreq
180 180
181 181 _pack = struct.pack
182 182 _unpack = struct.unpack
183 183
184 184 _fstreamparamsize = '>i'
185 185 _fpartheadersize = '>i'
186 186 _fparttypesize = '>B'
187 187 _fpartid = '>I'
188 188 _fpayloadsize = '>i'
189 189 _fpartparamcount = '>BB'
190 190
191 191 preferedchunksize = 32768
192 192
193 193 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
194 194
195 195 def outdebug(ui, message):
196 196 """debug regarding output stream (bundling)"""
197 197 if ui.configbool('devel', 'bundle2.debug'):
198 198 ui.debug('bundle2-output: %s\n' % message)
199 199
200 200 def indebug(ui, message):
201 201 """debug on input stream (unbundling)"""
202 202 if ui.configbool('devel', 'bundle2.debug'):
203 203 ui.debug('bundle2-input: %s\n' % message)
204 204
205 205 def validateparttype(parttype):
206 206 """raise ValueError if a parttype contains invalid character"""
207 207 if _parttypeforbidden.search(parttype):
208 208 raise ValueError(parttype)
209 209
210 210 def _makefpartparamsizes(nbparams):
211 211 """return a struct format to read part parameter sizes
212 212
213 213 The number parameters is variable so we need to build that format
214 214 dynamically.
215 215 """
216 216 return '>'+('BB'*nbparams)
217 217
218 218 parthandlermapping = {}
219 219
220 220 def parthandler(parttype, params=()):
221 221 """decorator that register a function as a bundle2 part handler
222 222
223 223 eg::
224 224
225 225 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
226 226 def myparttypehandler(...):
227 227 '''process a part of type "my part".'''
228 228 ...
229 229 """
230 230 validateparttype(parttype)
231 231 def _decorator(func):
232 232 lparttype = parttype.lower() # enforce lower case matching.
233 233 assert lparttype not in parthandlermapping
234 234 parthandlermapping[lparttype] = func
235 235 func.params = frozenset(params)
236 236 return func
237 237 return _decorator
238 238
239 239 class unbundlerecords(object):
240 240 """keep record of what happens during and unbundle
241 241
242 242 New records are added using `records.add('cat', obj)`. Where 'cat' is a
243 243 category of record and obj is an arbitrary object.
244 244
245 245 `records['cat']` will return all entries of this category 'cat'.
246 246
247 247 Iterating on the object itself will yield `('category', obj)` tuples
248 248 for all entries.
249 249
250 250 All iterations happens in chronological order.
251 251 """
252 252
253 253 def __init__(self):
254 254 self._categories = {}
255 255 self._sequences = []
256 256 self._replies = {}
257 257
258 258 def add(self, category, entry, inreplyto=None):
259 259 """add a new record of a given category.
260 260
261 261 The entry can then be retrieved in the list returned by
262 262 self['category']."""
263 263 self._categories.setdefault(category, []).append(entry)
264 264 self._sequences.append((category, entry))
265 265 if inreplyto is not None:
266 266 self.getreplies(inreplyto).add(category, entry)
267 267
268 268 def getreplies(self, partid):
269 269 """get the records that are replies to a specific part"""
270 270 return self._replies.setdefault(partid, unbundlerecords())
271 271
272 272 def __getitem__(self, cat):
273 273 return tuple(self._categories.get(cat, ()))
274 274
275 275 def __iter__(self):
276 276 return iter(self._sequences)
277 277
278 278 def __len__(self):
279 279 return len(self._sequences)
280 280
281 281 def __nonzero__(self):
282 282 return bool(self._sequences)
283 283
284 284 __bool__ = __nonzero__
285 285
286 286 class bundleoperation(object):
287 287 """an object that represents a single bundling process
288 288
289 289 Its purpose is to carry unbundle-related objects and states.
290 290
291 291 A new object should be created at the beginning of each bundle processing.
292 292 The object is to be returned by the processing function.
293 293
294 294 The object has very little content now it will ultimately contain:
295 295 * an access to the repo the bundle is applied to,
296 296 * a ui object,
297 297 * a way to retrieve a transaction to add changes to the repo,
298 298 * a way to record the result of processing each part,
299 299 * a way to construct a bundle response when applicable.
300 300 """
301 301
302 302 def __init__(self, repo, transactiongetter, captureoutput=True, source=''):
303 303 self.repo = repo
304 304 self.ui = repo.ui
305 305 self.records = unbundlerecords()
306 306 self.reply = None
307 307 self.captureoutput = captureoutput
308 308 self.hookargs = {}
309 309 self._gettransaction = transactiongetter
310 310 # carries value that can modify part behavior
311 311 self.modes = {}
312 312 self.source = source
313 313
314 314 def gettransaction(self):
315 315 transaction = self._gettransaction()
316 316
317 317 if self.hookargs:
318 318 # the ones added to the transaction supercede those added
319 319 # to the operation.
320 320 self.hookargs.update(transaction.hookargs)
321 321 transaction.hookargs = self.hookargs
322 322
323 323 # mark the hookargs as flushed. further attempts to add to
324 324 # hookargs will result in an abort.
325 325 self.hookargs = None
326 326
327 327 return transaction
328 328
329 329 def addhookargs(self, hookargs):
330 330 if self.hookargs is None:
331 331 raise error.ProgrammingError('attempted to add hookargs to '
332 332 'operation after transaction started')
333 333 self.hookargs.update(hookargs)
334 334
335 335 class TransactionUnavailable(RuntimeError):
336 336 pass
337 337
338 338 def _notransaction():
339 339 """default method to get a transaction while processing a bundle
340 340
341 341 Raise an exception to highlight the fact that no transaction was expected
342 342 to be created"""
343 343 raise TransactionUnavailable()
344 344
345 345 def applybundle(repo, unbundler, tr, source, url=None, **kwargs):
346 346 # transform me into unbundler.apply() as soon as the freeze is lifted
347 347 if isinstance(unbundler, unbundle20):
348 348 tr.hookargs['bundle2'] = '1'
349 349 if source is not None and 'source' not in tr.hookargs:
350 350 tr.hookargs['source'] = source
351 351 if url is not None and 'url' not in tr.hookargs:
352 352 tr.hookargs['url'] = url
353 353 return processbundle(repo, unbundler, lambda: tr, source=source)
354 354 else:
355 355 # the transactiongetter won't be used, but we might as well set it
356 356 op = bundleoperation(repo, lambda: tr, source=source)
357 357 _processchangegroup(op, unbundler, tr, source, url, **kwargs)
358 358 return op
359 359
360 360 class partiterator(object):
361 361 def __init__(self, repo, op, unbundler):
362 362 self.repo = repo
363 363 self.op = op
364 364 self.unbundler = unbundler
365 365 self.iterator = None
366 366 self.count = 0
367 367 self.current = None
368 368
369 369 def __enter__(self):
370 370 def func():
371 371 itr = enumerate(self.unbundler.iterparts())
372 372 for count, p in itr:
373 373 self.count = count
374 374 self.current = p
375 375 yield p
376 376 p.consume()
377 377 self.current = None
378 378 self.iterator = func()
379 379 return self.iterator
380 380
381 381 def __exit__(self, type, exc, tb):
382 382 if not self.iterator:
383 383 return
384 384
385 385 # Only gracefully abort in a normal exception situation. User aborts
386 386 # like Ctrl+C throw a KeyboardInterrupt which is not a base Exception,
387 387 # and should not gracefully cleanup.
388 388 if isinstance(exc, Exception):
389 389 # Any exceptions seeking to the end of the bundle at this point are
390 390 # almost certainly related to the underlying stream being bad.
391 391 # And, chances are that the exception we're handling is related to
392 392 # getting in that bad state. So, we swallow the seeking error and
393 393 # re-raise the original error.
394 394 seekerror = False
395 395 try:
396 396 if self.current:
397 397 # consume the part content to not corrupt the stream.
398 398 self.current.consume()
399 399
400 400 for part in self.iterator:
401 401 # consume the bundle content
402 402 part.consume()
403 403 except Exception:
404 404 seekerror = True
405 405
406 406 # Small hack to let caller code distinguish exceptions from bundle2
407 407 # processing from processing the old format. This is mostly needed
408 408 # to handle different return codes to unbundle according to the type
409 409 # of bundle. We should probably clean up or drop this return code
410 410 # craziness in a future version.
411 411 exc.duringunbundle2 = True
412 412 salvaged = []
413 413 replycaps = None
414 414 if self.op.reply is not None:
415 415 salvaged = self.op.reply.salvageoutput()
416 416 replycaps = self.op.reply.capabilities
417 417 exc._replycaps = replycaps
418 418 exc._bundle2salvagedoutput = salvaged
419 419
420 420 # Re-raising from a variable loses the original stack. So only use
421 421 # that form if we need to.
422 422 if seekerror:
423 423 raise exc
424 424
425 425 self.repo.ui.debug('bundle2-input-bundle: %i parts total\n' %
426 426 self.count)
427 427
428 428 def processbundle(repo, unbundler, transactiongetter=None, op=None, source=''):
429 429 """This function process a bundle, apply effect to/from a repo
430 430
431 431 It iterates over each part then searches for and uses the proper handling
432 432 code to process the part. Parts are processed in order.
433 433
434 434 Unknown Mandatory part will abort the process.
435 435
436 436 It is temporarily possible to provide a prebuilt bundleoperation to the
437 437 function. This is used to ensure output is properly propagated in case of
438 438 an error during the unbundling. This output capturing part will likely be
439 439 reworked and this ability will probably go away in the process.
440 440 """
441 441 if op is None:
442 442 if transactiongetter is None:
443 443 transactiongetter = _notransaction
444 444 op = bundleoperation(repo, transactiongetter, source=source)
445 445 # todo:
446 446 # - replace this is a init function soon.
447 447 # - exception catching
448 448 unbundler.params
449 449 if repo.ui.debugflag:
450 450 msg = ['bundle2-input-bundle:']
451 451 if unbundler.params:
452 452 msg.append(' %i params' % len(unbundler.params))
453 453 if op._gettransaction is None or op._gettransaction is _notransaction:
454 454 msg.append(' no-transaction')
455 455 else:
456 456 msg.append(' with-transaction')
457 457 msg.append('\n')
458 458 repo.ui.debug(''.join(msg))
459 459
460 460 processparts(repo, op, unbundler)
461 461
462 462 return op
463 463
464 464 def processparts(repo, op, unbundler):
465 465 with partiterator(repo, op, unbundler) as parts:
466 466 for part in parts:
467 467 _processpart(op, part)
468 468
469 469 def _processchangegroup(op, cg, tr, source, url, **kwargs):
470 470 ret = cg.apply(op.repo, tr, source, url, **kwargs)
471 471 op.records.add('changegroup', {
472 472 'return': ret,
473 473 })
474 474 return ret
475 475
476 476 def _gethandler(op, part):
477 477 status = 'unknown' # used by debug output
478 478 try:
479 479 handler = parthandlermapping.get(part.type)
480 480 if handler is None:
481 481 status = 'unsupported-type'
482 482 raise error.BundleUnknownFeatureError(parttype=part.type)
483 483 indebug(op.ui, 'found a handler for part %s' % part.type)
484 484 unknownparams = part.mandatorykeys - handler.params
485 485 if unknownparams:
486 486 unknownparams = list(unknownparams)
487 487 unknownparams.sort()
488 488 status = 'unsupported-params (%s)' % ', '.join(unknownparams)
489 489 raise error.BundleUnknownFeatureError(parttype=part.type,
490 490 params=unknownparams)
491 491 status = 'supported'
492 492 except error.BundleUnknownFeatureError as exc:
493 493 if part.mandatory: # mandatory parts
494 494 raise
495 495 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
496 496 return # skip to part processing
497 497 finally:
498 498 if op.ui.debugflag:
499 499 msg = ['bundle2-input-part: "%s"' % part.type]
500 500 if not part.mandatory:
501 501 msg.append(' (advisory)')
502 502 nbmp = len(part.mandatorykeys)
503 503 nbap = len(part.params) - nbmp
504 504 if nbmp or nbap:
505 505 msg.append(' (params:')
506 506 if nbmp:
507 507 msg.append(' %i mandatory' % nbmp)
508 508 if nbap:
509 509 msg.append(' %i advisory' % nbmp)
510 510 msg.append(')')
511 511 msg.append(' %s\n' % status)
512 512 op.ui.debug(''.join(msg))
513 513
514 514 return handler
515 515
516 516 def _processpart(op, part):
517 517 """process a single part from a bundle
518 518
519 519 The part is guaranteed to have been fully consumed when the function exits
520 520 (even if an exception is raised)."""
521 521 handler = _gethandler(op, part)
522 522 if handler is None:
523 523 return
524 524
525 525 # handler is called outside the above try block so that we don't
526 526 # risk catching KeyErrors from anything other than the
527 527 # parthandlermapping lookup (any KeyError raised by handler()
528 528 # itself represents a defect of a different variety).
529 529 output = None
530 530 if op.captureoutput and op.reply is not None:
531 531 op.ui.pushbuffer(error=True, subproc=True)
532 532 output = ''
533 533 try:
534 534 handler(op, part)
535 535 finally:
536 536 if output is not None:
537 537 output = op.ui.popbuffer()
538 538 if output:
539 539 outpart = op.reply.newpart('output', data=output,
540 540 mandatory=False)
541 541 outpart.addparam(
542 542 'in-reply-to', pycompat.bytestr(part.id), mandatory=False)
543 543
544 544 def decodecaps(blob):
545 545 """decode a bundle2 caps bytes blob into a dictionary
546 546
547 547 The blob is a list of capabilities (one per line)
548 548 Capabilities may have values using a line of the form::
549 549
550 550 capability=value1,value2,value3
551 551
552 552 The values are always a list."""
553 553 caps = {}
554 554 for line in blob.splitlines():
555 555 if not line:
556 556 continue
557 557 if '=' not in line:
558 558 key, vals = line, ()
559 559 else:
560 560 key, vals = line.split('=', 1)
561 561 vals = vals.split(',')
562 562 key = urlreq.unquote(key)
563 563 vals = [urlreq.unquote(v) for v in vals]
564 564 caps[key] = vals
565 565 return caps
566 566
567 567 def encodecaps(caps):
568 568 """encode a bundle2 caps dictionary into a bytes blob"""
569 569 chunks = []
570 570 for ca in sorted(caps):
571 571 vals = caps[ca]
572 572 ca = urlreq.quote(ca)
573 573 vals = [urlreq.quote(v) for v in vals]
574 574 if vals:
575 575 ca = "%s=%s" % (ca, ','.join(vals))
576 576 chunks.append(ca)
577 577 return '\n'.join(chunks)
578 578
579 579 bundletypes = {
580 580 "": ("", 'UN'), # only when using unbundle on ssh and old http servers
581 581 # since the unification ssh accepts a header but there
582 582 # is no capability signaling it.
583 583 "HG20": (), # special-cased below
584 584 "HG10UN": ("HG10UN", 'UN'),
585 585 "HG10BZ": ("HG10", 'BZ'),
586 586 "HG10GZ": ("HG10GZ", 'GZ'),
587 587 }
588 588
589 589 # hgweb uses this list to communicate its preferred type
590 590 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
591 591
592 592 class bundle20(object):
593 593 """represent an outgoing bundle2 container
594 594
595 595 Use the `addparam` method to add stream level parameter. and `newpart` to
596 596 populate it. Then call `getchunks` to retrieve all the binary chunks of
597 597 data that compose the bundle2 container."""
598 598
599 599 _magicstring = 'HG20'
600 600
601 601 def __init__(self, ui, capabilities=()):
602 602 self.ui = ui
603 603 self._params = []
604 604 self._parts = []
605 605 self.capabilities = dict(capabilities)
606 606 self._compengine = util.compengines.forbundletype('UN')
607 607 self._compopts = None
608 608 # If compression is being handled by a consumer of the raw
609 609 # data (e.g. the wire protocol), unsetting this flag tells
610 610 # consumers that the bundle is best left uncompressed.
611 611 self.prefercompressed = True
612 612
613 613 def setcompression(self, alg, compopts=None):
614 614 """setup core part compression to <alg>"""
615 615 if alg in (None, 'UN'):
616 616 return
617 617 assert not any(n.lower() == 'compression' for n, v in self._params)
618 618 self.addparam('Compression', alg)
619 619 self._compengine = util.compengines.forbundletype(alg)
620 620 self._compopts = compopts
621 621
622 622 @property
623 623 def nbparts(self):
624 624 """total number of parts added to the bundler"""
625 625 return len(self._parts)
626 626
627 627 # methods used to defines the bundle2 content
628 628 def addparam(self, name, value=None):
629 629 """add a stream level parameter"""
630 630 if not name:
631 631 raise error.ProgrammingError(b'empty parameter name')
632 632 if name[0:1] not in pycompat.bytestr(string.ascii_letters):
633 633 raise error.ProgrammingError(b'non letter first character: %s'
634 634 % name)
635 635 self._params.append((name, value))
636 636
637 637 def addpart(self, part):
638 638 """add a new part to the bundle2 container
639 639
640 640 Parts contains the actual applicative payload."""
641 641 assert part.id is None
642 642 part.id = len(self._parts) # very cheap counter
643 643 self._parts.append(part)
644 644
645 645 def newpart(self, typeid, *args, **kwargs):
646 646 """create a new part and add it to the containers
647 647
648 648 As the part is directly added to the containers. For now, this means
649 649 that any failure to properly initialize the part after calling
650 650 ``newpart`` should result in a failure of the whole bundling process.
651 651
652 652 You can still fall back to manually create and add if you need better
653 653 control."""
654 654 part = bundlepart(typeid, *args, **kwargs)
655 655 self.addpart(part)
656 656 return part
657 657
658 658 # methods used to generate the bundle2 stream
659 659 def getchunks(self):
660 660 if self.ui.debugflag:
661 661 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
662 662 if self._params:
663 663 msg.append(' (%i params)' % len(self._params))
664 664 msg.append(' %i parts total\n' % len(self._parts))
665 665 self.ui.debug(''.join(msg))
666 666 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
667 667 yield self._magicstring
668 668 param = self._paramchunk()
669 669 outdebug(self.ui, 'bundle parameter: %s' % param)
670 670 yield _pack(_fstreamparamsize, len(param))
671 671 if param:
672 672 yield param
673 673 for chunk in self._compengine.compressstream(self._getcorechunk(),
674 674 self._compopts):
675 675 yield chunk
676 676
677 677 def _paramchunk(self):
678 678 """return a encoded version of all stream parameters"""
679 679 blocks = []
680 680 for par, value in self._params:
681 681 par = urlreq.quote(par)
682 682 if value is not None:
683 683 value = urlreq.quote(value)
684 684 par = '%s=%s' % (par, value)
685 685 blocks.append(par)
686 686 return ' '.join(blocks)
687 687
688 688 def _getcorechunk(self):
689 689 """yield chunk for the core part of the bundle
690 690
691 691 (all but headers and parameters)"""
692 692 outdebug(self.ui, 'start of parts')
693 693 for part in self._parts:
694 694 outdebug(self.ui, 'bundle part: "%s"' % part.type)
695 695 for chunk in part.getchunks(ui=self.ui):
696 696 yield chunk
697 697 outdebug(self.ui, 'end of bundle')
698 698 yield _pack(_fpartheadersize, 0)
699 699
700 700
701 701 def salvageoutput(self):
702 702 """return a list with a copy of all output parts in the bundle
703 703
704 704 This is meant to be used during error handling to make sure we preserve
705 705 server output"""
706 706 salvaged = []
707 707 for part in self._parts:
708 708 if part.type.startswith('output'):
709 709 salvaged.append(part.copy())
710 710 return salvaged
711 711
712 712
713 713 class unpackermixin(object):
714 714 """A mixin to extract bytes and struct data from a stream"""
715 715
716 716 def __init__(self, fp):
717 717 self._fp = fp
718 718
719 719 def _unpack(self, format):
720 720 """unpack this struct format from the stream
721 721
722 722 This method is meant for internal usage by the bundle2 protocol only.
723 723 They directly manipulate the low level stream including bundle2 level
724 724 instruction.
725 725
726 726 Do not use it to implement higher-level logic or methods."""
727 727 data = self._readexact(struct.calcsize(format))
728 728 return _unpack(format, data)
729 729
730 730 def _readexact(self, size):
731 731 """read exactly <size> bytes from the stream
732 732
733 733 This method is meant for internal usage by the bundle2 protocol only.
734 734 They directly manipulate the low level stream including bundle2 level
735 735 instruction.
736 736
737 737 Do not use it to implement higher-level logic or methods."""
738 738 return changegroup.readexactly(self._fp, size)
739 739
740 740 def getunbundler(ui, fp, magicstring=None):
741 741 """return a valid unbundler object for a given magicstring"""
742 742 if magicstring is None:
743 743 magicstring = changegroup.readexactly(fp, 4)
744 744 magic, version = magicstring[0:2], magicstring[2:4]
745 745 if magic != 'HG':
746 746 ui.debug(
747 747 "error: invalid magic: %r (version %r), should be 'HG'\n"
748 748 % (magic, version))
749 749 raise error.Abort(_('not a Mercurial bundle'))
750 750 unbundlerclass = formatmap.get(version)
751 751 if unbundlerclass is None:
752 752 raise error.Abort(_('unknown bundle version %s') % version)
753 753 unbundler = unbundlerclass(ui, fp)
754 754 indebug(ui, 'start processing of %s stream' % magicstring)
755 755 return unbundler
756 756
757 757 class unbundle20(unpackermixin):
758 758 """interpret a bundle2 stream
759 759
760 760 This class is fed with a binary stream and yields parts through its
761 761 `iterparts` methods."""
762 762
763 763 _magicstring = 'HG20'
764 764
765 765 def __init__(self, ui, fp):
766 766 """If header is specified, we do not read it out of the stream."""
767 767 self.ui = ui
768 768 self._compengine = util.compengines.forbundletype('UN')
769 769 self._compressed = None
770 770 super(unbundle20, self).__init__(fp)
771 771
772 772 @util.propertycache
773 773 def params(self):
774 774 """dictionary of stream level parameters"""
775 775 indebug(self.ui, 'reading bundle2 stream parameters')
776 776 params = {}
777 777 paramssize = self._unpack(_fstreamparamsize)[0]
778 778 if paramssize < 0:
779 779 raise error.BundleValueError('negative bundle param size: %i'
780 780 % paramssize)
781 781 if paramssize:
782 782 params = self._readexact(paramssize)
783 783 params = self._processallparams(params)
784 784 return params
785 785
786 786 def _processallparams(self, paramsblock):
787 787 """"""
788 788 params = util.sortdict()
789 789 for p in paramsblock.split(' '):
790 790 p = p.split('=', 1)
791 791 p = [urlreq.unquote(i) for i in p]
792 792 if len(p) < 2:
793 793 p.append(None)
794 794 self._processparam(*p)
795 795 params[p[0]] = p[1]
796 796 return params
797 797
798 798
799 799 def _processparam(self, name, value):
800 800 """process a parameter, applying its effect if needed
801 801
802 802 Parameter starting with a lower case letter are advisory and will be
803 803 ignored when unknown. Those starting with an upper case letter are
804 804 mandatory and will this function will raise a KeyError when unknown.
805 805
806 806 Note: no option are currently supported. Any input will be either
807 807 ignored or failing.
808 808 """
809 809 if not name:
810 810 raise ValueError(r'empty parameter name')
811 811 if name[0:1] not in pycompat.bytestr(string.ascii_letters):
812 812 raise ValueError(r'non letter first character: %s' % name)
813 813 try:
814 814 handler = b2streamparamsmap[name.lower()]
815 815 except KeyError:
816 816 if name[0:1].islower():
817 817 indebug(self.ui, "ignoring unknown parameter %s" % name)
818 818 else:
819 819 raise error.BundleUnknownFeatureError(params=(name,))
820 820 else:
821 821 handler(self, name, value)
822 822
823 823 def _forwardchunks(self):
824 824 """utility to transfer a bundle2 as binary
825 825
826 826 This is made necessary by the fact the 'getbundle' command over 'ssh'
827 827 have no way to know then the reply end, relying on the bundle to be
828 828 interpreted to know its end. This is terrible and we are sorry, but we
829 829 needed to move forward to get general delta enabled.
830 830 """
831 831 yield self._magicstring
832 832 assert 'params' not in vars(self)
833 833 paramssize = self._unpack(_fstreamparamsize)[0]
834 834 if paramssize < 0:
835 835 raise error.BundleValueError('negative bundle param size: %i'
836 836 % paramssize)
837 837 yield _pack(_fstreamparamsize, paramssize)
838 838 if paramssize:
839 839 params = self._readexact(paramssize)
840 840 self._processallparams(params)
841 841 yield params
842 842 assert self._compengine.bundletype == 'UN'
843 843 # From there, payload might need to be decompressed
844 844 self._fp = self._compengine.decompressorreader(self._fp)
845 845 emptycount = 0
846 846 while emptycount < 2:
847 847 # so we can brainlessly loop
848 848 assert _fpartheadersize == _fpayloadsize
849 849 size = self._unpack(_fpartheadersize)[0]
850 850 yield _pack(_fpartheadersize, size)
851 851 if size:
852 852 emptycount = 0
853 853 else:
854 854 emptycount += 1
855 855 continue
856 856 if size == flaginterrupt:
857 857 continue
858 858 elif size < 0:
859 859 raise error.BundleValueError('negative chunk size: %i')
860 860 yield self._readexact(size)
861 861
862 862
863 863 def iterparts(self, seekable=False):
864 864 """yield all parts contained in the stream"""
865 865 cls = seekableunbundlepart if seekable else unbundlepart
866 866 # make sure param have been loaded
867 867 self.params
868 868 # From there, payload need to be decompressed
869 869 self._fp = self._compengine.decompressorreader(self._fp)
870 870 indebug(self.ui, 'start extraction of bundle2 parts')
871 871 headerblock = self._readpartheader()
872 872 while headerblock is not None:
873 873 part = cls(self.ui, headerblock, self._fp)
874 874 yield part
875 875 # Ensure part is fully consumed so we can start reading the next
876 876 # part.
877 877 part.consume()
878 878
879 879 headerblock = self._readpartheader()
880 880 indebug(self.ui, 'end of bundle2 stream')
881 881
882 882 def _readpartheader(self):
883 883 """reads a part header size and return the bytes blob
884 884
885 885 returns None if empty"""
886 886 headersize = self._unpack(_fpartheadersize)[0]
887 887 if headersize < 0:
888 888 raise error.BundleValueError('negative part header size: %i'
889 889 % headersize)
890 890 indebug(self.ui, 'part header size: %i' % headersize)
891 891 if headersize:
892 892 return self._readexact(headersize)
893 893 return None
894 894
895 895 def compressed(self):
896 896 self.params # load params
897 897 return self._compressed
898 898
899 899 def close(self):
900 900 """close underlying file"""
901 901 if util.safehasattr(self._fp, 'close'):
902 902 return self._fp.close()
903 903
904 904 formatmap = {'20': unbundle20}
905 905
906 906 b2streamparamsmap = {}
907 907
908 908 def b2streamparamhandler(name):
909 909 """register a handler for a stream level parameter"""
910 910 def decorator(func):
911 911 assert name not in formatmap
912 912 b2streamparamsmap[name] = func
913 913 return func
914 914 return decorator
915 915
916 916 @b2streamparamhandler('compression')
917 917 def processcompression(unbundler, param, value):
918 918 """read compression parameter and install payload decompression"""
919 919 if value not in util.compengines.supportedbundletypes:
920 920 raise error.BundleUnknownFeatureError(params=(param,),
921 921 values=(value,))
922 922 unbundler._compengine = util.compengines.forbundletype(value)
923 923 if value is not None:
924 924 unbundler._compressed = True
925 925
926 926 class bundlepart(object):
927 927 """A bundle2 part contains application level payload
928 928
929 929 The part `type` is used to route the part to the application level
930 930 handler.
931 931
932 932 The part payload is contained in ``part.data``. It could be raw bytes or a
933 933 generator of byte chunks.
934 934
935 935 You can add parameters to the part using the ``addparam`` method.
936 936 Parameters can be either mandatory (default) or advisory. Remote side
937 937 should be able to safely ignore the advisory ones.
938 938
939 939 Both data and parameters cannot be modified after the generation has begun.
940 940 """
941 941
942 942 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
943 943 data='', mandatory=True):
944 944 validateparttype(parttype)
945 945 self.id = None
946 946 self.type = parttype
947 947 self._data = data
948 948 self._mandatoryparams = list(mandatoryparams)
949 949 self._advisoryparams = list(advisoryparams)
950 950 # checking for duplicated entries
951 951 self._seenparams = set()
952 952 for pname, __ in self._mandatoryparams + self._advisoryparams:
953 953 if pname in self._seenparams:
954 954 raise error.ProgrammingError('duplicated params: %s' % pname)
955 955 self._seenparams.add(pname)
956 956 # status of the part's generation:
957 957 # - None: not started,
958 958 # - False: currently generated,
959 959 # - True: generation done.
960 960 self._generated = None
961 961 self.mandatory = mandatory
962 962
963 963 def __repr__(self):
964 964 cls = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
965 965 return ('<%s object at %x; id: %s; type: %s; mandatory: %s>'
966 966 % (cls, id(self), self.id, self.type, self.mandatory))
967 967
968 968 def copy(self):
969 969 """return a copy of the part
970 970
971 971 The new part have the very same content but no partid assigned yet.
972 972 Parts with generated data cannot be copied."""
973 973 assert not util.safehasattr(self.data, 'next')
974 974 return self.__class__(self.type, self._mandatoryparams,
975 975 self._advisoryparams, self._data, self.mandatory)
976 976
977 977 # methods used to defines the part content
978 978 @property
979 979 def data(self):
980 980 return self._data
981 981
982 982 @data.setter
983 983 def data(self, data):
984 984 if self._generated is not None:
985 985 raise error.ReadOnlyPartError('part is being generated')
986 986 self._data = data
987 987
988 988 @property
989 989 def mandatoryparams(self):
990 990 # make it an immutable tuple to force people through ``addparam``
991 991 return tuple(self._mandatoryparams)
992 992
993 993 @property
994 994 def advisoryparams(self):
995 995 # make it an immutable tuple to force people through ``addparam``
996 996 return tuple(self._advisoryparams)
997 997
998 998 def addparam(self, name, value='', mandatory=True):
999 999 """add a parameter to the part
1000 1000
1001 1001 If 'mandatory' is set to True, the remote handler must claim support
1002 1002 for this parameter or the unbundling will be aborted.
1003 1003
1004 1004 The 'name' and 'value' cannot exceed 255 bytes each.
1005 1005 """
1006 1006 if self._generated is not None:
1007 1007 raise error.ReadOnlyPartError('part is being generated')
1008 1008 if name in self._seenparams:
1009 1009 raise ValueError('duplicated params: %s' % name)
1010 1010 self._seenparams.add(name)
1011 1011 params = self._advisoryparams
1012 1012 if mandatory:
1013 1013 params = self._mandatoryparams
1014 1014 params.append((name, value))
1015 1015
1016 1016 # methods used to generates the bundle2 stream
1017 1017 def getchunks(self, ui):
1018 1018 if self._generated is not None:
1019 1019 raise error.ProgrammingError('part can only be consumed once')
1020 1020 self._generated = False
1021 1021
1022 1022 if ui.debugflag:
1023 1023 msg = ['bundle2-output-part: "%s"' % self.type]
1024 1024 if not self.mandatory:
1025 1025 msg.append(' (advisory)')
1026 1026 nbmp = len(self.mandatoryparams)
1027 1027 nbap = len(self.advisoryparams)
1028 1028 if nbmp or nbap:
1029 1029 msg.append(' (params:')
1030 1030 if nbmp:
1031 1031 msg.append(' %i mandatory' % nbmp)
1032 1032 if nbap:
1033 1033 msg.append(' %i advisory' % nbmp)
1034 1034 msg.append(')')
1035 1035 if not self.data:
1036 1036 msg.append(' empty payload')
1037 1037 elif (util.safehasattr(self.data, 'next')
1038 1038 or util.safehasattr(self.data, '__next__')):
1039 1039 msg.append(' streamed payload')
1040 1040 else:
1041 1041 msg.append(' %i bytes payload' % len(self.data))
1042 1042 msg.append('\n')
1043 1043 ui.debug(''.join(msg))
1044 1044
1045 1045 #### header
1046 1046 if self.mandatory:
1047 1047 parttype = self.type.upper()
1048 1048 else:
1049 1049 parttype = self.type.lower()
1050 1050 outdebug(ui, 'part %s: "%s"' % (pycompat.bytestr(self.id), parttype))
1051 1051 ## parttype
1052 1052 header = [_pack(_fparttypesize, len(parttype)),
1053 1053 parttype, _pack(_fpartid, self.id),
1054 1054 ]
1055 1055 ## parameters
1056 1056 # count
1057 1057 manpar = self.mandatoryparams
1058 1058 advpar = self.advisoryparams
1059 1059 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
1060 1060 # size
1061 1061 parsizes = []
1062 1062 for key, value in manpar:
1063 1063 parsizes.append(len(key))
1064 1064 parsizes.append(len(value))
1065 1065 for key, value in advpar:
1066 1066 parsizes.append(len(key))
1067 1067 parsizes.append(len(value))
1068 1068 paramsizes = _pack(_makefpartparamsizes(len(parsizes) // 2), *parsizes)
1069 1069 header.append(paramsizes)
1070 1070 # key, value
1071 1071 for key, value in manpar:
1072 1072 header.append(key)
1073 1073 header.append(value)
1074 1074 for key, value in advpar:
1075 1075 header.append(key)
1076 1076 header.append(value)
1077 1077 ## finalize header
1078 1078 try:
1079 1079 headerchunk = ''.join(header)
1080 1080 except TypeError:
1081 1081 raise TypeError(r'Found a non-bytes trying to '
1082 1082 r'build bundle part header: %r' % header)
1083 1083 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
1084 1084 yield _pack(_fpartheadersize, len(headerchunk))
1085 1085 yield headerchunk
1086 1086 ## payload
1087 1087 try:
1088 1088 for chunk in self._payloadchunks():
1089 1089 outdebug(ui, 'payload chunk size: %i' % len(chunk))
1090 1090 yield _pack(_fpayloadsize, len(chunk))
1091 1091 yield chunk
1092 1092 except GeneratorExit:
1093 1093 # GeneratorExit means that nobody is listening for our
1094 1094 # results anyway, so just bail quickly rather than trying
1095 1095 # to produce an error part.
1096 1096 ui.debug('bundle2-generatorexit\n')
1097 1097 raise
1098 1098 except BaseException as exc:
1099 1099 bexc = stringutil.forcebytestr(exc)
1100 1100 # backup exception data for later
1101 1101 ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
1102 1102 % bexc)
1103 1103 tb = sys.exc_info()[2]
1104 1104 msg = 'unexpected error: %s' % bexc
1105 1105 interpart = bundlepart('error:abort', [('message', msg)],
1106 1106 mandatory=False)
1107 1107 interpart.id = 0
1108 1108 yield _pack(_fpayloadsize, -1)
1109 1109 for chunk in interpart.getchunks(ui=ui):
1110 1110 yield chunk
1111 1111 outdebug(ui, 'closing payload chunk')
1112 1112 # abort current part payload
1113 1113 yield _pack(_fpayloadsize, 0)
1114 1114 pycompat.raisewithtb(exc, tb)
1115 1115 # end of payload
1116 1116 outdebug(ui, 'closing payload chunk')
1117 1117 yield _pack(_fpayloadsize, 0)
1118 1118 self._generated = True
1119 1119
1120 1120 def _payloadchunks(self):
1121 1121 """yield chunks of a the part payload
1122 1122
1123 1123 Exists to handle the different methods to provide data to a part."""
1124 1124 # we only support fixed size data now.
1125 1125 # This will be improved in the future.
1126 1126 if (util.safehasattr(self.data, 'next')
1127 1127 or util.safehasattr(self.data, '__next__')):
1128 1128 buff = util.chunkbuffer(self.data)
1129 1129 chunk = buff.read(preferedchunksize)
1130 1130 while chunk:
1131 1131 yield chunk
1132 1132 chunk = buff.read(preferedchunksize)
1133 1133 elif len(self.data):
1134 1134 yield self.data
1135 1135
1136 1136
1137 1137 flaginterrupt = -1
1138 1138
1139 1139 class interrupthandler(unpackermixin):
1140 1140 """read one part and process it with restricted capability
1141 1141
1142 1142 This allows to transmit exception raised on the producer size during part
1143 1143 iteration while the consumer is reading a part.
1144 1144
1145 1145 Part processed in this manner only have access to a ui object,"""
1146 1146
1147 1147 def __init__(self, ui, fp):
1148 1148 super(interrupthandler, self).__init__(fp)
1149 1149 self.ui = ui
1150 1150
1151 1151 def _readpartheader(self):
1152 1152 """reads a part header size and return the bytes blob
1153 1153
1154 1154 returns None if empty"""
1155 1155 headersize = self._unpack(_fpartheadersize)[0]
1156 1156 if headersize < 0:
1157 1157 raise error.BundleValueError('negative part header size: %i'
1158 1158 % headersize)
1159 1159 indebug(self.ui, 'part header size: %i\n' % headersize)
1160 1160 if headersize:
1161 1161 return self._readexact(headersize)
1162 1162 return None
1163 1163
1164 1164 def __call__(self):
1165 1165
1166 1166 self.ui.debug('bundle2-input-stream-interrupt:'
1167 1167 ' opening out of band context\n')
1168 1168 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
1169 1169 headerblock = self._readpartheader()
1170 1170 if headerblock is None:
1171 1171 indebug(self.ui, 'no part found during interruption.')
1172 1172 return
1173 1173 part = unbundlepart(self.ui, headerblock, self._fp)
1174 1174 op = interruptoperation(self.ui)
1175 1175 hardabort = False
1176 1176 try:
1177 1177 _processpart(op, part)
1178 1178 except (SystemExit, KeyboardInterrupt):
1179 1179 hardabort = True
1180 1180 raise
1181 1181 finally:
1182 1182 if not hardabort:
1183 1183 part.consume()
1184 1184 self.ui.debug('bundle2-input-stream-interrupt:'
1185 1185 ' closing out of band context\n')
1186 1186
1187 1187 class interruptoperation(object):
1188 1188 """A limited operation to be use by part handler during interruption
1189 1189
1190 1190 It only have access to an ui object.
1191 1191 """
1192 1192
1193 1193 def __init__(self, ui):
1194 1194 self.ui = ui
1195 1195 self.reply = None
1196 1196 self.captureoutput = False
1197 1197
1198 1198 @property
1199 1199 def repo(self):
1200 1200 raise error.ProgrammingError('no repo access from stream interruption')
1201 1201
1202 1202 def gettransaction(self):
1203 1203 raise TransactionUnavailable('no repo access from stream interruption')
1204 1204
1205 1205 def decodepayloadchunks(ui, fh):
1206 1206 """Reads bundle2 part payload data into chunks.
1207 1207
1208 1208 Part payload data consists of framed chunks. This function takes
1209 1209 a file handle and emits those chunks.
1210 1210 """
1211 1211 dolog = ui.configbool('devel', 'bundle2.debug')
1212 1212 debug = ui.debug
1213 1213
1214 1214 headerstruct = struct.Struct(_fpayloadsize)
1215 1215 headersize = headerstruct.size
1216 1216 unpack = headerstruct.unpack
1217 1217
1218 1218 readexactly = changegroup.readexactly
1219 1219 read = fh.read
1220 1220
1221 1221 chunksize = unpack(readexactly(fh, headersize))[0]
1222 1222 indebug(ui, 'payload chunk size: %i' % chunksize)
1223 1223
1224 1224 # changegroup.readexactly() is inlined below for performance.
1225 1225 while chunksize:
1226 1226 if chunksize >= 0:
1227 1227 s = read(chunksize)
1228 1228 if len(s) < chunksize:
1229 1229 raise error.Abort(_('stream ended unexpectedly '
1230 1230 ' (got %d bytes, expected %d)') %
1231 1231 (len(s), chunksize))
1232 1232
1233 1233 yield s
1234 1234 elif chunksize == flaginterrupt:
1235 1235 # Interrupt "signal" detected. The regular stream is interrupted
1236 1236 # and a bundle2 part follows. Consume it.
1237 1237 interrupthandler(ui, fh)()
1238 1238 else:
1239 1239 raise error.BundleValueError(
1240 1240 'negative payload chunk size: %s' % chunksize)
1241 1241
1242 1242 s = read(headersize)
1243 1243 if len(s) < headersize:
1244 1244 raise error.Abort(_('stream ended unexpectedly '
1245 1245 ' (got %d bytes, expected %d)') %
1246 1246 (len(s), chunksize))
1247 1247
1248 1248 chunksize = unpack(s)[0]
1249 1249
1250 1250 # indebug() inlined for performance.
1251 1251 if dolog:
1252 1252 debug('bundle2-input: payload chunk size: %i\n' % chunksize)
1253 1253
1254 1254 class unbundlepart(unpackermixin):
1255 1255 """a bundle part read from a bundle"""
1256 1256
1257 1257 def __init__(self, ui, header, fp):
1258 1258 super(unbundlepart, self).__init__(fp)
1259 1259 self._seekable = (util.safehasattr(fp, 'seek') and
1260 1260 util.safehasattr(fp, 'tell'))
1261 1261 self.ui = ui
1262 1262 # unbundle state attr
1263 1263 self._headerdata = header
1264 1264 self._headeroffset = 0
1265 1265 self._initialized = False
1266 1266 self.consumed = False
1267 1267 # part data
1268 1268 self.id = None
1269 1269 self.type = None
1270 1270 self.mandatoryparams = None
1271 1271 self.advisoryparams = None
1272 1272 self.params = None
1273 1273 self.mandatorykeys = ()
1274 1274 self._readheader()
1275 1275 self._mandatory = None
1276 1276 self._pos = 0
1277 1277
1278 1278 def _fromheader(self, size):
1279 1279 """return the next <size> byte from the header"""
1280 1280 offset = self._headeroffset
1281 1281 data = self._headerdata[offset:(offset + size)]
1282 1282 self._headeroffset = offset + size
1283 1283 return data
1284 1284
1285 1285 def _unpackheader(self, format):
1286 1286 """read given format from header
1287 1287
1288 1288 This automatically compute the size of the format to read."""
1289 1289 data = self._fromheader(struct.calcsize(format))
1290 1290 return _unpack(format, data)
1291 1291
1292 1292 def _initparams(self, mandatoryparams, advisoryparams):
1293 1293 """internal function to setup all logic related parameters"""
1294 1294 # make it read only to prevent people touching it by mistake.
1295 1295 self.mandatoryparams = tuple(mandatoryparams)
1296 1296 self.advisoryparams = tuple(advisoryparams)
1297 1297 # user friendly UI
1298 1298 self.params = util.sortdict(self.mandatoryparams)
1299 1299 self.params.update(self.advisoryparams)
1300 1300 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
1301 1301
1302 1302 def _readheader(self):
1303 1303 """read the header and setup the object"""
1304 1304 typesize = self._unpackheader(_fparttypesize)[0]
1305 1305 self.type = self._fromheader(typesize)
1306 1306 indebug(self.ui, 'part type: "%s"' % self.type)
1307 1307 self.id = self._unpackheader(_fpartid)[0]
1308 1308 indebug(self.ui, 'part id: "%s"' % pycompat.bytestr(self.id))
1309 1309 # extract mandatory bit from type
1310 1310 self.mandatory = (self.type != self.type.lower())
1311 1311 self.type = self.type.lower()
1312 1312 ## reading parameters
1313 1313 # param count
1314 1314 mancount, advcount = self._unpackheader(_fpartparamcount)
1315 1315 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
1316 1316 # param size
1317 1317 fparamsizes = _makefpartparamsizes(mancount + advcount)
1318 1318 paramsizes = self._unpackheader(fparamsizes)
1319 1319 # make it a list of couple again
1320 1320 paramsizes = list(zip(paramsizes[::2], paramsizes[1::2]))
1321 1321 # split mandatory from advisory
1322 1322 mansizes = paramsizes[:mancount]
1323 1323 advsizes = paramsizes[mancount:]
1324 1324 # retrieve param value
1325 1325 manparams = []
1326 1326 for key, value in mansizes:
1327 1327 manparams.append((self._fromheader(key), self._fromheader(value)))
1328 1328 advparams = []
1329 1329 for key, value in advsizes:
1330 1330 advparams.append((self._fromheader(key), self._fromheader(value)))
1331 1331 self._initparams(manparams, advparams)
1332 1332 ## part payload
1333 1333 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1334 1334 # we read the data, tell it
1335 1335 self._initialized = True
1336 1336
1337 1337 def _payloadchunks(self):
1338 1338 """Generator of decoded chunks in the payload."""
1339 1339 return decodepayloadchunks(self.ui, self._fp)
1340 1340
1341 1341 def consume(self):
1342 1342 """Read the part payload until completion.
1343 1343
1344 1344 By consuming the part data, the underlying stream read offset will
1345 1345 be advanced to the next part (or end of stream).
1346 1346 """
1347 1347 if self.consumed:
1348 1348 return
1349 1349
1350 1350 chunk = self.read(32768)
1351 1351 while chunk:
1352 1352 self._pos += len(chunk)
1353 1353 chunk = self.read(32768)
1354 1354
1355 1355 def read(self, size=None):
1356 1356 """read payload data"""
1357 1357 if not self._initialized:
1358 1358 self._readheader()
1359 1359 if size is None:
1360 1360 data = self._payloadstream.read()
1361 1361 else:
1362 1362 data = self._payloadstream.read(size)
1363 1363 self._pos += len(data)
1364 1364 if size is None or len(data) < size:
1365 1365 if not self.consumed and self._pos:
1366 1366 self.ui.debug('bundle2-input-part: total payload size %i\n'
1367 1367 % self._pos)
1368 1368 self.consumed = True
1369 1369 return data
1370 1370
1371 1371 class seekableunbundlepart(unbundlepart):
1372 1372 """A bundle2 part in a bundle that is seekable.
1373 1373
1374 1374 Regular ``unbundlepart`` instances can only be read once. This class
1375 1375 extends ``unbundlepart`` to enable bi-directional seeking within the
1376 1376 part.
1377 1377
1378 1378 Bundle2 part data consists of framed chunks. Offsets when seeking
1379 1379 refer to the decoded data, not the offsets in the underlying bundle2
1380 1380 stream.
1381 1381
1382 1382 To facilitate quickly seeking within the decoded data, instances of this
1383 1383 class maintain a mapping between offsets in the underlying stream and
1384 1384 the decoded payload. This mapping will consume memory in proportion
1385 1385 to the number of chunks within the payload (which almost certainly
1386 1386 increases in proportion with the size of the part).
1387 1387 """
1388 1388 def __init__(self, ui, header, fp):
1389 1389 # (payload, file) offsets for chunk starts.
1390 1390 self._chunkindex = []
1391 1391
1392 1392 super(seekableunbundlepart, self).__init__(ui, header, fp)
1393 1393
1394 1394 def _payloadchunks(self, chunknum=0):
1395 1395 '''seek to specified chunk and start yielding data'''
1396 1396 if len(self._chunkindex) == 0:
1397 1397 assert chunknum == 0, 'Must start with chunk 0'
1398 1398 self._chunkindex.append((0, self._tellfp()))
1399 1399 else:
1400 1400 assert chunknum < len(self._chunkindex), \
1401 1401 'Unknown chunk %d' % chunknum
1402 1402 self._seekfp(self._chunkindex[chunknum][1])
1403 1403
1404 1404 pos = self._chunkindex[chunknum][0]
1405 1405
1406 1406 for chunk in decodepayloadchunks(self.ui, self._fp):
1407 1407 chunknum += 1
1408 1408 pos += len(chunk)
1409 1409 if chunknum == len(self._chunkindex):
1410 1410 self._chunkindex.append((pos, self._tellfp()))
1411 1411
1412 1412 yield chunk
1413 1413
1414 1414 def _findchunk(self, pos):
1415 1415 '''for a given payload position, return a chunk number and offset'''
1416 1416 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
1417 1417 if ppos == pos:
1418 1418 return chunk, 0
1419 1419 elif ppos > pos:
1420 1420 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
1421 1421 raise ValueError('Unknown chunk')
1422 1422
1423 1423 def tell(self):
1424 1424 return self._pos
1425 1425
1426 1426 def seek(self, offset, whence=os.SEEK_SET):
1427 1427 if whence == os.SEEK_SET:
1428 1428 newpos = offset
1429 1429 elif whence == os.SEEK_CUR:
1430 1430 newpos = self._pos + offset
1431 1431 elif whence == os.SEEK_END:
1432 1432 if not self.consumed:
1433 1433 # Can't use self.consume() here because it advances self._pos.
1434 1434 chunk = self.read(32768)
1435 1435 while chunk:
1436 1436 chunk = self.read(32768)
1437 1437 newpos = self._chunkindex[-1][0] - offset
1438 1438 else:
1439 1439 raise ValueError('Unknown whence value: %r' % (whence,))
1440 1440
1441 1441 if newpos > self._chunkindex[-1][0] and not self.consumed:
1442 1442 # Can't use self.consume() here because it advances self._pos.
1443 1443 chunk = self.read(32768)
1444 1444 while chunk:
1445 1445 chunk = self.read(32668)
1446 1446
1447 1447 if not 0 <= newpos <= self._chunkindex[-1][0]:
1448 1448 raise ValueError('Offset out of range')
1449 1449
1450 1450 if self._pos != newpos:
1451 1451 chunk, internaloffset = self._findchunk(newpos)
1452 1452 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1453 1453 adjust = self.read(internaloffset)
1454 1454 if len(adjust) != internaloffset:
1455 1455 raise error.Abort(_('Seek failed\n'))
1456 1456 self._pos = newpos
1457 1457
1458 1458 def _seekfp(self, offset, whence=0):
1459 1459 """move the underlying file pointer
1460 1460
1461 1461 This method is meant for internal usage by the bundle2 protocol only.
1462 1462 They directly manipulate the low level stream including bundle2 level
1463 1463 instruction.
1464 1464
1465 1465 Do not use it to implement higher-level logic or methods."""
1466 1466 if self._seekable:
1467 1467 return self._fp.seek(offset, whence)
1468 1468 else:
1469 1469 raise NotImplementedError(_('File pointer is not seekable'))
1470 1470
1471 1471 def _tellfp(self):
1472 1472 """return the file offset, or None if file is not seekable
1473 1473
1474 1474 This method is meant for internal usage by the bundle2 protocol only.
1475 1475 They directly manipulate the low level stream including bundle2 level
1476 1476 instruction.
1477 1477
1478 1478 Do not use it to implement higher-level logic or methods."""
1479 1479 if self._seekable:
1480 1480 try:
1481 1481 return self._fp.tell()
1482 1482 except IOError as e:
1483 1483 if e.errno == errno.ESPIPE:
1484 1484 self._seekable = False
1485 1485 else:
1486 1486 raise
1487 1487 return None
1488 1488
1489 1489 # These are only the static capabilities.
1490 1490 # Check the 'getrepocaps' function for the rest.
1491 1491 capabilities = {'HG20': (),
1492 1492 'bookmarks': (),
1493 1493 'error': ('abort', 'unsupportedcontent', 'pushraced',
1494 1494 'pushkey'),
1495 1495 'listkeys': (),
1496 1496 'pushkey': (),
1497 1497 'digests': tuple(sorted(util.DIGESTS.keys())),
1498 1498 'remote-changegroup': ('http', 'https'),
1499 1499 'hgtagsfnodes': (),
1500 1500 'rev-branch-cache': (),
1501 1501 'phases': ('heads',),
1502 1502 'stream': ('v2',),
1503 1503 }
1504 1504
1505 1505 def getrepocaps(repo, allowpushback=False, role=None):
1506 1506 """return the bundle2 capabilities for a given repo
1507 1507
1508 1508 Exists to allow extensions (like evolution) to mutate the capabilities.
1509 1509
1510 1510 The returned value is used for servers advertising their capabilities as
1511 1511 well as clients advertising their capabilities to servers as part of
1512 1512 bundle2 requests. The ``role`` argument specifies which is which.
1513 1513 """
1514 1514 if role not in ('client', 'server'):
1515 1515 raise error.ProgrammingError('role argument must be client or server')
1516 1516
1517 1517 caps = capabilities.copy()
1518 1518 caps['changegroup'] = tuple(sorted(
1519 1519 changegroup.supportedincomingversions(repo)))
1520 1520 if obsolete.isenabled(repo, obsolete.exchangeopt):
1521 1521 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1522 1522 caps['obsmarkers'] = supportedformat
1523 1523 if allowpushback:
1524 1524 caps['pushback'] = ()
1525 1525 cpmode = repo.ui.config('server', 'concurrent-push-mode')
1526 1526 if cpmode == 'check-related':
1527 1527 caps['checkheads'] = ('related',)
1528 1528 if 'phases' in repo.ui.configlist('devel', 'legacy.exchange'):
1529 1529 caps.pop('phases')
1530 1530
1531 1531 # Don't advertise stream clone support in server mode if not configured.
1532 1532 if role == 'server':
1533 1533 streamsupported = repo.ui.configbool('server', 'uncompressed',
1534 1534 untrusted=True)
1535 1535 featuresupported = repo.ui.configbool('server', 'bundle2.stream')
1536 1536
1537 1537 if not streamsupported or not featuresupported:
1538 1538 caps.pop('stream')
1539 1539 # Else always advertise support on client, because payload support
1540 1540 # should always be advertised.
1541 1541
1542 1542 return caps
1543 1543
1544 1544 def bundle2caps(remote):
1545 1545 """return the bundle capabilities of a peer as dict"""
1546 1546 raw = remote.capable('bundle2')
1547 1547 if not raw and raw != '':
1548 1548 return {}
1549 1549 capsblob = urlreq.unquote(remote.capable('bundle2'))
1550 1550 return decodecaps(capsblob)
1551 1551
1552 1552 def obsmarkersversion(caps):
1553 1553 """extract the list of supported obsmarkers versions from a bundle2caps dict
1554 1554 """
1555 1555 obscaps = caps.get('obsmarkers', ())
1556 1556 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1557 1557
1558 1558 def writenewbundle(ui, repo, source, filename, bundletype, outgoing, opts,
1559 1559 vfs=None, compression=None, compopts=None):
1560 1560 if bundletype.startswith('HG10'):
1561 1561 cg = changegroup.makechangegroup(repo, outgoing, '01', source)
1562 1562 return writebundle(ui, cg, filename, bundletype, vfs=vfs,
1563 1563 compression=compression, compopts=compopts)
1564 1564 elif not bundletype.startswith('HG20'):
1565 1565 raise error.ProgrammingError('unknown bundle type: %s' % bundletype)
1566 1566
1567 1567 caps = {}
1568 1568 if 'obsolescence' in opts:
1569 1569 caps['obsmarkers'] = ('V1',)
1570 1570 bundle = bundle20(ui, caps)
1571 1571 bundle.setcompression(compression, compopts)
1572 1572 _addpartsfromopts(ui, repo, bundle, source, outgoing, opts)
1573 1573 chunkiter = bundle.getchunks()
1574 1574
1575 1575 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1576 1576
1577 1577 def _addpartsfromopts(ui, repo, bundler, source, outgoing, opts):
1578 1578 # We should eventually reconcile this logic with the one behind
1579 1579 # 'exchange.getbundle2partsgenerator'.
1580 1580 #
1581 1581 # The type of input from 'getbundle' and 'writenewbundle' are a bit
1582 1582 # different right now. So we keep them separated for now for the sake of
1583 1583 # simplicity.
1584 1584
1585 1585 # we might not always want a changegroup in such bundle, for example in
1586 1586 # stream bundles
1587 1587 if opts.get('changegroup', True):
1588 1588 cgversion = opts.get('cg.version')
1589 1589 if cgversion is None:
1590 1590 cgversion = changegroup.safeversion(repo)
1591 1591 cg = changegroup.makechangegroup(repo, outgoing, cgversion, source)
1592 1592 part = bundler.newpart('changegroup', data=cg.getchunks())
1593 1593 part.addparam('version', cg.version)
1594 1594 if 'clcount' in cg.extras:
1595 1595 part.addparam('nbchanges', '%d' % cg.extras['clcount'],
1596 1596 mandatory=False)
1597 1597 if opts.get('phases') and repo.revs('%ln and secret()',
1598 1598 outgoing.missingheads):
1599 1599 part.addparam('targetphase', '%d' % phases.secret, mandatory=False)
1600 1600
1601 1601 if opts.get('streamv2', False):
1602 1602 addpartbundlestream2(bundler, repo, stream=True)
1603 1603
1604 1604 if opts.get('tagsfnodescache', True):
1605 1605 addparttagsfnodescache(repo, bundler, outgoing)
1606 1606
1607 1607 if opts.get('revbranchcache', True):
1608 1608 addpartrevbranchcache(repo, bundler, outgoing)
1609 1609
1610 1610 if opts.get('obsolescence', False):
1611 1611 obsmarkers = repo.obsstore.relevantmarkers(outgoing.missing)
1612 1612 buildobsmarkerspart(bundler, obsmarkers)
1613 1613
1614 1614 if opts.get('phases', False):
1615 1615 headsbyphase = phases.subsetphaseheads(repo, outgoing.missing)
1616 1616 phasedata = phases.binaryencode(headsbyphase)
1617 1617 bundler.newpart('phase-heads', data=phasedata)
1618 1618
1619 1619 def addparttagsfnodescache(repo, bundler, outgoing):
1620 1620 # we include the tags fnode cache for the bundle changeset
1621 1621 # (as an optional parts)
1622 1622 cache = tags.hgtagsfnodescache(repo.unfiltered())
1623 1623 chunks = []
1624 1624
1625 1625 # .hgtags fnodes are only relevant for head changesets. While we could
1626 1626 # transfer values for all known nodes, there will likely be little to
1627 1627 # no benefit.
1628 1628 #
1629 1629 # We don't bother using a generator to produce output data because
1630 1630 # a) we only have 40 bytes per head and even esoteric numbers of heads
1631 1631 # consume little memory (1M heads is 40MB) b) we don't want to send the
1632 1632 # part if we don't have entries and knowing if we have entries requires
1633 1633 # cache lookups.
1634 1634 for node in outgoing.missingheads:
1635 1635 # Don't compute missing, as this may slow down serving.
1636 1636 fnode = cache.getfnode(node, computemissing=False)
1637 1637 if fnode is not None:
1638 1638 chunks.extend([node, fnode])
1639 1639
1640 1640 if chunks:
1641 1641 bundler.newpart('hgtagsfnodes', data=''.join(chunks))
1642 1642
1643 1643 def addpartrevbranchcache(repo, bundler, outgoing):
1644 1644 # we include the rev branch cache for the bundle changeset
1645 1645 # (as an optional parts)
1646 1646 cache = repo.revbranchcache()
1647 1647 cl = repo.unfiltered().changelog
1648 1648 branchesdata = collections.defaultdict(lambda: (set(), set()))
1649 1649 for node in outgoing.missing:
1650 1650 branch, close = cache.branchinfo(cl.rev(node))
1651 1651 branchesdata[branch][close].add(node)
1652 1652
1653 1653 def generate():
1654 1654 for branch, (nodes, closed) in sorted(branchesdata.items()):
1655 1655 utf8branch = encoding.fromlocal(branch)
1656 1656 yield rbcstruct.pack(len(utf8branch), len(nodes), len(closed))
1657 1657 yield utf8branch
1658 1658 for n in sorted(nodes):
1659 1659 yield n
1660 1660 for n in sorted(closed):
1661 1661 yield n
1662 1662
1663 1663 bundler.newpart('cache:rev-branch-cache', data=generate(),
1664 1664 mandatory=False)
1665 1665
1666 1666 def _formatrequirementsspec(requirements):
1667 1667 return urlreq.quote(','.join(sorted(requirements)))
1668 1668
1669 1669 def _formatrequirementsparams(requirements):
1670 1670 requirements = _formatrequirementsspec(requirements)
1671 1671 params = "%s%s" % (urlreq.quote("requirements="), requirements)
1672 1672 return params
1673 1673
1674 1674 def addpartbundlestream2(bundler, repo, **kwargs):
1675 1675 if not kwargs.get(r'stream', False):
1676 1676 return
1677 1677
1678 1678 if not streamclone.allowservergeneration(repo):
1679 1679 raise error.Abort(_('stream data requested but server does not allow '
1680 1680 'this feature'),
1681 1681 hint=_('well-behaved clients should not be '
1682 1682 'requesting stream data from servers not '
1683 1683 'advertising it; the client may be buggy'))
1684 1684
1685 1685 # Stream clones don't compress well. And compression undermines a
1686 1686 # goal of stream clones, which is to be fast. Communicate the desire
1687 1687 # to avoid compression to consumers of the bundle.
1688 1688 bundler.prefercompressed = False
1689 1689
1690 filecount, bytecount, it = streamclone.generatev2(repo)
1690 # get the includes and excludes
1691 includepats = kwargs.get(r'includepats')
1692 excludepats = kwargs.get(r'excludepats')
1693
1694 narrowstream = repo.ui.configbool('experimental.server',
1695 'stream-narrow-clones')
1696
1697 if (includepats or excludepats) and not narrowstream:
1698 raise error.Abort(_('server does not support narrow stream clones'))
1699
1700 filecount, bytecount, it = streamclone.generatev2(repo, includepats,
1701 excludepats)
1691 1702 requirements = _formatrequirementsspec(repo.requirements)
1692 1703 part = bundler.newpart('stream2', data=it)
1693 1704 part.addparam('bytecount', '%d' % bytecount, mandatory=True)
1694 1705 part.addparam('filecount', '%d' % filecount, mandatory=True)
1695 1706 part.addparam('requirements', requirements, mandatory=True)
1696 1707
1697 1708 def buildobsmarkerspart(bundler, markers):
1698 1709 """add an obsmarker part to the bundler with <markers>
1699 1710
1700 1711 No part is created if markers is empty.
1701 1712 Raises ValueError if the bundler doesn't support any known obsmarker format.
1702 1713 """
1703 1714 if not markers:
1704 1715 return None
1705 1716
1706 1717 remoteversions = obsmarkersversion(bundler.capabilities)
1707 1718 version = obsolete.commonversion(remoteversions)
1708 1719 if version is None:
1709 1720 raise ValueError('bundler does not support common obsmarker format')
1710 1721 stream = obsolete.encodemarkers(markers, True, version=version)
1711 1722 return bundler.newpart('obsmarkers', data=stream)
1712 1723
1713 1724 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None,
1714 1725 compopts=None):
1715 1726 """Write a bundle file and return its filename.
1716 1727
1717 1728 Existing files will not be overwritten.
1718 1729 If no filename is specified, a temporary file is created.
1719 1730 bz2 compression can be turned off.
1720 1731 The bundle file will be deleted in case of errors.
1721 1732 """
1722 1733
1723 1734 if bundletype == "HG20":
1724 1735 bundle = bundle20(ui)
1725 1736 bundle.setcompression(compression, compopts)
1726 1737 part = bundle.newpart('changegroup', data=cg.getchunks())
1727 1738 part.addparam('version', cg.version)
1728 1739 if 'clcount' in cg.extras:
1729 1740 part.addparam('nbchanges', '%d' % cg.extras['clcount'],
1730 1741 mandatory=False)
1731 1742 chunkiter = bundle.getchunks()
1732 1743 else:
1733 1744 # compression argument is only for the bundle2 case
1734 1745 assert compression is None
1735 1746 if cg.version != '01':
1736 1747 raise error.Abort(_('old bundle types only supports v1 '
1737 1748 'changegroups'))
1738 1749 header, comp = bundletypes[bundletype]
1739 1750 if comp not in util.compengines.supportedbundletypes:
1740 1751 raise error.Abort(_('unknown stream compression type: %s')
1741 1752 % comp)
1742 1753 compengine = util.compengines.forbundletype(comp)
1743 1754 def chunkiter():
1744 1755 yield header
1745 1756 for chunk in compengine.compressstream(cg.getchunks(), compopts):
1746 1757 yield chunk
1747 1758 chunkiter = chunkiter()
1748 1759
1749 1760 # parse the changegroup data, otherwise we will block
1750 1761 # in case of sshrepo because we don't know the end of the stream
1751 1762 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1752 1763
1753 1764 def combinechangegroupresults(op):
1754 1765 """logic to combine 0 or more addchangegroup results into one"""
1755 1766 results = [r.get('return', 0)
1756 1767 for r in op.records['changegroup']]
1757 1768 changedheads = 0
1758 1769 result = 1
1759 1770 for ret in results:
1760 1771 # If any changegroup result is 0, return 0
1761 1772 if ret == 0:
1762 1773 result = 0
1763 1774 break
1764 1775 if ret < -1:
1765 1776 changedheads += ret + 1
1766 1777 elif ret > 1:
1767 1778 changedheads += ret - 1
1768 1779 if changedheads > 0:
1769 1780 result = 1 + changedheads
1770 1781 elif changedheads < 0:
1771 1782 result = -1 + changedheads
1772 1783 return result
1773 1784
1774 1785 @parthandler('changegroup', ('version', 'nbchanges', 'treemanifest',
1775 1786 'targetphase'))
1776 1787 def handlechangegroup(op, inpart):
1777 1788 """apply a changegroup part on the repo
1778 1789
1779 1790 This is a very early implementation that will massive rework before being
1780 1791 inflicted to any end-user.
1781 1792 """
1782 1793 from . import localrepo
1783 1794
1784 1795 tr = op.gettransaction()
1785 1796 unpackerversion = inpart.params.get('version', '01')
1786 1797 # We should raise an appropriate exception here
1787 1798 cg = changegroup.getunbundler(unpackerversion, inpart, None)
1788 1799 # the source and url passed here are overwritten by the one contained in
1789 1800 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1790 1801 nbchangesets = None
1791 1802 if 'nbchanges' in inpart.params:
1792 1803 nbchangesets = int(inpart.params.get('nbchanges'))
1793 1804 if ('treemanifest' in inpart.params and
1794 1805 'treemanifest' not in op.repo.requirements):
1795 1806 if len(op.repo.changelog) != 0:
1796 1807 raise error.Abort(_(
1797 1808 "bundle contains tree manifests, but local repo is "
1798 1809 "non-empty and does not use tree manifests"))
1799 1810 op.repo.requirements.add('treemanifest')
1800 1811 op.repo.svfs.options = localrepo.resolvestorevfsoptions(
1801 1812 op.repo.ui, op.repo.requirements, op.repo.features)
1802 1813 op.repo._writerequirements()
1803 1814 extrakwargs = {}
1804 1815 targetphase = inpart.params.get('targetphase')
1805 1816 if targetphase is not None:
1806 1817 extrakwargs[r'targetphase'] = int(targetphase)
1807 1818 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2',
1808 1819 expectedtotal=nbchangesets, **extrakwargs)
1809 1820 if op.reply is not None:
1810 1821 # This is definitely not the final form of this
1811 1822 # return. But one need to start somewhere.
1812 1823 part = op.reply.newpart('reply:changegroup', mandatory=False)
1813 1824 part.addparam(
1814 1825 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
1815 1826 part.addparam('return', '%i' % ret, mandatory=False)
1816 1827 assert not inpart.read()
1817 1828
1818 1829 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1819 1830 ['digest:%s' % k for k in util.DIGESTS.keys()])
1820 1831 @parthandler('remote-changegroup', _remotechangegroupparams)
1821 1832 def handleremotechangegroup(op, inpart):
1822 1833 """apply a bundle10 on the repo, given an url and validation information
1823 1834
1824 1835 All the information about the remote bundle to import are given as
1825 1836 parameters. The parameters include:
1826 1837 - url: the url to the bundle10.
1827 1838 - size: the bundle10 file size. It is used to validate what was
1828 1839 retrieved by the client matches the server knowledge about the bundle.
1829 1840 - digests: a space separated list of the digest types provided as
1830 1841 parameters.
1831 1842 - digest:<digest-type>: the hexadecimal representation of the digest with
1832 1843 that name. Like the size, it is used to validate what was retrieved by
1833 1844 the client matches what the server knows about the bundle.
1834 1845
1835 1846 When multiple digest types are given, all of them are checked.
1836 1847 """
1837 1848 try:
1838 1849 raw_url = inpart.params['url']
1839 1850 except KeyError:
1840 1851 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1841 1852 parsed_url = util.url(raw_url)
1842 1853 if parsed_url.scheme not in capabilities['remote-changegroup']:
1843 1854 raise error.Abort(_('remote-changegroup does not support %s urls') %
1844 1855 parsed_url.scheme)
1845 1856
1846 1857 try:
1847 1858 size = int(inpart.params['size'])
1848 1859 except ValueError:
1849 1860 raise error.Abort(_('remote-changegroup: invalid value for param "%s"')
1850 1861 % 'size')
1851 1862 except KeyError:
1852 1863 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1853 1864
1854 1865 digests = {}
1855 1866 for typ in inpart.params.get('digests', '').split():
1856 1867 param = 'digest:%s' % typ
1857 1868 try:
1858 1869 value = inpart.params[param]
1859 1870 except KeyError:
1860 1871 raise error.Abort(_('remote-changegroup: missing "%s" param') %
1861 1872 param)
1862 1873 digests[typ] = value
1863 1874
1864 1875 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1865 1876
1866 1877 tr = op.gettransaction()
1867 1878 from . import exchange
1868 1879 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1869 1880 if not isinstance(cg, changegroup.cg1unpacker):
1870 1881 raise error.Abort(_('%s: not a bundle version 1.0') %
1871 1882 util.hidepassword(raw_url))
1872 1883 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2')
1873 1884 if op.reply is not None:
1874 1885 # This is definitely not the final form of this
1875 1886 # return. But one need to start somewhere.
1876 1887 part = op.reply.newpart('reply:changegroup')
1877 1888 part.addparam(
1878 1889 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
1879 1890 part.addparam('return', '%i' % ret, mandatory=False)
1880 1891 try:
1881 1892 real_part.validate()
1882 1893 except error.Abort as e:
1883 1894 raise error.Abort(_('bundle at %s is corrupted:\n%s') %
1884 1895 (util.hidepassword(raw_url), bytes(e)))
1885 1896 assert not inpart.read()
1886 1897
1887 1898 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1888 1899 def handlereplychangegroup(op, inpart):
1889 1900 ret = int(inpart.params['return'])
1890 1901 replyto = int(inpart.params['in-reply-to'])
1891 1902 op.records.add('changegroup', {'return': ret}, replyto)
1892 1903
1893 1904 @parthandler('check:bookmarks')
1894 1905 def handlecheckbookmarks(op, inpart):
1895 1906 """check location of bookmarks
1896 1907
1897 1908 This part is to be used to detect push race regarding bookmark, it
1898 1909 contains binary encoded (bookmark, node) tuple. If the local state does
1899 1910 not marks the one in the part, a PushRaced exception is raised
1900 1911 """
1901 1912 bookdata = bookmarks.binarydecode(inpart)
1902 1913
1903 1914 msgstandard = ('remote repository changed while pushing - please try again '
1904 1915 '(bookmark "%s" move from %s to %s)')
1905 1916 msgmissing = ('remote repository changed while pushing - please try again '
1906 1917 '(bookmark "%s" is missing, expected %s)')
1907 1918 msgexist = ('remote repository changed while pushing - please try again '
1908 1919 '(bookmark "%s" set on %s, expected missing)')
1909 1920 for book, node in bookdata:
1910 1921 currentnode = op.repo._bookmarks.get(book)
1911 1922 if currentnode != node:
1912 1923 if node is None:
1913 1924 finalmsg = msgexist % (book, nodemod.short(currentnode))
1914 1925 elif currentnode is None:
1915 1926 finalmsg = msgmissing % (book, nodemod.short(node))
1916 1927 else:
1917 1928 finalmsg = msgstandard % (book, nodemod.short(node),
1918 1929 nodemod.short(currentnode))
1919 1930 raise error.PushRaced(finalmsg)
1920 1931
1921 1932 @parthandler('check:heads')
1922 1933 def handlecheckheads(op, inpart):
1923 1934 """check that head of the repo did not change
1924 1935
1925 1936 This is used to detect a push race when using unbundle.
1926 1937 This replaces the "heads" argument of unbundle."""
1927 1938 h = inpart.read(20)
1928 1939 heads = []
1929 1940 while len(h) == 20:
1930 1941 heads.append(h)
1931 1942 h = inpart.read(20)
1932 1943 assert not h
1933 1944 # Trigger a transaction so that we are guaranteed to have the lock now.
1934 1945 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1935 1946 op.gettransaction()
1936 1947 if sorted(heads) != sorted(op.repo.heads()):
1937 1948 raise error.PushRaced('remote repository changed while pushing - '
1938 1949 'please try again')
1939 1950
1940 1951 @parthandler('check:updated-heads')
1941 1952 def handlecheckupdatedheads(op, inpart):
1942 1953 """check for race on the heads touched by a push
1943 1954
1944 1955 This is similar to 'check:heads' but focus on the heads actually updated
1945 1956 during the push. If other activities happen on unrelated heads, it is
1946 1957 ignored.
1947 1958
1948 1959 This allow server with high traffic to avoid push contention as long as
1949 1960 unrelated parts of the graph are involved."""
1950 1961 h = inpart.read(20)
1951 1962 heads = []
1952 1963 while len(h) == 20:
1953 1964 heads.append(h)
1954 1965 h = inpart.read(20)
1955 1966 assert not h
1956 1967 # trigger a transaction so that we are guaranteed to have the lock now.
1957 1968 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1958 1969 op.gettransaction()
1959 1970
1960 1971 currentheads = set()
1961 1972 for ls in op.repo.branchmap().itervalues():
1962 1973 currentheads.update(ls)
1963 1974
1964 1975 for h in heads:
1965 1976 if h not in currentheads:
1966 1977 raise error.PushRaced('remote repository changed while pushing - '
1967 1978 'please try again')
1968 1979
1969 1980 @parthandler('check:phases')
1970 1981 def handlecheckphases(op, inpart):
1971 1982 """check that phase boundaries of the repository did not change
1972 1983
1973 1984 This is used to detect a push race.
1974 1985 """
1975 1986 phasetonodes = phases.binarydecode(inpart)
1976 1987 unfi = op.repo.unfiltered()
1977 1988 cl = unfi.changelog
1978 1989 phasecache = unfi._phasecache
1979 1990 msg = ('remote repository changed while pushing - please try again '
1980 1991 '(%s is %s expected %s)')
1981 1992 for expectedphase, nodes in enumerate(phasetonodes):
1982 1993 for n in nodes:
1983 1994 actualphase = phasecache.phase(unfi, cl.rev(n))
1984 1995 if actualphase != expectedphase:
1985 1996 finalmsg = msg % (nodemod.short(n),
1986 1997 phases.phasenames[actualphase],
1987 1998 phases.phasenames[expectedphase])
1988 1999 raise error.PushRaced(finalmsg)
1989 2000
1990 2001 @parthandler('output')
1991 2002 def handleoutput(op, inpart):
1992 2003 """forward output captured on the server to the client"""
1993 2004 for line in inpart.read().splitlines():
1994 2005 op.ui.status(_('remote: %s\n') % line)
1995 2006
1996 2007 @parthandler('replycaps')
1997 2008 def handlereplycaps(op, inpart):
1998 2009 """Notify that a reply bundle should be created
1999 2010
2000 2011 The payload contains the capabilities information for the reply"""
2001 2012 caps = decodecaps(inpart.read())
2002 2013 if op.reply is None:
2003 2014 op.reply = bundle20(op.ui, caps)
2004 2015
2005 2016 class AbortFromPart(error.Abort):
2006 2017 """Sub-class of Abort that denotes an error from a bundle2 part."""
2007 2018
2008 2019 @parthandler('error:abort', ('message', 'hint'))
2009 2020 def handleerrorabort(op, inpart):
2010 2021 """Used to transmit abort error over the wire"""
2011 2022 raise AbortFromPart(inpart.params['message'],
2012 2023 hint=inpart.params.get('hint'))
2013 2024
2014 2025 @parthandler('error:pushkey', ('namespace', 'key', 'new', 'old', 'ret',
2015 2026 'in-reply-to'))
2016 2027 def handleerrorpushkey(op, inpart):
2017 2028 """Used to transmit failure of a mandatory pushkey over the wire"""
2018 2029 kwargs = {}
2019 2030 for name in ('namespace', 'key', 'new', 'old', 'ret'):
2020 2031 value = inpart.params.get(name)
2021 2032 if value is not None:
2022 2033 kwargs[name] = value
2023 2034 raise error.PushkeyFailed(inpart.params['in-reply-to'],
2024 2035 **pycompat.strkwargs(kwargs))
2025 2036
2026 2037 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
2027 2038 def handleerrorunsupportedcontent(op, inpart):
2028 2039 """Used to transmit unknown content error over the wire"""
2029 2040 kwargs = {}
2030 2041 parttype = inpart.params.get('parttype')
2031 2042 if parttype is not None:
2032 2043 kwargs['parttype'] = parttype
2033 2044 params = inpart.params.get('params')
2034 2045 if params is not None:
2035 2046 kwargs['params'] = params.split('\0')
2036 2047
2037 2048 raise error.BundleUnknownFeatureError(**pycompat.strkwargs(kwargs))
2038 2049
2039 2050 @parthandler('error:pushraced', ('message',))
2040 2051 def handleerrorpushraced(op, inpart):
2041 2052 """Used to transmit push race error over the wire"""
2042 2053 raise error.ResponseError(_('push failed:'), inpart.params['message'])
2043 2054
2044 2055 @parthandler('listkeys', ('namespace',))
2045 2056 def handlelistkeys(op, inpart):
2046 2057 """retrieve pushkey namespace content stored in a bundle2"""
2047 2058 namespace = inpart.params['namespace']
2048 2059 r = pushkey.decodekeys(inpart.read())
2049 2060 op.records.add('listkeys', (namespace, r))
2050 2061
2051 2062 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
2052 2063 def handlepushkey(op, inpart):
2053 2064 """process a pushkey request"""
2054 2065 dec = pushkey.decode
2055 2066 namespace = dec(inpart.params['namespace'])
2056 2067 key = dec(inpart.params['key'])
2057 2068 old = dec(inpart.params['old'])
2058 2069 new = dec(inpart.params['new'])
2059 2070 # Grab the transaction to ensure that we have the lock before performing the
2060 2071 # pushkey.
2061 2072 if op.ui.configbool('experimental', 'bundle2lazylocking'):
2062 2073 op.gettransaction()
2063 2074 ret = op.repo.pushkey(namespace, key, old, new)
2064 2075 record = {'namespace': namespace,
2065 2076 'key': key,
2066 2077 'old': old,
2067 2078 'new': new}
2068 2079 op.records.add('pushkey', record)
2069 2080 if op.reply is not None:
2070 2081 rpart = op.reply.newpart('reply:pushkey')
2071 2082 rpart.addparam(
2072 2083 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
2073 2084 rpart.addparam('return', '%i' % ret, mandatory=False)
2074 2085 if inpart.mandatory and not ret:
2075 2086 kwargs = {}
2076 2087 for key in ('namespace', 'key', 'new', 'old', 'ret'):
2077 2088 if key in inpart.params:
2078 2089 kwargs[key] = inpart.params[key]
2079 2090 raise error.PushkeyFailed(partid='%d' % inpart.id,
2080 2091 **pycompat.strkwargs(kwargs))
2081 2092
2082 2093 @parthandler('bookmarks')
2083 2094 def handlebookmark(op, inpart):
2084 2095 """transmit bookmark information
2085 2096
2086 2097 The part contains binary encoded bookmark information.
2087 2098
2088 2099 The exact behavior of this part can be controlled by the 'bookmarks' mode
2089 2100 on the bundle operation.
2090 2101
2091 2102 When mode is 'apply' (the default) the bookmark information is applied as
2092 2103 is to the unbundling repository. Make sure a 'check:bookmarks' part is
2093 2104 issued earlier to check for push races in such update. This behavior is
2094 2105 suitable for pushing.
2095 2106
2096 2107 When mode is 'records', the information is recorded into the 'bookmarks'
2097 2108 records of the bundle operation. This behavior is suitable for pulling.
2098 2109 """
2099 2110 changes = bookmarks.binarydecode(inpart)
2100 2111
2101 2112 pushkeycompat = op.repo.ui.configbool('server', 'bookmarks-pushkey-compat')
2102 2113 bookmarksmode = op.modes.get('bookmarks', 'apply')
2103 2114
2104 2115 if bookmarksmode == 'apply':
2105 2116 tr = op.gettransaction()
2106 2117 bookstore = op.repo._bookmarks
2107 2118 if pushkeycompat:
2108 2119 allhooks = []
2109 2120 for book, node in changes:
2110 2121 hookargs = tr.hookargs.copy()
2111 2122 hookargs['pushkeycompat'] = '1'
2112 2123 hookargs['namespace'] = 'bookmarks'
2113 2124 hookargs['key'] = book
2114 2125 hookargs['old'] = nodemod.hex(bookstore.get(book, ''))
2115 2126 hookargs['new'] = nodemod.hex(node if node is not None else '')
2116 2127 allhooks.append(hookargs)
2117 2128
2118 2129 for hookargs in allhooks:
2119 2130 op.repo.hook('prepushkey', throw=True,
2120 2131 **pycompat.strkwargs(hookargs))
2121 2132
2122 2133 bookstore.applychanges(op.repo, op.gettransaction(), changes)
2123 2134
2124 2135 if pushkeycompat:
2125 2136 def runhook():
2126 2137 for hookargs in allhooks:
2127 2138 op.repo.hook('pushkey', **pycompat.strkwargs(hookargs))
2128 2139 op.repo._afterlock(runhook)
2129 2140
2130 2141 elif bookmarksmode == 'records':
2131 2142 for book, node in changes:
2132 2143 record = {'bookmark': book, 'node': node}
2133 2144 op.records.add('bookmarks', record)
2134 2145 else:
2135 2146 raise error.ProgrammingError('unkown bookmark mode: %s' % bookmarksmode)
2136 2147
2137 2148 @parthandler('phase-heads')
2138 2149 def handlephases(op, inpart):
2139 2150 """apply phases from bundle part to repo"""
2140 2151 headsbyphase = phases.binarydecode(inpart)
2141 2152 phases.updatephases(op.repo.unfiltered(), op.gettransaction, headsbyphase)
2142 2153
2143 2154 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
2144 2155 def handlepushkeyreply(op, inpart):
2145 2156 """retrieve the result of a pushkey request"""
2146 2157 ret = int(inpart.params['return'])
2147 2158 partid = int(inpart.params['in-reply-to'])
2148 2159 op.records.add('pushkey', {'return': ret}, partid)
2149 2160
2150 2161 @parthandler('obsmarkers')
2151 2162 def handleobsmarker(op, inpart):
2152 2163 """add a stream of obsmarkers to the repo"""
2153 2164 tr = op.gettransaction()
2154 2165 markerdata = inpart.read()
2155 2166 if op.ui.config('experimental', 'obsmarkers-exchange-debug'):
2156 2167 op.ui.write(('obsmarker-exchange: %i bytes received\n')
2157 2168 % len(markerdata))
2158 2169 # The mergemarkers call will crash if marker creation is not enabled.
2159 2170 # we want to avoid this if the part is advisory.
2160 2171 if not inpart.mandatory and op.repo.obsstore.readonly:
2161 2172 op.repo.ui.debug('ignoring obsolescence markers, feature not enabled\n')
2162 2173 return
2163 2174 new = op.repo.obsstore.mergemarkers(tr, markerdata)
2164 2175 op.repo.invalidatevolatilesets()
2165 2176 if new:
2166 2177 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
2167 2178 op.records.add('obsmarkers', {'new': new})
2168 2179 if op.reply is not None:
2169 2180 rpart = op.reply.newpart('reply:obsmarkers')
2170 2181 rpart.addparam(
2171 2182 'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
2172 2183 rpart.addparam('new', '%i' % new, mandatory=False)
2173 2184
2174 2185
2175 2186 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
2176 2187 def handleobsmarkerreply(op, inpart):
2177 2188 """retrieve the result of a pushkey request"""
2178 2189 ret = int(inpart.params['new'])
2179 2190 partid = int(inpart.params['in-reply-to'])
2180 2191 op.records.add('obsmarkers', {'new': ret}, partid)
2181 2192
2182 2193 @parthandler('hgtagsfnodes')
2183 2194 def handlehgtagsfnodes(op, inpart):
2184 2195 """Applies .hgtags fnodes cache entries to the local repo.
2185 2196
2186 2197 Payload is pairs of 20 byte changeset nodes and filenodes.
2187 2198 """
2188 2199 # Grab the transaction so we ensure that we have the lock at this point.
2189 2200 if op.ui.configbool('experimental', 'bundle2lazylocking'):
2190 2201 op.gettransaction()
2191 2202 cache = tags.hgtagsfnodescache(op.repo.unfiltered())
2192 2203
2193 2204 count = 0
2194 2205 while True:
2195 2206 node = inpart.read(20)
2196 2207 fnode = inpart.read(20)
2197 2208 if len(node) < 20 or len(fnode) < 20:
2198 2209 op.ui.debug('ignoring incomplete received .hgtags fnodes data\n')
2199 2210 break
2200 2211 cache.setfnode(node, fnode)
2201 2212 count += 1
2202 2213
2203 2214 cache.write()
2204 2215 op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
2205 2216
2206 2217 rbcstruct = struct.Struct('>III')
2207 2218
2208 2219 @parthandler('cache:rev-branch-cache')
2209 2220 def handlerbc(op, inpart):
2210 2221 """receive a rev-branch-cache payload and update the local cache
2211 2222
2212 2223 The payload is a series of data related to each branch
2213 2224
2214 2225 1) branch name length
2215 2226 2) number of open heads
2216 2227 3) number of closed heads
2217 2228 4) open heads nodes
2218 2229 5) closed heads nodes
2219 2230 """
2220 2231 total = 0
2221 2232 rawheader = inpart.read(rbcstruct.size)
2222 2233 cache = op.repo.revbranchcache()
2223 2234 cl = op.repo.unfiltered().changelog
2224 2235 while rawheader:
2225 2236 header = rbcstruct.unpack(rawheader)
2226 2237 total += header[1] + header[2]
2227 2238 utf8branch = inpart.read(header[0])
2228 2239 branch = encoding.tolocal(utf8branch)
2229 2240 for x in pycompat.xrange(header[1]):
2230 2241 node = inpart.read(20)
2231 2242 rev = cl.rev(node)
2232 2243 cache.setdata(branch, rev, node, False)
2233 2244 for x in pycompat.xrange(header[2]):
2234 2245 node = inpart.read(20)
2235 2246 rev = cl.rev(node)
2236 2247 cache.setdata(branch, rev, node, True)
2237 2248 rawheader = inpart.read(rbcstruct.size)
2238 2249 cache.write()
2239 2250
2240 2251 @parthandler('pushvars')
2241 2252 def bundle2getvars(op, part):
2242 2253 '''unbundle a bundle2 containing shellvars on the server'''
2243 2254 # An option to disable unbundling on server-side for security reasons
2244 2255 if op.ui.configbool('push', 'pushvars.server'):
2245 2256 hookargs = {}
2246 2257 for key, value in part.advisoryparams:
2247 2258 key = key.upper()
2248 2259 # We want pushed variables to have USERVAR_ prepended so we know
2249 2260 # they came from the --pushvar flag.
2250 2261 key = "USERVAR_" + key
2251 2262 hookargs[key] = value
2252 2263 op.addhookargs(hookargs)
2253 2264
2254 2265 @parthandler('stream2', ('requirements', 'filecount', 'bytecount'))
2255 2266 def handlestreamv2bundle(op, part):
2256 2267
2257 2268 requirements = urlreq.unquote(part.params['requirements']).split(',')
2258 2269 filecount = int(part.params['filecount'])
2259 2270 bytecount = int(part.params['bytecount'])
2260 2271
2261 2272 repo = op.repo
2262 2273 if len(repo):
2263 2274 msg = _('cannot apply stream clone to non empty repository')
2264 2275 raise error.Abort(msg)
2265 2276
2266 2277 repo.ui.debug('applying stream bundle\n')
2267 2278 streamclone.applybundlev2(repo, part, filecount, bytecount,
2268 2279 requirements)
2269 2280
2270 2281 def widen_bundle(repo, diffmatcher, common, known, cgversion, ellipses):
2271 2282 """generates bundle2 for widening a narrow clone
2272 2283
2273 2284 repo is the localrepository instance
2274 2285 diffmatcher is a differencemacther of '(newincludes, newexcludes) -
2275 2286 (oldincludes, oldexcludes)'
2276 2287 common is set of common heads between server and client
2277 2288 known is a set of revs known on the client side (used in ellipses)
2278 2289 cgversion is the changegroup version to send
2279 2290 ellipses is boolean value telling whether to send ellipses data or not
2280 2291
2281 2292 returns bundle2 of the data required for extending
2282 2293 """
2283 2294 bundler = bundle20(repo.ui)
2284 2295 commonnodes = set()
2285 2296 cl = repo.changelog
2286 2297 for r in repo.revs("::%ln", common):
2287 2298 commonnodes.add(cl.node(r))
2288 2299 if commonnodes:
2289 2300 # XXX: we should only send the filelogs (and treemanifest). user
2290 2301 # already has the changelog and manifest
2291 2302 packer = changegroup.getbundler(cgversion, repo,
2292 2303 filematcher=diffmatcher,
2293 2304 fullnodes=commonnodes)
2294 2305 cgdata = packer.generate(set([nodemod.nullid]), list(commonnodes),
2295 2306 False, 'narrow_widen', changelog=False)
2296 2307
2297 2308 part = bundler.newpart('changegroup', data=cgdata)
2298 2309 part.addparam('version', cgversion)
2299 2310 if 'treemanifest' in repo.requirements:
2300 2311 part.addparam('treemanifest', '1')
2301 2312
2302 2313 return bundler
@@ -1,1424 +1,1427
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import functools
11 11 import re
12 12
13 13 from . import (
14 14 encoding,
15 15 error,
16 16 )
17 17
18 18 def loadconfigtable(ui, extname, configtable):
19 19 """update config item known to the ui with the extension ones"""
20 20 for section, items in sorted(configtable.items()):
21 21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 22 knownkeys = set(knownitems)
23 23 newkeys = set(items)
24 24 for key in sorted(knownkeys & newkeys):
25 25 msg = "extension '%s' overwrite config item '%s.%s'"
26 26 msg %= (extname, section, key)
27 27 ui.develwarn(msg, config='warn-config')
28 28
29 29 knownitems.update(items)
30 30
31 31 class configitem(object):
32 32 """represent a known config item
33 33
34 34 :section: the official config section where to find this item,
35 35 :name: the official name within the section,
36 36 :default: default value for this item,
37 37 :alias: optional list of tuples as alternatives,
38 38 :generic: this is a generic definition, match name using regular expression.
39 39 """
40 40
41 41 def __init__(self, section, name, default=None, alias=(),
42 42 generic=False, priority=0):
43 43 self.section = section
44 44 self.name = name
45 45 self.default = default
46 46 self.alias = list(alias)
47 47 self.generic = generic
48 48 self.priority = priority
49 49 self._re = None
50 50 if generic:
51 51 self._re = re.compile(self.name)
52 52
53 53 class itemregister(dict):
54 54 """A specialized dictionary that can handle wild-card selection"""
55 55
56 56 def __init__(self):
57 57 super(itemregister, self).__init__()
58 58 self._generics = set()
59 59
60 60 def update(self, other):
61 61 super(itemregister, self).update(other)
62 62 self._generics.update(other._generics)
63 63
64 64 def __setitem__(self, key, item):
65 65 super(itemregister, self).__setitem__(key, item)
66 66 if item.generic:
67 67 self._generics.add(item)
68 68
69 69 def get(self, key):
70 70 baseitem = super(itemregister, self).get(key)
71 71 if baseitem is not None and not baseitem.generic:
72 72 return baseitem
73 73
74 74 # search for a matching generic item
75 75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
76 76 for item in generics:
77 77 # we use 'match' instead of 'search' to make the matching simpler
78 78 # for people unfamiliar with regular expression. Having the match
79 79 # rooted to the start of the string will produce less surprising
80 80 # result for user writing simple regex for sub-attribute.
81 81 #
82 82 # For example using "color\..*" match produces an unsurprising
83 83 # result, while using search could suddenly match apparently
84 84 # unrelated configuration that happens to contains "color."
85 85 # anywhere. This is a tradeoff where we favor requiring ".*" on
86 86 # some match to avoid the need to prefix most pattern with "^".
87 87 # The "^" seems more error prone.
88 88 if item._re.match(key):
89 89 return item
90 90
91 91 return None
92 92
93 93 coreitems = {}
94 94
95 95 def _register(configtable, *args, **kwargs):
96 96 item = configitem(*args, **kwargs)
97 97 section = configtable.setdefault(item.section, itemregister())
98 98 if item.name in section:
99 99 msg = "duplicated config item registration for '%s.%s'"
100 100 raise error.ProgrammingError(msg % (item.section, item.name))
101 101 section[item.name] = item
102 102
103 103 # special value for case where the default is derived from other values
104 104 dynamicdefault = object()
105 105
106 106 # Registering actual config items
107 107
108 108 def getitemregister(configtable):
109 109 f = functools.partial(_register, configtable)
110 110 # export pseudo enum as configitem.*
111 111 f.dynamicdefault = dynamicdefault
112 112 return f
113 113
114 114 coreconfigitem = getitemregister(coreitems)
115 115
116 116 coreconfigitem('alias', '.*',
117 117 default=dynamicdefault,
118 118 generic=True,
119 119 )
120 120 coreconfigitem('annotate', 'nodates',
121 121 default=False,
122 122 )
123 123 coreconfigitem('annotate', 'showfunc',
124 124 default=False,
125 125 )
126 126 coreconfigitem('annotate', 'unified',
127 127 default=None,
128 128 )
129 129 coreconfigitem('annotate', 'git',
130 130 default=False,
131 131 )
132 132 coreconfigitem('annotate', 'ignorews',
133 133 default=False,
134 134 )
135 135 coreconfigitem('annotate', 'ignorewsamount',
136 136 default=False,
137 137 )
138 138 coreconfigitem('annotate', 'ignoreblanklines',
139 139 default=False,
140 140 )
141 141 coreconfigitem('annotate', 'ignorewseol',
142 142 default=False,
143 143 )
144 144 coreconfigitem('annotate', 'nobinary',
145 145 default=False,
146 146 )
147 147 coreconfigitem('annotate', 'noprefix',
148 148 default=False,
149 149 )
150 150 coreconfigitem('annotate', 'word-diff',
151 151 default=False,
152 152 )
153 153 coreconfigitem('auth', 'cookiefile',
154 154 default=None,
155 155 )
156 156 # bookmarks.pushing: internal hack for discovery
157 157 coreconfigitem('bookmarks', 'pushing',
158 158 default=list,
159 159 )
160 160 # bundle.mainreporoot: internal hack for bundlerepo
161 161 coreconfigitem('bundle', 'mainreporoot',
162 162 default='',
163 163 )
164 164 coreconfigitem('censor', 'policy',
165 165 default='abort',
166 166 )
167 167 coreconfigitem('chgserver', 'idletimeout',
168 168 default=3600,
169 169 )
170 170 coreconfigitem('chgserver', 'skiphash',
171 171 default=False,
172 172 )
173 173 coreconfigitem('cmdserver', 'log',
174 174 default=None,
175 175 )
176 176 coreconfigitem('color', '.*',
177 177 default=None,
178 178 generic=True,
179 179 )
180 180 coreconfigitem('color', 'mode',
181 181 default='auto',
182 182 )
183 183 coreconfigitem('color', 'pagermode',
184 184 default=dynamicdefault,
185 185 )
186 186 coreconfigitem('commands', 'grep.all-files',
187 187 default=False,
188 188 )
189 189 coreconfigitem('commands', 'resolve.confirm',
190 190 default=False,
191 191 )
192 192 coreconfigitem('commands', 'resolve.explicit-re-merge',
193 193 default=False,
194 194 )
195 195 coreconfigitem('commands', 'resolve.mark-check',
196 196 default='none',
197 197 )
198 198 coreconfigitem('commands', 'show.aliasprefix',
199 199 default=list,
200 200 )
201 201 coreconfigitem('commands', 'status.relative',
202 202 default=False,
203 203 )
204 204 coreconfigitem('commands', 'status.skipstates',
205 205 default=[],
206 206 )
207 207 coreconfigitem('commands', 'status.terse',
208 208 default='',
209 209 )
210 210 coreconfigitem('commands', 'status.verbose',
211 211 default=False,
212 212 )
213 213 coreconfigitem('commands', 'update.check',
214 214 default=None,
215 215 )
216 216 coreconfigitem('commands', 'update.requiredest',
217 217 default=False,
218 218 )
219 219 coreconfigitem('committemplate', '.*',
220 220 default=None,
221 221 generic=True,
222 222 )
223 223 coreconfigitem('convert', 'bzr.saverev',
224 224 default=True,
225 225 )
226 226 coreconfigitem('convert', 'cvsps.cache',
227 227 default=True,
228 228 )
229 229 coreconfigitem('convert', 'cvsps.fuzz',
230 230 default=60,
231 231 )
232 232 coreconfigitem('convert', 'cvsps.logencoding',
233 233 default=None,
234 234 )
235 235 coreconfigitem('convert', 'cvsps.mergefrom',
236 236 default=None,
237 237 )
238 238 coreconfigitem('convert', 'cvsps.mergeto',
239 239 default=None,
240 240 )
241 241 coreconfigitem('convert', 'git.committeractions',
242 242 default=lambda: ['messagedifferent'],
243 243 )
244 244 coreconfigitem('convert', 'git.extrakeys',
245 245 default=list,
246 246 )
247 247 coreconfigitem('convert', 'git.findcopiesharder',
248 248 default=False,
249 249 )
250 250 coreconfigitem('convert', 'git.remoteprefix',
251 251 default='remote',
252 252 )
253 253 coreconfigitem('convert', 'git.renamelimit',
254 254 default=400,
255 255 )
256 256 coreconfigitem('convert', 'git.saverev',
257 257 default=True,
258 258 )
259 259 coreconfigitem('convert', 'git.similarity',
260 260 default=50,
261 261 )
262 262 coreconfigitem('convert', 'git.skipsubmodules',
263 263 default=False,
264 264 )
265 265 coreconfigitem('convert', 'hg.clonebranches',
266 266 default=False,
267 267 )
268 268 coreconfigitem('convert', 'hg.ignoreerrors',
269 269 default=False,
270 270 )
271 271 coreconfigitem('convert', 'hg.revs',
272 272 default=None,
273 273 )
274 274 coreconfigitem('convert', 'hg.saverev',
275 275 default=False,
276 276 )
277 277 coreconfigitem('convert', 'hg.sourcename',
278 278 default=None,
279 279 )
280 280 coreconfigitem('convert', 'hg.startrev',
281 281 default=None,
282 282 )
283 283 coreconfigitem('convert', 'hg.tagsbranch',
284 284 default='default',
285 285 )
286 286 coreconfigitem('convert', 'hg.usebranchnames',
287 287 default=True,
288 288 )
289 289 coreconfigitem('convert', 'ignoreancestorcheck',
290 290 default=False,
291 291 )
292 292 coreconfigitem('convert', 'localtimezone',
293 293 default=False,
294 294 )
295 295 coreconfigitem('convert', 'p4.encoding',
296 296 default=dynamicdefault,
297 297 )
298 298 coreconfigitem('convert', 'p4.startrev',
299 299 default=0,
300 300 )
301 301 coreconfigitem('convert', 'skiptags',
302 302 default=False,
303 303 )
304 304 coreconfigitem('convert', 'svn.debugsvnlog',
305 305 default=True,
306 306 )
307 307 coreconfigitem('convert', 'svn.trunk',
308 308 default=None,
309 309 )
310 310 coreconfigitem('convert', 'svn.tags',
311 311 default=None,
312 312 )
313 313 coreconfigitem('convert', 'svn.branches',
314 314 default=None,
315 315 )
316 316 coreconfigitem('convert', 'svn.startrev',
317 317 default=0,
318 318 )
319 319 coreconfigitem('debug', 'dirstate.delaywrite',
320 320 default=0,
321 321 )
322 322 coreconfigitem('defaults', '.*',
323 323 default=None,
324 324 generic=True,
325 325 )
326 326 coreconfigitem('devel', 'all-warnings',
327 327 default=False,
328 328 )
329 329 coreconfigitem('devel', 'bundle2.debug',
330 330 default=False,
331 331 )
332 332 coreconfigitem('devel', 'cache-vfs',
333 333 default=None,
334 334 )
335 335 coreconfigitem('devel', 'check-locks',
336 336 default=False,
337 337 )
338 338 coreconfigitem('devel', 'check-relroot',
339 339 default=False,
340 340 )
341 341 coreconfigitem('devel', 'default-date',
342 342 default=None,
343 343 )
344 344 coreconfigitem('devel', 'deprec-warn',
345 345 default=False,
346 346 )
347 347 coreconfigitem('devel', 'disableloaddefaultcerts',
348 348 default=False,
349 349 )
350 350 coreconfigitem('devel', 'warn-empty-changegroup',
351 351 default=False,
352 352 )
353 353 coreconfigitem('devel', 'legacy.exchange',
354 354 default=list,
355 355 )
356 356 coreconfigitem('devel', 'servercafile',
357 357 default='',
358 358 )
359 359 coreconfigitem('devel', 'serverexactprotocol',
360 360 default='',
361 361 )
362 362 coreconfigitem('devel', 'serverrequirecert',
363 363 default=False,
364 364 )
365 365 coreconfigitem('devel', 'strip-obsmarkers',
366 366 default=True,
367 367 )
368 368 coreconfigitem('devel', 'warn-config',
369 369 default=None,
370 370 )
371 371 coreconfigitem('devel', 'warn-config-default',
372 372 default=None,
373 373 )
374 374 coreconfigitem('devel', 'user.obsmarker',
375 375 default=None,
376 376 )
377 377 coreconfigitem('devel', 'warn-config-unknown',
378 378 default=None,
379 379 )
380 380 coreconfigitem('devel', 'debug.copies',
381 381 default=False,
382 382 )
383 383 coreconfigitem('devel', 'debug.extensions',
384 384 default=False,
385 385 )
386 386 coreconfigitem('devel', 'debug.peer-request',
387 387 default=False,
388 388 )
389 389 coreconfigitem('diff', 'nodates',
390 390 default=False,
391 391 )
392 392 coreconfigitem('diff', 'showfunc',
393 393 default=False,
394 394 )
395 395 coreconfigitem('diff', 'unified',
396 396 default=None,
397 397 )
398 398 coreconfigitem('diff', 'git',
399 399 default=False,
400 400 )
401 401 coreconfigitem('diff', 'ignorews',
402 402 default=False,
403 403 )
404 404 coreconfigitem('diff', 'ignorewsamount',
405 405 default=False,
406 406 )
407 407 coreconfigitem('diff', 'ignoreblanklines',
408 408 default=False,
409 409 )
410 410 coreconfigitem('diff', 'ignorewseol',
411 411 default=False,
412 412 )
413 413 coreconfigitem('diff', 'nobinary',
414 414 default=False,
415 415 )
416 416 coreconfigitem('diff', 'noprefix',
417 417 default=False,
418 418 )
419 419 coreconfigitem('diff', 'word-diff',
420 420 default=False,
421 421 )
422 422 coreconfigitem('email', 'bcc',
423 423 default=None,
424 424 )
425 425 coreconfigitem('email', 'cc',
426 426 default=None,
427 427 )
428 428 coreconfigitem('email', 'charsets',
429 429 default=list,
430 430 )
431 431 coreconfigitem('email', 'from',
432 432 default=None,
433 433 )
434 434 coreconfigitem('email', 'method',
435 435 default='smtp',
436 436 )
437 437 coreconfigitem('email', 'reply-to',
438 438 default=None,
439 439 )
440 440 coreconfigitem('email', 'to',
441 441 default=None,
442 442 )
443 443 coreconfigitem('experimental', 'archivemetatemplate',
444 444 default=dynamicdefault,
445 445 )
446 446 coreconfigitem('experimental', 'bundle-phases',
447 447 default=False,
448 448 )
449 449 coreconfigitem('experimental', 'bundle2-advertise',
450 450 default=True,
451 451 )
452 452 coreconfigitem('experimental', 'bundle2-output-capture',
453 453 default=False,
454 454 )
455 455 coreconfigitem('experimental', 'bundle2.pushback',
456 456 default=False,
457 457 )
458 458 coreconfigitem('experimental', 'bundle2lazylocking',
459 459 default=False,
460 460 )
461 461 coreconfigitem('experimental', 'bundlecomplevel',
462 462 default=None,
463 463 )
464 464 coreconfigitem('experimental', 'bundlecomplevel.bzip2',
465 465 default=None,
466 466 )
467 467 coreconfigitem('experimental', 'bundlecomplevel.gzip',
468 468 default=None,
469 469 )
470 470 coreconfigitem('experimental', 'bundlecomplevel.none',
471 471 default=None,
472 472 )
473 473 coreconfigitem('experimental', 'bundlecomplevel.zstd',
474 474 default=None,
475 475 )
476 476 coreconfigitem('experimental', 'changegroup3',
477 477 default=False,
478 478 )
479 479 coreconfigitem('experimental', 'clientcompressionengines',
480 480 default=list,
481 481 )
482 482 coreconfigitem('experimental', 'copytrace',
483 483 default='on',
484 484 )
485 485 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
486 486 default=100,
487 487 )
488 488 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
489 489 default=100,
490 490 )
491 491 coreconfigitem('experimental', 'crecordtest',
492 492 default=None,
493 493 )
494 494 coreconfigitem('experimental', 'directaccess',
495 495 default=False,
496 496 )
497 497 coreconfigitem('experimental', 'directaccess.revnums',
498 498 default=False,
499 499 )
500 500 coreconfigitem('experimental', 'editortmpinhg',
501 501 default=False,
502 502 )
503 503 coreconfigitem('experimental', 'evolution',
504 504 default=list,
505 505 )
506 506 coreconfigitem('experimental', 'evolution.allowdivergence',
507 507 default=False,
508 508 alias=[('experimental', 'allowdivergence')]
509 509 )
510 510 coreconfigitem('experimental', 'evolution.allowunstable',
511 511 default=None,
512 512 )
513 513 coreconfigitem('experimental', 'evolution.createmarkers',
514 514 default=None,
515 515 )
516 516 coreconfigitem('experimental', 'evolution.effect-flags',
517 517 default=True,
518 518 alias=[('experimental', 'effect-flags')]
519 519 )
520 520 coreconfigitem('experimental', 'evolution.exchange',
521 521 default=None,
522 522 )
523 523 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
524 524 default=False,
525 525 )
526 526 coreconfigitem('experimental', 'evolution.report-instabilities',
527 527 default=True,
528 528 )
529 529 coreconfigitem('experimental', 'evolution.track-operation',
530 530 default=True,
531 531 )
532 532 coreconfigitem('experimental', 'maxdeltachainspan',
533 533 default=-1,
534 534 )
535 535 coreconfigitem('experimental', 'mergetempdirprefix',
536 536 default=None,
537 537 )
538 538 coreconfigitem('experimental', 'mmapindexthreshold',
539 539 default=None,
540 540 )
541 541 coreconfigitem('experimental', 'narrow',
542 542 default=False,
543 543 )
544 544 coreconfigitem('experimental', 'nonnormalparanoidcheck',
545 545 default=False,
546 546 )
547 547 coreconfigitem('experimental', 'exportableenviron',
548 548 default=list,
549 549 )
550 550 coreconfigitem('experimental', 'extendedheader.index',
551 551 default=None,
552 552 )
553 553 coreconfigitem('experimental', 'extendedheader.similarity',
554 554 default=False,
555 555 )
556 556 coreconfigitem('experimental', 'format.compression',
557 557 default='zlib',
558 558 )
559 559 coreconfigitem('experimental', 'graphshorten',
560 560 default=False,
561 561 )
562 562 coreconfigitem('experimental', 'graphstyle.parent',
563 563 default=dynamicdefault,
564 564 )
565 565 coreconfigitem('experimental', 'graphstyle.missing',
566 566 default=dynamicdefault,
567 567 )
568 568 coreconfigitem('experimental', 'graphstyle.grandparent',
569 569 default=dynamicdefault,
570 570 )
571 571 coreconfigitem('experimental', 'hook-track-tags',
572 572 default=False,
573 573 )
574 574 coreconfigitem('experimental', 'httppeer.advertise-v2',
575 575 default=False,
576 576 )
577 577 coreconfigitem('experimental', 'httppeer.v2-encoder-order',
578 578 default=None,
579 579 )
580 580 coreconfigitem('experimental', 'httppostargs',
581 581 default=False,
582 582 )
583 583 coreconfigitem('experimental', 'mergedriver',
584 584 default=None,
585 585 )
586 586 coreconfigitem('experimental', 'nointerrupt', default=False)
587 587 coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
588 588
589 589 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
590 590 default=False,
591 591 )
592 592 coreconfigitem('experimental', 'remotenames',
593 593 default=False,
594 594 )
595 595 coreconfigitem('experimental', 'removeemptydirs',
596 596 default=True,
597 597 )
598 598 coreconfigitem('experimental', 'revisions.prefixhexnode',
599 599 default=False,
600 600 )
601 601 coreconfigitem('experimental', 'revlogv2',
602 602 default=None,
603 603 )
604 604 coreconfigitem('experimental', 'revisions.disambiguatewithin',
605 605 default=None,
606 606 )
607 607 coreconfigitem('experimental', 'server.filesdata.recommended-batch-size',
608 608 default=50000,
609 609 )
610 610 coreconfigitem('experimental', 'server.manifestdata.recommended-batch-size',
611 611 default=100000,
612 612 )
613 coreconfigitem('experimental.server', 'stream-narrow-clones',
614 default=False,
615 )
613 616 coreconfigitem('experimental', 'single-head-per-branch',
614 617 default=False,
615 618 )
616 619 coreconfigitem('experimental', 'sshserver.support-v2',
617 620 default=False,
618 621 )
619 622 coreconfigitem('experimental', 'sparse-read',
620 623 default=False,
621 624 )
622 625 coreconfigitem('experimental', 'sparse-read.density-threshold',
623 626 default=0.50,
624 627 )
625 628 coreconfigitem('experimental', 'sparse-read.min-gap-size',
626 629 default='65K',
627 630 )
628 631 coreconfigitem('experimental', 'treemanifest',
629 632 default=False,
630 633 )
631 634 coreconfigitem('experimental', 'update.atomic-file',
632 635 default=False,
633 636 )
634 637 coreconfigitem('experimental', 'sshpeer.advertise-v2',
635 638 default=False,
636 639 )
637 640 coreconfigitem('experimental', 'web.apiserver',
638 641 default=False,
639 642 )
640 643 coreconfigitem('experimental', 'web.api.http-v2',
641 644 default=False,
642 645 )
643 646 coreconfigitem('experimental', 'web.api.debugreflect',
644 647 default=False,
645 648 )
646 649 coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
647 650 default=False,
648 651 )
649 652 coreconfigitem('experimental', 'xdiff',
650 653 default=False,
651 654 )
652 655 coreconfigitem('extensions', '.*',
653 656 default=None,
654 657 generic=True,
655 658 )
656 659 coreconfigitem('extdata', '.*',
657 660 default=None,
658 661 generic=True,
659 662 )
660 663 coreconfigitem('format', 'chunkcachesize',
661 664 default=None,
662 665 )
663 666 coreconfigitem('format', 'dotencode',
664 667 default=True,
665 668 )
666 669 coreconfigitem('format', 'generaldelta',
667 670 default=False,
668 671 )
669 672 coreconfigitem('format', 'manifestcachesize',
670 673 default=None,
671 674 )
672 675 coreconfigitem('format', 'maxchainlen',
673 676 default=dynamicdefault,
674 677 )
675 678 coreconfigitem('format', 'obsstore-version',
676 679 default=None,
677 680 )
678 681 coreconfigitem('format', 'sparse-revlog',
679 682 default=False,
680 683 )
681 684 coreconfigitem('format', 'usefncache',
682 685 default=True,
683 686 )
684 687 coreconfigitem('format', 'usegeneraldelta',
685 688 default=True,
686 689 )
687 690 coreconfigitem('format', 'usestore',
688 691 default=True,
689 692 )
690 693 coreconfigitem('format', 'internal-phase',
691 694 default=False,
692 695 )
693 696 coreconfigitem('fsmonitor', 'warn_when_unused',
694 697 default=True,
695 698 )
696 699 coreconfigitem('fsmonitor', 'warn_update_file_count',
697 700 default=50000,
698 701 )
699 702 coreconfigitem('hooks', '.*',
700 703 default=dynamicdefault,
701 704 generic=True,
702 705 )
703 706 coreconfigitem('hgweb-paths', '.*',
704 707 default=list,
705 708 generic=True,
706 709 )
707 710 coreconfigitem('hostfingerprints', '.*',
708 711 default=list,
709 712 generic=True,
710 713 )
711 714 coreconfigitem('hostsecurity', 'ciphers',
712 715 default=None,
713 716 )
714 717 coreconfigitem('hostsecurity', 'disabletls10warning',
715 718 default=False,
716 719 )
717 720 coreconfigitem('hostsecurity', 'minimumprotocol',
718 721 default=dynamicdefault,
719 722 )
720 723 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
721 724 default=dynamicdefault,
722 725 generic=True,
723 726 )
724 727 coreconfigitem('hostsecurity', '.*:ciphers$',
725 728 default=dynamicdefault,
726 729 generic=True,
727 730 )
728 731 coreconfigitem('hostsecurity', '.*:fingerprints$',
729 732 default=list,
730 733 generic=True,
731 734 )
732 735 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
733 736 default=None,
734 737 generic=True,
735 738 )
736 739
737 740 coreconfigitem('http_proxy', 'always',
738 741 default=False,
739 742 )
740 743 coreconfigitem('http_proxy', 'host',
741 744 default=None,
742 745 )
743 746 coreconfigitem('http_proxy', 'no',
744 747 default=list,
745 748 )
746 749 coreconfigitem('http_proxy', 'passwd',
747 750 default=None,
748 751 )
749 752 coreconfigitem('http_proxy', 'user',
750 753 default=None,
751 754 )
752 755
753 756 coreconfigitem('http', 'timeout',
754 757 default=None,
755 758 )
756 759
757 760 coreconfigitem('logtoprocess', 'commandexception',
758 761 default=None,
759 762 )
760 763 coreconfigitem('logtoprocess', 'commandfinish',
761 764 default=None,
762 765 )
763 766 coreconfigitem('logtoprocess', 'command',
764 767 default=None,
765 768 )
766 769 coreconfigitem('logtoprocess', 'develwarn',
767 770 default=None,
768 771 )
769 772 coreconfigitem('logtoprocess', 'uiblocked',
770 773 default=None,
771 774 )
772 775 coreconfigitem('merge', 'checkunknown',
773 776 default='abort',
774 777 )
775 778 coreconfigitem('merge', 'checkignored',
776 779 default='abort',
777 780 )
778 781 coreconfigitem('experimental', 'merge.checkpathconflicts',
779 782 default=False,
780 783 )
781 784 coreconfigitem('merge', 'followcopies',
782 785 default=True,
783 786 )
784 787 coreconfigitem('merge', 'on-failure',
785 788 default='continue',
786 789 )
787 790 coreconfigitem('merge', 'preferancestor',
788 791 default=lambda: ['*'],
789 792 )
790 793 coreconfigitem('merge', 'strict-capability-check',
791 794 default=False,
792 795 )
793 796 coreconfigitem('merge-tools', '.*',
794 797 default=None,
795 798 generic=True,
796 799 )
797 800 coreconfigitem('merge-tools', br'.*\.args$',
798 801 default="$local $base $other",
799 802 generic=True,
800 803 priority=-1,
801 804 )
802 805 coreconfigitem('merge-tools', br'.*\.binary$',
803 806 default=False,
804 807 generic=True,
805 808 priority=-1,
806 809 )
807 810 coreconfigitem('merge-tools', br'.*\.check$',
808 811 default=list,
809 812 generic=True,
810 813 priority=-1,
811 814 )
812 815 coreconfigitem('merge-tools', br'.*\.checkchanged$',
813 816 default=False,
814 817 generic=True,
815 818 priority=-1,
816 819 )
817 820 coreconfigitem('merge-tools', br'.*\.executable$',
818 821 default=dynamicdefault,
819 822 generic=True,
820 823 priority=-1,
821 824 )
822 825 coreconfigitem('merge-tools', br'.*\.fixeol$',
823 826 default=False,
824 827 generic=True,
825 828 priority=-1,
826 829 )
827 830 coreconfigitem('merge-tools', br'.*\.gui$',
828 831 default=False,
829 832 generic=True,
830 833 priority=-1,
831 834 )
832 835 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
833 836 default='basic',
834 837 generic=True,
835 838 priority=-1,
836 839 )
837 840 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
838 841 default=dynamicdefault, # take from ui.mergemarkertemplate
839 842 generic=True,
840 843 priority=-1,
841 844 )
842 845 coreconfigitem('merge-tools', br'.*\.priority$',
843 846 default=0,
844 847 generic=True,
845 848 priority=-1,
846 849 )
847 850 coreconfigitem('merge-tools', br'.*\.premerge$',
848 851 default=dynamicdefault,
849 852 generic=True,
850 853 priority=-1,
851 854 )
852 855 coreconfigitem('merge-tools', br'.*\.symlink$',
853 856 default=False,
854 857 generic=True,
855 858 priority=-1,
856 859 )
857 860 coreconfigitem('pager', 'attend-.*',
858 861 default=dynamicdefault,
859 862 generic=True,
860 863 )
861 864 coreconfigitem('pager', 'ignore',
862 865 default=list,
863 866 )
864 867 coreconfigitem('pager', 'pager',
865 868 default=dynamicdefault,
866 869 )
867 870 coreconfigitem('patch', 'eol',
868 871 default='strict',
869 872 )
870 873 coreconfigitem('patch', 'fuzz',
871 874 default=2,
872 875 )
873 876 coreconfigitem('paths', 'default',
874 877 default=None,
875 878 )
876 879 coreconfigitem('paths', 'default-push',
877 880 default=None,
878 881 )
879 882 coreconfigitem('paths', '.*',
880 883 default=None,
881 884 generic=True,
882 885 )
883 886 coreconfigitem('phases', 'checksubrepos',
884 887 default='follow',
885 888 )
886 889 coreconfigitem('phases', 'new-commit',
887 890 default='draft',
888 891 )
889 892 coreconfigitem('phases', 'publish',
890 893 default=True,
891 894 )
892 895 coreconfigitem('profiling', 'enabled',
893 896 default=False,
894 897 )
895 898 coreconfigitem('profiling', 'format',
896 899 default='text',
897 900 )
898 901 coreconfigitem('profiling', 'freq',
899 902 default=1000,
900 903 )
901 904 coreconfigitem('profiling', 'limit',
902 905 default=30,
903 906 )
904 907 coreconfigitem('profiling', 'nested',
905 908 default=0,
906 909 )
907 910 coreconfigitem('profiling', 'output',
908 911 default=None,
909 912 )
910 913 coreconfigitem('profiling', 'showmax',
911 914 default=0.999,
912 915 )
913 916 coreconfigitem('profiling', 'showmin',
914 917 default=dynamicdefault,
915 918 )
916 919 coreconfigitem('profiling', 'sort',
917 920 default='inlinetime',
918 921 )
919 922 coreconfigitem('profiling', 'statformat',
920 923 default='hotpath',
921 924 )
922 925 coreconfigitem('profiling', 'time-track',
923 926 default='cpu',
924 927 )
925 928 coreconfigitem('profiling', 'type',
926 929 default='stat',
927 930 )
928 931 coreconfigitem('progress', 'assume-tty',
929 932 default=False,
930 933 )
931 934 coreconfigitem('progress', 'changedelay',
932 935 default=1,
933 936 )
934 937 coreconfigitem('progress', 'clear-complete',
935 938 default=True,
936 939 )
937 940 coreconfigitem('progress', 'debug',
938 941 default=False,
939 942 )
940 943 coreconfigitem('progress', 'delay',
941 944 default=3,
942 945 )
943 946 coreconfigitem('progress', 'disable',
944 947 default=False,
945 948 )
946 949 coreconfigitem('progress', 'estimateinterval',
947 950 default=60.0,
948 951 )
949 952 coreconfigitem('progress', 'format',
950 953 default=lambda: ['topic', 'bar', 'number', 'estimate'],
951 954 )
952 955 coreconfigitem('progress', 'refresh',
953 956 default=0.1,
954 957 )
955 958 coreconfigitem('progress', 'width',
956 959 default=dynamicdefault,
957 960 )
958 961 coreconfigitem('push', 'pushvars.server',
959 962 default=False,
960 963 )
961 964 coreconfigitem('storage', 'new-repo-backend',
962 965 default='revlogv1',
963 966 )
964 967 coreconfigitem('storage', 'revlog.optimize-delta-parent-choice',
965 968 default=True,
966 969 alias=[('format', 'aggressivemergedeltas')],
967 970 )
968 971 coreconfigitem('server', 'bookmarks-pushkey-compat',
969 972 default=True,
970 973 )
971 974 coreconfigitem('server', 'bundle1',
972 975 default=True,
973 976 )
974 977 coreconfigitem('server', 'bundle1gd',
975 978 default=None,
976 979 )
977 980 coreconfigitem('server', 'bundle1.pull',
978 981 default=None,
979 982 )
980 983 coreconfigitem('server', 'bundle1gd.pull',
981 984 default=None,
982 985 )
983 986 coreconfigitem('server', 'bundle1.push',
984 987 default=None,
985 988 )
986 989 coreconfigitem('server', 'bundle1gd.push',
987 990 default=None,
988 991 )
989 992 coreconfigitem('server', 'bundle2.stream',
990 993 default=True,
991 994 alias=[('experimental', 'bundle2.stream')]
992 995 )
993 996 coreconfigitem('server', 'compressionengines',
994 997 default=list,
995 998 )
996 999 coreconfigitem('server', 'concurrent-push-mode',
997 1000 default='strict',
998 1001 )
999 1002 coreconfigitem('server', 'disablefullbundle',
1000 1003 default=False,
1001 1004 )
1002 1005 coreconfigitem('server', 'maxhttpheaderlen',
1003 1006 default=1024,
1004 1007 )
1005 1008 coreconfigitem('server', 'pullbundle',
1006 1009 default=False,
1007 1010 )
1008 1011 coreconfigitem('server', 'preferuncompressed',
1009 1012 default=False,
1010 1013 )
1011 1014 coreconfigitem('server', 'streamunbundle',
1012 1015 default=False,
1013 1016 )
1014 1017 coreconfigitem('server', 'uncompressed',
1015 1018 default=True,
1016 1019 )
1017 1020 coreconfigitem('server', 'uncompressedallowsecret',
1018 1021 default=False,
1019 1022 )
1020 1023 coreconfigitem('server', 'validate',
1021 1024 default=False,
1022 1025 )
1023 1026 coreconfigitem('server', 'zliblevel',
1024 1027 default=-1,
1025 1028 )
1026 1029 coreconfigitem('server', 'zstdlevel',
1027 1030 default=3,
1028 1031 )
1029 1032 coreconfigitem('share', 'pool',
1030 1033 default=None,
1031 1034 )
1032 1035 coreconfigitem('share', 'poolnaming',
1033 1036 default='identity',
1034 1037 )
1035 1038 coreconfigitem('smtp', 'host',
1036 1039 default=None,
1037 1040 )
1038 1041 coreconfigitem('smtp', 'local_hostname',
1039 1042 default=None,
1040 1043 )
1041 1044 coreconfigitem('smtp', 'password',
1042 1045 default=None,
1043 1046 )
1044 1047 coreconfigitem('smtp', 'port',
1045 1048 default=dynamicdefault,
1046 1049 )
1047 1050 coreconfigitem('smtp', 'tls',
1048 1051 default='none',
1049 1052 )
1050 1053 coreconfigitem('smtp', 'username',
1051 1054 default=None,
1052 1055 )
1053 1056 coreconfigitem('sparse', 'missingwarning',
1054 1057 default=True,
1055 1058 )
1056 1059 coreconfigitem('subrepos', 'allowed',
1057 1060 default=dynamicdefault, # to make backporting simpler
1058 1061 )
1059 1062 coreconfigitem('subrepos', 'hg:allowed',
1060 1063 default=dynamicdefault,
1061 1064 )
1062 1065 coreconfigitem('subrepos', 'git:allowed',
1063 1066 default=dynamicdefault,
1064 1067 )
1065 1068 coreconfigitem('subrepos', 'svn:allowed',
1066 1069 default=dynamicdefault,
1067 1070 )
1068 1071 coreconfigitem('templates', '.*',
1069 1072 default=None,
1070 1073 generic=True,
1071 1074 )
1072 1075 coreconfigitem('trusted', 'groups',
1073 1076 default=list,
1074 1077 )
1075 1078 coreconfigitem('trusted', 'users',
1076 1079 default=list,
1077 1080 )
1078 1081 coreconfigitem('ui', '_usedassubrepo',
1079 1082 default=False,
1080 1083 )
1081 1084 coreconfigitem('ui', 'allowemptycommit',
1082 1085 default=False,
1083 1086 )
1084 1087 coreconfigitem('ui', 'archivemeta',
1085 1088 default=True,
1086 1089 )
1087 1090 coreconfigitem('ui', 'askusername',
1088 1091 default=False,
1089 1092 )
1090 1093 coreconfigitem('ui', 'clonebundlefallback',
1091 1094 default=False,
1092 1095 )
1093 1096 coreconfigitem('ui', 'clonebundleprefers',
1094 1097 default=list,
1095 1098 )
1096 1099 coreconfigitem('ui', 'clonebundles',
1097 1100 default=True,
1098 1101 )
1099 1102 coreconfigitem('ui', 'color',
1100 1103 default='auto',
1101 1104 )
1102 1105 coreconfigitem('ui', 'commitsubrepos',
1103 1106 default=False,
1104 1107 )
1105 1108 coreconfigitem('ui', 'debug',
1106 1109 default=False,
1107 1110 )
1108 1111 coreconfigitem('ui', 'debugger',
1109 1112 default=None,
1110 1113 )
1111 1114 coreconfigitem('ui', 'editor',
1112 1115 default=dynamicdefault,
1113 1116 )
1114 1117 coreconfigitem('ui', 'fallbackencoding',
1115 1118 default=None,
1116 1119 )
1117 1120 coreconfigitem('ui', 'forcecwd',
1118 1121 default=None,
1119 1122 )
1120 1123 coreconfigitem('ui', 'forcemerge',
1121 1124 default=None,
1122 1125 )
1123 1126 coreconfigitem('ui', 'formatdebug',
1124 1127 default=False,
1125 1128 )
1126 1129 coreconfigitem('ui', 'formatjson',
1127 1130 default=False,
1128 1131 )
1129 1132 coreconfigitem('ui', 'formatted',
1130 1133 default=None,
1131 1134 )
1132 1135 coreconfigitem('ui', 'graphnodetemplate',
1133 1136 default=None,
1134 1137 )
1135 1138 coreconfigitem('ui', 'history-editing-backup',
1136 1139 default=True,
1137 1140 )
1138 1141 coreconfigitem('ui', 'interactive',
1139 1142 default=None,
1140 1143 )
1141 1144 coreconfigitem('ui', 'interface',
1142 1145 default=None,
1143 1146 )
1144 1147 coreconfigitem('ui', 'interface.chunkselector',
1145 1148 default=None,
1146 1149 )
1147 1150 coreconfigitem('ui', 'large-file-limit',
1148 1151 default=10000000,
1149 1152 )
1150 1153 coreconfigitem('ui', 'logblockedtimes',
1151 1154 default=False,
1152 1155 )
1153 1156 coreconfigitem('ui', 'logtemplate',
1154 1157 default=None,
1155 1158 )
1156 1159 coreconfigitem('ui', 'merge',
1157 1160 default=None,
1158 1161 )
1159 1162 coreconfigitem('ui', 'mergemarkers',
1160 1163 default='basic',
1161 1164 )
1162 1165 coreconfigitem('ui', 'mergemarkertemplate',
1163 1166 default=('{node|short} '
1164 1167 '{ifeq(tags, "tip", "", '
1165 1168 'ifeq(tags, "", "", "{tags} "))}'
1166 1169 '{if(bookmarks, "{bookmarks} ")}'
1167 1170 '{ifeq(branch, "default", "", "{branch} ")}'
1168 1171 '- {author|user}: {desc|firstline}')
1169 1172 )
1170 1173 coreconfigitem('ui', 'nontty',
1171 1174 default=False,
1172 1175 )
1173 1176 coreconfigitem('ui', 'origbackuppath',
1174 1177 default=None,
1175 1178 )
1176 1179 coreconfigitem('ui', 'paginate',
1177 1180 default=True,
1178 1181 )
1179 1182 coreconfigitem('ui', 'patch',
1180 1183 default=None,
1181 1184 )
1182 1185 coreconfigitem('ui', 'portablefilenames',
1183 1186 default='warn',
1184 1187 )
1185 1188 coreconfigitem('ui', 'promptecho',
1186 1189 default=False,
1187 1190 )
1188 1191 coreconfigitem('ui', 'quiet',
1189 1192 default=False,
1190 1193 )
1191 1194 coreconfigitem('ui', 'quietbookmarkmove',
1192 1195 default=False,
1193 1196 )
1194 1197 coreconfigitem('ui', 'remotecmd',
1195 1198 default='hg',
1196 1199 )
1197 1200 coreconfigitem('ui', 'report_untrusted',
1198 1201 default=True,
1199 1202 )
1200 1203 coreconfigitem('ui', 'rollback',
1201 1204 default=True,
1202 1205 )
1203 1206 coreconfigitem('ui', 'signal-safe-lock',
1204 1207 default=True,
1205 1208 )
1206 1209 coreconfigitem('ui', 'slash',
1207 1210 default=False,
1208 1211 )
1209 1212 coreconfigitem('ui', 'ssh',
1210 1213 default='ssh',
1211 1214 )
1212 1215 coreconfigitem('ui', 'ssherrorhint',
1213 1216 default=None,
1214 1217 )
1215 1218 coreconfigitem('ui', 'statuscopies',
1216 1219 default=False,
1217 1220 )
1218 1221 coreconfigitem('ui', 'strict',
1219 1222 default=False,
1220 1223 )
1221 1224 coreconfigitem('ui', 'style',
1222 1225 default='',
1223 1226 )
1224 1227 coreconfigitem('ui', 'supportcontact',
1225 1228 default=None,
1226 1229 )
1227 1230 coreconfigitem('ui', 'textwidth',
1228 1231 default=78,
1229 1232 )
1230 1233 coreconfigitem('ui', 'timeout',
1231 1234 default='600',
1232 1235 )
1233 1236 coreconfigitem('ui', 'timeout.warn',
1234 1237 default=0,
1235 1238 )
1236 1239 coreconfigitem('ui', 'traceback',
1237 1240 default=False,
1238 1241 )
1239 1242 coreconfigitem('ui', 'tweakdefaults',
1240 1243 default=False,
1241 1244 )
1242 1245 coreconfigitem('ui', 'username',
1243 1246 alias=[('ui', 'user')]
1244 1247 )
1245 1248 coreconfigitem('ui', 'verbose',
1246 1249 default=False,
1247 1250 )
1248 1251 coreconfigitem('verify', 'skipflags',
1249 1252 default=None,
1250 1253 )
1251 1254 coreconfigitem('web', 'allowbz2',
1252 1255 default=False,
1253 1256 )
1254 1257 coreconfigitem('web', 'allowgz',
1255 1258 default=False,
1256 1259 )
1257 1260 coreconfigitem('web', 'allow-pull',
1258 1261 alias=[('web', 'allowpull')],
1259 1262 default=True,
1260 1263 )
1261 1264 coreconfigitem('web', 'allow-push',
1262 1265 alias=[('web', 'allow_push')],
1263 1266 default=list,
1264 1267 )
1265 1268 coreconfigitem('web', 'allowzip',
1266 1269 default=False,
1267 1270 )
1268 1271 coreconfigitem('web', 'archivesubrepos',
1269 1272 default=False,
1270 1273 )
1271 1274 coreconfigitem('web', 'cache',
1272 1275 default=True,
1273 1276 )
1274 1277 coreconfigitem('web', 'contact',
1275 1278 default=None,
1276 1279 )
1277 1280 coreconfigitem('web', 'deny_push',
1278 1281 default=list,
1279 1282 )
1280 1283 coreconfigitem('web', 'guessmime',
1281 1284 default=False,
1282 1285 )
1283 1286 coreconfigitem('web', 'hidden',
1284 1287 default=False,
1285 1288 )
1286 1289 coreconfigitem('web', 'labels',
1287 1290 default=list,
1288 1291 )
1289 1292 coreconfigitem('web', 'logoimg',
1290 1293 default='hglogo.png',
1291 1294 )
1292 1295 coreconfigitem('web', 'logourl',
1293 1296 default='https://mercurial-scm.org/',
1294 1297 )
1295 1298 coreconfigitem('web', 'accesslog',
1296 1299 default='-',
1297 1300 )
1298 1301 coreconfigitem('web', 'address',
1299 1302 default='',
1300 1303 )
1301 1304 coreconfigitem('web', 'allow-archive',
1302 1305 alias=[('web', 'allow_archive')],
1303 1306 default=list,
1304 1307 )
1305 1308 coreconfigitem('web', 'allow_read',
1306 1309 default=list,
1307 1310 )
1308 1311 coreconfigitem('web', 'baseurl',
1309 1312 default=None,
1310 1313 )
1311 1314 coreconfigitem('web', 'cacerts',
1312 1315 default=None,
1313 1316 )
1314 1317 coreconfigitem('web', 'certificate',
1315 1318 default=None,
1316 1319 )
1317 1320 coreconfigitem('web', 'collapse',
1318 1321 default=False,
1319 1322 )
1320 1323 coreconfigitem('web', 'csp',
1321 1324 default=None,
1322 1325 )
1323 1326 coreconfigitem('web', 'deny_read',
1324 1327 default=list,
1325 1328 )
1326 1329 coreconfigitem('web', 'descend',
1327 1330 default=True,
1328 1331 )
1329 1332 coreconfigitem('web', 'description',
1330 1333 default="",
1331 1334 )
1332 1335 coreconfigitem('web', 'encoding',
1333 1336 default=lambda: encoding.encoding,
1334 1337 )
1335 1338 coreconfigitem('web', 'errorlog',
1336 1339 default='-',
1337 1340 )
1338 1341 coreconfigitem('web', 'ipv6',
1339 1342 default=False,
1340 1343 )
1341 1344 coreconfigitem('web', 'maxchanges',
1342 1345 default=10,
1343 1346 )
1344 1347 coreconfigitem('web', 'maxfiles',
1345 1348 default=10,
1346 1349 )
1347 1350 coreconfigitem('web', 'maxshortchanges',
1348 1351 default=60,
1349 1352 )
1350 1353 coreconfigitem('web', 'motd',
1351 1354 default='',
1352 1355 )
1353 1356 coreconfigitem('web', 'name',
1354 1357 default=dynamicdefault,
1355 1358 )
1356 1359 coreconfigitem('web', 'port',
1357 1360 default=8000,
1358 1361 )
1359 1362 coreconfigitem('web', 'prefix',
1360 1363 default='',
1361 1364 )
1362 1365 coreconfigitem('web', 'push_ssl',
1363 1366 default=True,
1364 1367 )
1365 1368 coreconfigitem('web', 'refreshinterval',
1366 1369 default=20,
1367 1370 )
1368 1371 coreconfigitem('web', 'server-header',
1369 1372 default=None,
1370 1373 )
1371 1374 coreconfigitem('web', 'static',
1372 1375 default=None,
1373 1376 )
1374 1377 coreconfigitem('web', 'staticurl',
1375 1378 default=None,
1376 1379 )
1377 1380 coreconfigitem('web', 'stripes',
1378 1381 default=1,
1379 1382 )
1380 1383 coreconfigitem('web', 'style',
1381 1384 default='paper',
1382 1385 )
1383 1386 coreconfigitem('web', 'templates',
1384 1387 default=None,
1385 1388 )
1386 1389 coreconfigitem('web', 'view',
1387 1390 default='served',
1388 1391 )
1389 1392 coreconfigitem('worker', 'backgroundclose',
1390 1393 default=dynamicdefault,
1391 1394 )
1392 1395 # Windows defaults to a limit of 512 open files. A buffer of 128
1393 1396 # should give us enough headway.
1394 1397 coreconfigitem('worker', 'backgroundclosemaxqueue',
1395 1398 default=384,
1396 1399 )
1397 1400 coreconfigitem('worker', 'backgroundcloseminfilecount',
1398 1401 default=2048,
1399 1402 )
1400 1403 coreconfigitem('worker', 'backgroundclosethreadcount',
1401 1404 default=4,
1402 1405 )
1403 1406 coreconfigitem('worker', 'enabled',
1404 1407 default=True,
1405 1408 )
1406 1409 coreconfigitem('worker', 'numcpus',
1407 1410 default=None,
1408 1411 )
1409 1412
1410 1413 # Rebase related configuration moved to core because other extension are doing
1411 1414 # strange things. For example, shelve import the extensions to reuse some bit
1412 1415 # without formally loading it.
1413 1416 coreconfigitem('commands', 'rebase.requiredest',
1414 1417 default=False,
1415 1418 )
1416 1419 coreconfigitem('experimental', 'rebaseskipobsolete',
1417 1420 default=True,
1418 1421 )
1419 1422 coreconfigitem('rebase', 'singletransaction',
1420 1423 default=False,
1421 1424 )
1422 1425 coreconfigitem('rebase', 'experimental.inmemory',
1423 1426 default=False,
1424 1427 )
@@ -1,651 +1,655
1 1 # streamclone.py - producing and consuming streaming repository data
2 2 #
3 3 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import contextlib
11 11 import os
12 12 import struct
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 branchmap,
17 17 cacheutil,
18 18 error,
19 19 phases,
20 20 pycompat,
21 21 repository,
22 22 store,
23 23 util,
24 24 )
25 25
26 26 def canperformstreamclone(pullop, bundle2=False):
27 27 """Whether it is possible to perform a streaming clone as part of pull.
28 28
29 29 ``bundle2`` will cause the function to consider stream clone through
30 30 bundle2 and only through bundle2.
31 31
32 32 Returns a tuple of (supported, requirements). ``supported`` is True if
33 33 streaming clone is supported and False otherwise. ``requirements`` is
34 34 a set of repo requirements from the remote, or ``None`` if stream clone
35 35 isn't supported.
36 36 """
37 37 repo = pullop.repo
38 38 remote = pullop.remote
39 39
40 40 bundle2supported = False
41 41 if pullop.canusebundle2:
42 42 if 'v2' in pullop.remotebundle2caps.get('stream', []):
43 43 bundle2supported = True
44 44 # else
45 45 # Server doesn't support bundle2 stream clone or doesn't support
46 46 # the versions we support. Fall back and possibly allow legacy.
47 47
48 48 # Ensures legacy code path uses available bundle2.
49 49 if bundle2supported and not bundle2:
50 50 return False, None
51 51 # Ensures bundle2 doesn't try to do a stream clone if it isn't supported.
52 52 elif bundle2 and not bundle2supported:
53 53 return False, None
54 54
55 55 # Streaming clone only works on empty repositories.
56 56 if len(repo):
57 57 return False, None
58 58
59 59 # Streaming clone only works if all data is being requested.
60 60 if pullop.heads:
61 61 return False, None
62 62
63 63 streamrequested = pullop.streamclonerequested
64 64
65 65 # If we don't have a preference, let the server decide for us. This
66 66 # likely only comes into play in LANs.
67 67 if streamrequested is None:
68 68 # The server can advertise whether to prefer streaming clone.
69 69 streamrequested = remote.capable('stream-preferred')
70 70
71 71 if not streamrequested:
72 72 return False, None
73 73
74 74 # In order for stream clone to work, the client has to support all the
75 75 # requirements advertised by the server.
76 76 #
77 77 # The server advertises its requirements via the "stream" and "streamreqs"
78 78 # capability. "stream" (a value-less capability) is advertised if and only
79 79 # if the only requirement is "revlogv1." Else, the "streamreqs" capability
80 80 # is advertised and contains a comma-delimited list of requirements.
81 81 requirements = set()
82 82 if remote.capable('stream'):
83 83 requirements.add('revlogv1')
84 84 else:
85 85 streamreqs = remote.capable('streamreqs')
86 86 # This is weird and shouldn't happen with modern servers.
87 87 if not streamreqs:
88 88 pullop.repo.ui.warn(_(
89 89 'warning: stream clone requested but server has them '
90 90 'disabled\n'))
91 91 return False, None
92 92
93 93 streamreqs = set(streamreqs.split(','))
94 94 # Server requires something we don't support. Bail.
95 95 missingreqs = streamreqs - repo.supportedformats
96 96 if missingreqs:
97 97 pullop.repo.ui.warn(_(
98 98 'warning: stream clone requested but client is missing '
99 99 'requirements: %s\n') % ', '.join(sorted(missingreqs)))
100 100 pullop.repo.ui.warn(
101 101 _('(see https://www.mercurial-scm.org/wiki/MissingRequirement '
102 102 'for more information)\n'))
103 103 return False, None
104 104 requirements = streamreqs
105 105
106 106 return True, requirements
107 107
108 108 def maybeperformlegacystreamclone(pullop):
109 109 """Possibly perform a legacy stream clone operation.
110 110
111 111 Legacy stream clones are performed as part of pull but before all other
112 112 operations.
113 113
114 114 A legacy stream clone will not be performed if a bundle2 stream clone is
115 115 supported.
116 116 """
117 117 from . import localrepo
118 118
119 119 supported, requirements = canperformstreamclone(pullop)
120 120
121 121 if not supported:
122 122 return
123 123
124 124 repo = pullop.repo
125 125 remote = pullop.remote
126 126
127 127 # Save remote branchmap. We will use it later to speed up branchcache
128 128 # creation.
129 129 rbranchmap = None
130 130 if remote.capable('branchmap'):
131 131 with remote.commandexecutor() as e:
132 132 rbranchmap = e.callcommand('branchmap', {}).result()
133 133
134 134 repo.ui.status(_('streaming all changes\n'))
135 135
136 136 with remote.commandexecutor() as e:
137 137 fp = e.callcommand('stream_out', {}).result()
138 138
139 139 # TODO strictly speaking, this code should all be inside the context
140 140 # manager because the context manager is supposed to ensure all wire state
141 141 # is flushed when exiting. But the legacy peers don't do this, so it
142 142 # doesn't matter.
143 143 l = fp.readline()
144 144 try:
145 145 resp = int(l)
146 146 except ValueError:
147 147 raise error.ResponseError(
148 148 _('unexpected response from remote server:'), l)
149 149 if resp == 1:
150 150 raise error.Abort(_('operation forbidden by server'))
151 151 elif resp == 2:
152 152 raise error.Abort(_('locking the remote repository failed'))
153 153 elif resp != 0:
154 154 raise error.Abort(_('the server sent an unknown error code'))
155 155
156 156 l = fp.readline()
157 157 try:
158 158 filecount, bytecount = map(int, l.split(' ', 1))
159 159 except (ValueError, TypeError):
160 160 raise error.ResponseError(
161 161 _('unexpected response from remote server:'), l)
162 162
163 163 with repo.lock():
164 164 consumev1(repo, fp, filecount, bytecount)
165 165
166 166 # new requirements = old non-format requirements +
167 167 # new format-related remote requirements
168 168 # requirements from the streamed-in repository
169 169 repo.requirements = requirements | (
170 170 repo.requirements - repo.supportedformats)
171 171 repo.svfs.options = localrepo.resolvestorevfsoptions(
172 172 repo.ui, repo.requirements, repo.features)
173 173 repo._writerequirements()
174 174
175 175 if rbranchmap:
176 176 branchmap.replacecache(repo, rbranchmap)
177 177
178 178 repo.invalidate()
179 179
180 180 def allowservergeneration(repo):
181 181 """Whether streaming clones are allowed from the server."""
182 182 if repository.REPO_FEATURE_STREAM_CLONE not in repo.features:
183 183 return False
184 184
185 185 if not repo.ui.configbool('server', 'uncompressed', untrusted=True):
186 186 return False
187 187
188 188 # The way stream clone works makes it impossible to hide secret changesets.
189 189 # So don't allow this by default.
190 190 secret = phases.hassecret(repo)
191 191 if secret:
192 192 return repo.ui.configbool('server', 'uncompressedallowsecret')
193 193
194 194 return True
195 195
196 196 # This is it's own function so extensions can override it.
197 197 def _walkstreamfiles(repo):
198 198 return repo.store.walk()
199 199
200 200 def generatev1(repo):
201 201 """Emit content for version 1 of a streaming clone.
202 202
203 203 This returns a 3-tuple of (file count, byte size, data iterator).
204 204
205 205 The data iterator consists of N entries for each file being transferred.
206 206 Each file entry starts as a line with the file name and integer size
207 207 delimited by a null byte.
208 208
209 209 The raw file data follows. Following the raw file data is the next file
210 210 entry, or EOF.
211 211
212 212 When used on the wire protocol, an additional line indicating protocol
213 213 success will be prepended to the stream. This function is not responsible
214 214 for adding it.
215 215
216 216 This function will obtain a repository lock to ensure a consistent view of
217 217 the store is captured. It therefore may raise LockError.
218 218 """
219 219 entries = []
220 220 total_bytes = 0
221 221 # Get consistent snapshot of repo, lock during scan.
222 222 with repo.lock():
223 223 repo.ui.debug('scanning\n')
224 224 for name, ename, size in _walkstreamfiles(repo):
225 225 if size:
226 226 entries.append((name, size))
227 227 total_bytes += size
228 228
229 229 repo.ui.debug('%d files, %d bytes to transfer\n' %
230 230 (len(entries), total_bytes))
231 231
232 232 svfs = repo.svfs
233 233 debugflag = repo.ui.debugflag
234 234
235 235 def emitrevlogdata():
236 236 for name, size in entries:
237 237 if debugflag:
238 238 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
239 239 # partially encode name over the wire for backwards compat
240 240 yield '%s\0%d\n' % (store.encodedir(name), size)
241 241 # auditing at this stage is both pointless (paths are already
242 242 # trusted by the local repo) and expensive
243 243 with svfs(name, 'rb', auditpath=False) as fp:
244 244 if size <= 65536:
245 245 yield fp.read(size)
246 246 else:
247 247 for chunk in util.filechunkiter(fp, limit=size):
248 248 yield chunk
249 249
250 250 return len(entries), total_bytes, emitrevlogdata()
251 251
252 252 def generatev1wireproto(repo):
253 253 """Emit content for version 1 of streaming clone suitable for the wire.
254 254
255 255 This is the data output from ``generatev1()`` with 2 header lines. The
256 256 first line indicates overall success. The 2nd contains the file count and
257 257 byte size of payload.
258 258
259 259 The success line contains "0" for success, "1" for stream generation not
260 260 allowed, and "2" for error locking the repository (possibly indicating
261 261 a permissions error for the server process).
262 262 """
263 263 if not allowservergeneration(repo):
264 264 yield '1\n'
265 265 return
266 266
267 267 try:
268 268 filecount, bytecount, it = generatev1(repo)
269 269 except error.LockError:
270 270 yield '2\n'
271 271 return
272 272
273 273 # Indicates successful response.
274 274 yield '0\n'
275 275 yield '%d %d\n' % (filecount, bytecount)
276 276 for chunk in it:
277 277 yield chunk
278 278
279 279 def generatebundlev1(repo, compression='UN'):
280 280 """Emit content for version 1 of a stream clone bundle.
281 281
282 282 The first 4 bytes of the output ("HGS1") denote this as stream clone
283 283 bundle version 1.
284 284
285 285 The next 2 bytes indicate the compression type. Only "UN" is currently
286 286 supported.
287 287
288 288 The next 16 bytes are two 64-bit big endian unsigned integers indicating
289 289 file count and byte count, respectively.
290 290
291 291 The next 2 bytes is a 16-bit big endian unsigned short declaring the length
292 292 of the requirements string, including a trailing \0. The following N bytes
293 293 are the requirements string, which is ASCII containing a comma-delimited
294 294 list of repo requirements that are needed to support the data.
295 295
296 296 The remaining content is the output of ``generatev1()`` (which may be
297 297 compressed in the future).
298 298
299 299 Returns a tuple of (requirements, data generator).
300 300 """
301 301 if compression != 'UN':
302 302 raise ValueError('we do not support the compression argument yet')
303 303
304 304 requirements = repo.requirements & repo.supportedformats
305 305 requires = ','.join(sorted(requirements))
306 306
307 307 def gen():
308 308 yield 'HGS1'
309 309 yield compression
310 310
311 311 filecount, bytecount, it = generatev1(repo)
312 312 repo.ui.status(_('writing %d bytes for %d files\n') %
313 313 (bytecount, filecount))
314 314
315 315 yield struct.pack('>QQ', filecount, bytecount)
316 316 yield struct.pack('>H', len(requires) + 1)
317 317 yield requires + '\0'
318 318
319 319 # This is where we'll add compression in the future.
320 320 assert compression == 'UN'
321 321
322 322 progress = repo.ui.makeprogress(_('bundle'), total=bytecount,
323 323 unit=_('bytes'))
324 324 progress.update(0)
325 325
326 326 for chunk in it:
327 327 progress.increment(step=len(chunk))
328 328 yield chunk
329 329
330 330 progress.complete()
331 331
332 332 return requirements, gen()
333 333
334 334 def consumev1(repo, fp, filecount, bytecount):
335 335 """Apply the contents from version 1 of a streaming clone file handle.
336 336
337 337 This takes the output from "stream_out" and applies it to the specified
338 338 repository.
339 339
340 340 Like "stream_out," the status line added by the wire protocol is not
341 341 handled by this function.
342 342 """
343 343 with repo.lock():
344 344 repo.ui.status(_('%d files to transfer, %s of data\n') %
345 345 (filecount, util.bytecount(bytecount)))
346 346 progress = repo.ui.makeprogress(_('clone'), total=bytecount,
347 347 unit=_('bytes'))
348 348 progress.update(0)
349 349 start = util.timer()
350 350
351 351 # TODO: get rid of (potential) inconsistency
352 352 #
353 353 # If transaction is started and any @filecache property is
354 354 # changed at this point, it causes inconsistency between
355 355 # in-memory cached property and streamclone-ed file on the
356 356 # disk. Nested transaction prevents transaction scope "clone"
357 357 # below from writing in-memory changes out at the end of it,
358 358 # even though in-memory changes are discarded at the end of it
359 359 # regardless of transaction nesting.
360 360 #
361 361 # But transaction nesting can't be simply prohibited, because
362 362 # nesting occurs also in ordinary case (e.g. enabling
363 363 # clonebundles).
364 364
365 365 with repo.transaction('clone'):
366 366 with repo.svfs.backgroundclosing(repo.ui, expectedcount=filecount):
367 367 for i in pycompat.xrange(filecount):
368 368 # XXX doesn't support '\n' or '\r' in filenames
369 369 l = fp.readline()
370 370 try:
371 371 name, size = l.split('\0', 1)
372 372 size = int(size)
373 373 except (ValueError, TypeError):
374 374 raise error.ResponseError(
375 375 _('unexpected response from remote server:'), l)
376 376 if repo.ui.debugflag:
377 377 repo.ui.debug('adding %s (%s)\n' %
378 378 (name, util.bytecount(size)))
379 379 # for backwards compat, name was partially encoded
380 380 path = store.decodedir(name)
381 381 with repo.svfs(path, 'w', backgroundclose=True) as ofp:
382 382 for chunk in util.filechunkiter(fp, limit=size):
383 383 progress.increment(step=len(chunk))
384 384 ofp.write(chunk)
385 385
386 386 # force @filecache properties to be reloaded from
387 387 # streamclone-ed file at next access
388 388 repo.invalidate(clearfilecache=True)
389 389
390 390 elapsed = util.timer() - start
391 391 if elapsed <= 0:
392 392 elapsed = 0.001
393 393 progress.complete()
394 394 repo.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
395 395 (util.bytecount(bytecount), elapsed,
396 396 util.bytecount(bytecount / elapsed)))
397 397
398 398 def readbundle1header(fp):
399 399 compression = fp.read(2)
400 400 if compression != 'UN':
401 401 raise error.Abort(_('only uncompressed stream clone bundles are '
402 402 'supported; got %s') % compression)
403 403
404 404 filecount, bytecount = struct.unpack('>QQ', fp.read(16))
405 405 requireslen = struct.unpack('>H', fp.read(2))[0]
406 406 requires = fp.read(requireslen)
407 407
408 408 if not requires.endswith('\0'):
409 409 raise error.Abort(_('malformed stream clone bundle: '
410 410 'requirements not properly encoded'))
411 411
412 412 requirements = set(requires.rstrip('\0').split(','))
413 413
414 414 return filecount, bytecount, requirements
415 415
416 416 def applybundlev1(repo, fp):
417 417 """Apply the content from a stream clone bundle version 1.
418 418
419 419 We assume the 4 byte header has been read and validated and the file handle
420 420 is at the 2 byte compression identifier.
421 421 """
422 422 if len(repo):
423 423 raise error.Abort(_('cannot apply stream clone bundle on non-empty '
424 424 'repo'))
425 425
426 426 filecount, bytecount, requirements = readbundle1header(fp)
427 427 missingreqs = requirements - repo.supportedformats
428 428 if missingreqs:
429 429 raise error.Abort(_('unable to apply stream clone: '
430 430 'unsupported format: %s') %
431 431 ', '.join(sorted(missingreqs)))
432 432
433 433 consumev1(repo, fp, filecount, bytecount)
434 434
435 435 class streamcloneapplier(object):
436 436 """Class to manage applying streaming clone bundles.
437 437
438 438 We need to wrap ``applybundlev1()`` in a dedicated type to enable bundle
439 439 readers to perform bundle type-specific functionality.
440 440 """
441 441 def __init__(self, fh):
442 442 self._fh = fh
443 443
444 444 def apply(self, repo):
445 445 return applybundlev1(repo, self._fh)
446 446
447 447 # type of file to stream
448 448 _fileappend = 0 # append only file
449 449 _filefull = 1 # full snapshot file
450 450
451 451 # Source of the file
452 452 _srcstore = 's' # store (svfs)
453 453 _srccache = 'c' # cache (cache)
454 454
455 455 # This is it's own function so extensions can override it.
456 456 def _walkstreamfullstorefiles(repo):
457 457 """list snapshot file from the store"""
458 458 fnames = []
459 459 if not repo.publishing():
460 460 fnames.append('phaseroots')
461 461 return fnames
462 462
463 463 def _filterfull(entry, copy, vfsmap):
464 464 """actually copy the snapshot files"""
465 465 src, name, ftype, data = entry
466 466 if ftype != _filefull:
467 467 return entry
468 468 return (src, name, ftype, copy(vfsmap[src].join(name)))
469 469
470 470 @contextlib.contextmanager
471 471 def maketempcopies():
472 472 """return a function to temporary copy file"""
473 473 files = []
474 474 try:
475 475 def copy(src):
476 476 fd, dst = pycompat.mkstemp()
477 477 os.close(fd)
478 478 files.append(dst)
479 479 util.copyfiles(src, dst, hardlink=True)
480 480 return dst
481 481 yield copy
482 482 finally:
483 483 for tmp in files:
484 484 util.tryunlink(tmp)
485 485
486 486 def _makemap(repo):
487 487 """make a (src -> vfs) map for the repo"""
488 488 vfsmap = {
489 489 _srcstore: repo.svfs,
490 490 _srccache: repo.cachevfs,
491 491 }
492 492 # we keep repo.vfs out of the on purpose, ther are too many danger there
493 493 # (eg: .hg/hgrc)
494 494 assert repo.vfs not in vfsmap.values()
495 495
496 496 return vfsmap
497 497
498 498 def _emit2(repo, entries, totalfilesize):
499 499 """actually emit the stream bundle"""
500 500 vfsmap = _makemap(repo)
501 501 progress = repo.ui.makeprogress(_('bundle'), total=totalfilesize,
502 502 unit=_('bytes'))
503 503 progress.update(0)
504 504 with maketempcopies() as copy, progress:
505 505 # copy is delayed until we are in the try
506 506 entries = [_filterfull(e, copy, vfsmap) for e in entries]
507 507 yield None # this release the lock on the repository
508 508 seen = 0
509 509
510 510 for src, name, ftype, data in entries:
511 511 vfs = vfsmap[src]
512 512 yield src
513 513 yield util.uvarintencode(len(name))
514 514 if ftype == _fileappend:
515 515 fp = vfs(name)
516 516 size = data
517 517 elif ftype == _filefull:
518 518 fp = open(data, 'rb')
519 519 size = util.fstat(fp).st_size
520 520 try:
521 521 yield util.uvarintencode(size)
522 522 yield name
523 523 if size <= 65536:
524 524 chunks = (fp.read(size),)
525 525 else:
526 526 chunks = util.filechunkiter(fp, limit=size)
527 527 for chunk in chunks:
528 528 seen += len(chunk)
529 529 progress.update(seen)
530 530 yield chunk
531 531 finally:
532 532 fp.close()
533 533
534 def generatev2(repo):
534 def generatev2(repo, includes, excludes):
535 535 """Emit content for version 2 of a streaming clone.
536 536
537 537 the data stream consists the following entries:
538 538 1) A char representing the file destination (eg: store or cache)
539 539 2) A varint containing the length of the filename
540 540 3) A varint containing the length of file data
541 541 4) N bytes containing the filename (the internal, store-agnostic form)
542 542 5) N bytes containing the file data
543 543
544 544 Returns a 3-tuple of (file count, file size, data iterator).
545 545 """
546 546
547 # temporarily raise error until we add storage level logic
548 if includes or excludes:
549 raise error.Abort(_("server does not support narrow stream clones"))
550
547 551 with repo.lock():
548 552
549 553 entries = []
550 554 totalfilesize = 0
551 555
552 556 repo.ui.debug('scanning\n')
553 557 for name, ename, size in _walkstreamfiles(repo):
554 558 if size:
555 559 entries.append((_srcstore, name, _fileappend, size))
556 560 totalfilesize += size
557 561 for name in _walkstreamfullstorefiles(repo):
558 562 if repo.svfs.exists(name):
559 563 totalfilesize += repo.svfs.lstat(name).st_size
560 564 entries.append((_srcstore, name, _filefull, None))
561 565 for name in cacheutil.cachetocopy(repo):
562 566 if repo.cachevfs.exists(name):
563 567 totalfilesize += repo.cachevfs.lstat(name).st_size
564 568 entries.append((_srccache, name, _filefull, None))
565 569
566 570 chunks = _emit2(repo, entries, totalfilesize)
567 571 first = next(chunks)
568 572 assert first is None
569 573
570 574 return len(entries), totalfilesize, chunks
571 575
572 576 @contextlib.contextmanager
573 577 def nested(*ctxs):
574 578 this = ctxs[0]
575 579 rest = ctxs[1:]
576 580 with this:
577 581 if rest:
578 582 with nested(*rest):
579 583 yield
580 584 else:
581 585 yield
582 586
583 587 def consumev2(repo, fp, filecount, filesize):
584 588 """Apply the contents from a version 2 streaming clone.
585 589
586 590 Data is read from an object that only needs to provide a ``read(size)``
587 591 method.
588 592 """
589 593 with repo.lock():
590 594 repo.ui.status(_('%d files to transfer, %s of data\n') %
591 595 (filecount, util.bytecount(filesize)))
592 596
593 597 start = util.timer()
594 598 progress = repo.ui.makeprogress(_('clone'), total=filesize,
595 599 unit=_('bytes'))
596 600 progress.update(0)
597 601
598 602 vfsmap = _makemap(repo)
599 603
600 604 with repo.transaction('clone'):
601 605 ctxs = (vfs.backgroundclosing(repo.ui)
602 606 for vfs in vfsmap.values())
603 607 with nested(*ctxs):
604 608 for i in range(filecount):
605 609 src = util.readexactly(fp, 1)
606 610 vfs = vfsmap[src]
607 611 namelen = util.uvarintdecodestream(fp)
608 612 datalen = util.uvarintdecodestream(fp)
609 613
610 614 name = util.readexactly(fp, namelen)
611 615
612 616 if repo.ui.debugflag:
613 617 repo.ui.debug('adding [%s] %s (%s)\n' %
614 618 (src, name, util.bytecount(datalen)))
615 619
616 620 with vfs(name, 'w') as ofp:
617 621 for chunk in util.filechunkiter(fp, limit=datalen):
618 622 progress.increment(step=len(chunk))
619 623 ofp.write(chunk)
620 624
621 625 # force @filecache properties to be reloaded from
622 626 # streamclone-ed file at next access
623 627 repo.invalidate(clearfilecache=True)
624 628
625 629 elapsed = util.timer() - start
626 630 if elapsed <= 0:
627 631 elapsed = 0.001
628 632 repo.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
629 633 (util.bytecount(progress.pos), elapsed,
630 634 util.bytecount(progress.pos / elapsed)))
631 635 progress.complete()
632 636
633 637 def applybundlev2(repo, fp, filecount, filesize, requirements):
634 638 from . import localrepo
635 639
636 640 missingreqs = [r for r in requirements if r not in repo.supported]
637 641 if missingreqs:
638 642 raise error.Abort(_('unable to apply stream clone: '
639 643 'unsupported format: %s') %
640 644 ', '.join(sorted(missingreqs)))
641 645
642 646 consumev2(repo, fp, filecount, filesize)
643 647
644 648 # new requirements = old non-format requirements +
645 649 # new format-related remote requirements
646 650 # requirements from the streamed-in repository
647 651 repo.requirements = set(requirements) | (
648 652 repo.requirements - repo.supportedformats)
649 653 repo.svfs.options = localrepo.resolvestorevfsoptions(
650 654 repo.ui, repo.requirements, repo.features)
651 655 repo._writerequirements()
General Comments 0
You need to be logged in to leave comments. Login now