##// END OF EJS Templates
bundle1: fix bundle1-denied reporting for pull over ssh...
Pierre-Yves David -
r30912:3d4afc2f stable
parent child Browse files
Show More
@@ -1,1030 +1,1033 b''
1 1 # wireproto.py - generic wire protocol support functions
2 2 #
3 3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.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 hashlib
11 11 import itertools
12 12 import os
13 13 import tempfile
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 bin,
18 18 hex,
19 19 )
20 20
21 21 from . import (
22 22 bundle2,
23 23 changegroup as changegroupmod,
24 24 encoding,
25 25 error,
26 26 exchange,
27 27 peer,
28 28 pushkey as pushkeymod,
29 29 streamclone,
30 30 util,
31 31 )
32 32
33 33 urlerr = util.urlerr
34 34 urlreq = util.urlreq
35 35
36 36 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
37 37 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
38 38 'IncompatibleClient')
39 39 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
40 40
41 41 class abstractserverproto(object):
42 42 """abstract class that summarizes the protocol API
43 43
44 44 Used as reference and documentation.
45 45 """
46 46
47 47 def getargs(self, args):
48 48 """return the value for arguments in <args>
49 49
50 50 returns a list of values (same order as <args>)"""
51 51 raise NotImplementedError()
52 52
53 53 def getfile(self, fp):
54 54 """write the whole content of a file into a file like object
55 55
56 56 The file is in the form::
57 57
58 58 (<chunk-size>\n<chunk>)+0\n
59 59
60 60 chunk size is the ascii version of the int.
61 61 """
62 62 raise NotImplementedError()
63 63
64 64 def redirect(self):
65 65 """may setup interception for stdout and stderr
66 66
67 67 See also the `restore` method."""
68 68 raise NotImplementedError()
69 69
70 70 # If the `redirect` function does install interception, the `restore`
71 71 # function MUST be defined. If interception is not used, this function
72 72 # MUST NOT be defined.
73 73 #
74 74 # left commented here on purpose
75 75 #
76 76 #def restore(self):
77 77 # """reinstall previous stdout and stderr and return intercepted stdout
78 78 # """
79 79 # raise NotImplementedError()
80 80
81 81 class remotebatch(peer.batcher):
82 82 '''batches the queued calls; uses as few roundtrips as possible'''
83 83 def __init__(self, remote):
84 84 '''remote must support _submitbatch(encbatch) and
85 85 _submitone(op, encargs)'''
86 86 peer.batcher.__init__(self)
87 87 self.remote = remote
88 88 def submit(self):
89 89 req, rsp = [], []
90 90 for name, args, opts, resref in self.calls:
91 91 mtd = getattr(self.remote, name)
92 92 batchablefn = getattr(mtd, 'batchable', None)
93 93 if batchablefn is not None:
94 94 batchable = batchablefn(mtd.im_self, *args, **opts)
95 95 encargsorres, encresref = next(batchable)
96 96 if encresref:
97 97 req.append((name, encargsorres,))
98 98 rsp.append((batchable, encresref, resref,))
99 99 else:
100 100 resref.set(encargsorres)
101 101 else:
102 102 if req:
103 103 self._submitreq(req, rsp)
104 104 req, rsp = [], []
105 105 resref.set(mtd(*args, **opts))
106 106 if req:
107 107 self._submitreq(req, rsp)
108 108 def _submitreq(self, req, rsp):
109 109 encresults = self.remote._submitbatch(req)
110 110 for encres, r in zip(encresults, rsp):
111 111 batchable, encresref, resref = r
112 112 encresref.set(encres)
113 113 resref.set(next(batchable))
114 114
115 115 class remoteiterbatcher(peer.iterbatcher):
116 116 def __init__(self, remote):
117 117 super(remoteiterbatcher, self).__init__()
118 118 self._remote = remote
119 119
120 120 def __getattr__(self, name):
121 121 if not getattr(self._remote, name, False):
122 122 raise AttributeError(
123 123 'Attempted to iterbatch non-batchable call to %r' % name)
124 124 return super(remoteiterbatcher, self).__getattr__(name)
125 125
126 126 def submit(self):
127 127 """Break the batch request into many patch calls and pipeline them.
128 128
129 129 This is mostly valuable over http where request sizes can be
130 130 limited, but can be used in other places as well.
131 131 """
132 132 req, rsp = [], []
133 133 for name, args, opts, resref in self.calls:
134 134 mtd = getattr(self._remote, name)
135 135 batchable = mtd.batchable(mtd.im_self, *args, **opts)
136 136 encargsorres, encresref = next(batchable)
137 137 assert encresref
138 138 req.append((name, encargsorres))
139 139 rsp.append((batchable, encresref))
140 140 if req:
141 141 self._resultiter = self._remote._submitbatch(req)
142 142 self._rsp = rsp
143 143
144 144 def results(self):
145 145 for (batchable, encresref), encres in itertools.izip(
146 146 self._rsp, self._resultiter):
147 147 encresref.set(encres)
148 148 yield next(batchable)
149 149
150 150 # Forward a couple of names from peer to make wireproto interactions
151 151 # slightly more sensible.
152 152 batchable = peer.batchable
153 153 future = peer.future
154 154
155 155 # list of nodes encoding / decoding
156 156
157 157 def decodelist(l, sep=' '):
158 158 if l:
159 159 return map(bin, l.split(sep))
160 160 return []
161 161
162 162 def encodelist(l, sep=' '):
163 163 try:
164 164 return sep.join(map(hex, l))
165 165 except TypeError:
166 166 raise
167 167
168 168 # batched call argument encoding
169 169
170 170 def escapearg(plain):
171 171 return (plain
172 172 .replace(':', ':c')
173 173 .replace(',', ':o')
174 174 .replace(';', ':s')
175 175 .replace('=', ':e'))
176 176
177 177 def unescapearg(escaped):
178 178 return (escaped
179 179 .replace(':e', '=')
180 180 .replace(':s', ';')
181 181 .replace(':o', ',')
182 182 .replace(':c', ':'))
183 183
184 184 def encodebatchcmds(req):
185 185 """Return a ``cmds`` argument value for the ``batch`` command."""
186 186 cmds = []
187 187 for op, argsdict in req:
188 188 # Old servers didn't properly unescape argument names. So prevent
189 189 # the sending of argument names that may not be decoded properly by
190 190 # servers.
191 191 assert all(escapearg(k) == k for k in argsdict)
192 192
193 193 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
194 194 for k, v in argsdict.iteritems())
195 195 cmds.append('%s %s' % (op, args))
196 196
197 197 return ';'.join(cmds)
198 198
199 199 # mapping of options accepted by getbundle and their types
200 200 #
201 201 # Meant to be extended by extensions. It is extensions responsibility to ensure
202 202 # such options are properly processed in exchange.getbundle.
203 203 #
204 204 # supported types are:
205 205 #
206 206 # :nodes: list of binary nodes
207 207 # :csv: list of comma-separated values
208 208 # :scsv: list of comma-separated values return as set
209 209 # :plain: string with no transformation needed.
210 210 gboptsmap = {'heads': 'nodes',
211 211 'common': 'nodes',
212 212 'obsmarkers': 'boolean',
213 213 'bundlecaps': 'scsv',
214 214 'listkeys': 'csv',
215 215 'cg': 'boolean',
216 216 'cbattempted': 'boolean'}
217 217
218 218 # client side
219 219
220 220 class wirepeer(peer.peerrepository):
221 221 """Client-side interface for communicating with a peer repository.
222 222
223 223 Methods commonly call wire protocol commands of the same name.
224 224
225 225 See also httppeer.py and sshpeer.py for protocol-specific
226 226 implementations of this interface.
227 227 """
228 228 def batch(self):
229 229 if self.capable('batch'):
230 230 return remotebatch(self)
231 231 else:
232 232 return peer.localbatch(self)
233 233 def _submitbatch(self, req):
234 234 """run batch request <req> on the server
235 235
236 236 Returns an iterator of the raw responses from the server.
237 237 """
238 238 rsp = self._callstream("batch", cmds=encodebatchcmds(req))
239 239 chunk = rsp.read(1024)
240 240 work = [chunk]
241 241 while chunk:
242 242 while ';' not in chunk and chunk:
243 243 chunk = rsp.read(1024)
244 244 work.append(chunk)
245 245 merged = ''.join(work)
246 246 while ';' in merged:
247 247 one, merged = merged.split(';', 1)
248 248 yield unescapearg(one)
249 249 chunk = rsp.read(1024)
250 250 work = [merged, chunk]
251 251 yield unescapearg(''.join(work))
252 252
253 253 def _submitone(self, op, args):
254 254 return self._call(op, **args)
255 255
256 256 def iterbatch(self):
257 257 return remoteiterbatcher(self)
258 258
259 259 @batchable
260 260 def lookup(self, key):
261 261 self.requirecap('lookup', _('look up remote revision'))
262 262 f = future()
263 263 yield {'key': encoding.fromlocal(key)}, f
264 264 d = f.value
265 265 success, data = d[:-1].split(" ", 1)
266 266 if int(success):
267 267 yield bin(data)
268 268 self._abort(error.RepoError(data))
269 269
270 270 @batchable
271 271 def heads(self):
272 272 f = future()
273 273 yield {}, f
274 274 d = f.value
275 275 try:
276 276 yield decodelist(d[:-1])
277 277 except ValueError:
278 278 self._abort(error.ResponseError(_("unexpected response:"), d))
279 279
280 280 @batchable
281 281 def known(self, nodes):
282 282 f = future()
283 283 yield {'nodes': encodelist(nodes)}, f
284 284 d = f.value
285 285 try:
286 286 yield [bool(int(b)) for b in d]
287 287 except ValueError:
288 288 self._abort(error.ResponseError(_("unexpected response:"), d))
289 289
290 290 @batchable
291 291 def branchmap(self):
292 292 f = future()
293 293 yield {}, f
294 294 d = f.value
295 295 try:
296 296 branchmap = {}
297 297 for branchpart in d.splitlines():
298 298 branchname, branchheads = branchpart.split(' ', 1)
299 299 branchname = encoding.tolocal(urlreq.unquote(branchname))
300 300 branchheads = decodelist(branchheads)
301 301 branchmap[branchname] = branchheads
302 302 yield branchmap
303 303 except TypeError:
304 304 self._abort(error.ResponseError(_("unexpected response:"), d))
305 305
306 306 def branches(self, nodes):
307 307 n = encodelist(nodes)
308 308 d = self._call("branches", nodes=n)
309 309 try:
310 310 br = [tuple(decodelist(b)) for b in d.splitlines()]
311 311 return br
312 312 except ValueError:
313 313 self._abort(error.ResponseError(_("unexpected response:"), d))
314 314
315 315 def between(self, pairs):
316 316 batch = 8 # avoid giant requests
317 317 r = []
318 318 for i in xrange(0, len(pairs), batch):
319 319 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
320 320 d = self._call("between", pairs=n)
321 321 try:
322 322 r.extend(l and decodelist(l) or [] for l in d.splitlines())
323 323 except ValueError:
324 324 self._abort(error.ResponseError(_("unexpected response:"), d))
325 325 return r
326 326
327 327 @batchable
328 328 def pushkey(self, namespace, key, old, new):
329 329 if not self.capable('pushkey'):
330 330 yield False, None
331 331 f = future()
332 332 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
333 333 yield {'namespace': encoding.fromlocal(namespace),
334 334 'key': encoding.fromlocal(key),
335 335 'old': encoding.fromlocal(old),
336 336 'new': encoding.fromlocal(new)}, f
337 337 d = f.value
338 338 d, output = d.split('\n', 1)
339 339 try:
340 340 d = bool(int(d))
341 341 except ValueError:
342 342 raise error.ResponseError(
343 343 _('push failed (unexpected response):'), d)
344 344 for l in output.splitlines(True):
345 345 self.ui.status(_('remote: '), l)
346 346 yield d
347 347
348 348 @batchable
349 349 def listkeys(self, namespace):
350 350 if not self.capable('pushkey'):
351 351 yield {}, None
352 352 f = future()
353 353 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
354 354 yield {'namespace': encoding.fromlocal(namespace)}, f
355 355 d = f.value
356 356 self.ui.debug('received listkey for "%s": %i bytes\n'
357 357 % (namespace, len(d)))
358 358 yield pushkeymod.decodekeys(d)
359 359
360 360 def stream_out(self):
361 361 return self._callstream('stream_out')
362 362
363 363 def changegroup(self, nodes, kind):
364 364 n = encodelist(nodes)
365 365 f = self._callcompressable("changegroup", roots=n)
366 366 return changegroupmod.cg1unpacker(f, 'UN')
367 367
368 368 def changegroupsubset(self, bases, heads, kind):
369 369 self.requirecap('changegroupsubset', _('look up remote changes'))
370 370 bases = encodelist(bases)
371 371 heads = encodelist(heads)
372 372 f = self._callcompressable("changegroupsubset",
373 373 bases=bases, heads=heads)
374 374 return changegroupmod.cg1unpacker(f, 'UN')
375 375
376 376 def getbundle(self, source, **kwargs):
377 377 self.requirecap('getbundle', _('look up remote changes'))
378 378 opts = {}
379 379 bundlecaps = kwargs.get('bundlecaps')
380 380 if bundlecaps is not None:
381 381 kwargs['bundlecaps'] = sorted(bundlecaps)
382 382 else:
383 383 bundlecaps = () # kwargs could have it to None
384 384 for key, value in kwargs.iteritems():
385 385 if value is None:
386 386 continue
387 387 keytype = gboptsmap.get(key)
388 388 if keytype is None:
389 389 assert False, 'unexpected'
390 390 elif keytype == 'nodes':
391 391 value = encodelist(value)
392 392 elif keytype in ('csv', 'scsv'):
393 393 value = ','.join(value)
394 394 elif keytype == 'boolean':
395 395 value = '%i' % bool(value)
396 396 elif keytype != 'plain':
397 397 raise KeyError('unknown getbundle option type %s'
398 398 % keytype)
399 399 opts[key] = value
400 400 f = self._callcompressable("getbundle", **opts)
401 401 if any((cap.startswith('HG2') for cap in bundlecaps)):
402 402 return bundle2.getunbundler(self.ui, f)
403 403 else:
404 404 return changegroupmod.cg1unpacker(f, 'UN')
405 405
406 406 def unbundle(self, cg, heads, url):
407 407 '''Send cg (a readable file-like object representing the
408 408 changegroup to push, typically a chunkbuffer object) to the
409 409 remote server as a bundle.
410 410
411 411 When pushing a bundle10 stream, return an integer indicating the
412 412 result of the push (see localrepository.addchangegroup()).
413 413
414 414 When pushing a bundle20 stream, return a bundle20 stream.
415 415
416 416 `url` is the url the client thinks it's pushing to, which is
417 417 visible to hooks.
418 418 '''
419 419
420 420 if heads != ['force'] and self.capable('unbundlehash'):
421 421 heads = encodelist(['hashed',
422 422 hashlib.sha1(''.join(sorted(heads))).digest()])
423 423 else:
424 424 heads = encodelist(heads)
425 425
426 426 if util.safehasattr(cg, 'deltaheader'):
427 427 # this a bundle10, do the old style call sequence
428 428 ret, output = self._callpush("unbundle", cg, heads=heads)
429 429 if ret == "":
430 430 raise error.ResponseError(
431 431 _('push failed:'), output)
432 432 try:
433 433 ret = int(ret)
434 434 except ValueError:
435 435 raise error.ResponseError(
436 436 _('push failed (unexpected response):'), ret)
437 437
438 438 for l in output.splitlines(True):
439 439 self.ui.status(_('remote: '), l)
440 440 else:
441 441 # bundle2 push. Send a stream, fetch a stream.
442 442 stream = self._calltwowaystream('unbundle', cg, heads=heads)
443 443 ret = bundle2.getunbundler(self.ui, stream)
444 444 return ret
445 445
446 446 def debugwireargs(self, one, two, three=None, four=None, five=None):
447 447 # don't pass optional arguments left at their default value
448 448 opts = {}
449 449 if three is not None:
450 450 opts['three'] = three
451 451 if four is not None:
452 452 opts['four'] = four
453 453 return self._call('debugwireargs', one=one, two=two, **opts)
454 454
455 455 def _call(self, cmd, **args):
456 456 """execute <cmd> on the server
457 457
458 458 The command is expected to return a simple string.
459 459
460 460 returns the server reply as a string."""
461 461 raise NotImplementedError()
462 462
463 463 def _callstream(self, cmd, **args):
464 464 """execute <cmd> on the server
465 465
466 466 The command is expected to return a stream. Note that if the
467 467 command doesn't return a stream, _callstream behaves
468 468 differently for ssh and http peers.
469 469
470 470 returns the server reply as a file like object.
471 471 """
472 472 raise NotImplementedError()
473 473
474 474 def _callcompressable(self, cmd, **args):
475 475 """execute <cmd> on the server
476 476
477 477 The command is expected to return a stream.
478 478
479 479 The stream may have been compressed in some implementations. This
480 480 function takes care of the decompression. This is the only difference
481 481 with _callstream.
482 482
483 483 returns the server reply as a file like object.
484 484 """
485 485 raise NotImplementedError()
486 486
487 487 def _callpush(self, cmd, fp, **args):
488 488 """execute a <cmd> on server
489 489
490 490 The command is expected to be related to a push. Push has a special
491 491 return method.
492 492
493 493 returns the server reply as a (ret, output) tuple. ret is either
494 494 empty (error) or a stringified int.
495 495 """
496 496 raise NotImplementedError()
497 497
498 498 def _calltwowaystream(self, cmd, fp, **args):
499 499 """execute <cmd> on server
500 500
501 501 The command will send a stream to the server and get a stream in reply.
502 502 """
503 503 raise NotImplementedError()
504 504
505 505 def _abort(self, exception):
506 506 """clearly abort the wire protocol connection and raise the exception
507 507 """
508 508 raise NotImplementedError()
509 509
510 510 # server side
511 511
512 512 # wire protocol command can either return a string or one of these classes.
513 513 class streamres(object):
514 514 """wireproto reply: binary stream
515 515
516 516 The call was successful and the result is a stream.
517 517
518 518 Accepts either a generator or an object with a ``read(size)`` method.
519 519
520 520 ``v1compressible`` indicates whether this data can be compressed to
521 521 "version 1" clients (technically: HTTP peers using
522 522 application/mercurial-0.1 media type). This flag should NOT be used on
523 523 new commands because new clients should support a more modern compression
524 524 mechanism.
525 525 """
526 526 def __init__(self, gen=None, reader=None, v1compressible=False):
527 527 self.gen = gen
528 528 self.reader = reader
529 529 self.v1compressible = v1compressible
530 530
531 531 class pushres(object):
532 532 """wireproto reply: success with simple integer return
533 533
534 534 The call was successful and returned an integer contained in `self.res`.
535 535 """
536 536 def __init__(self, res):
537 537 self.res = res
538 538
539 539 class pusherr(object):
540 540 """wireproto reply: failure
541 541
542 542 The call failed. The `self.res` attribute contains the error message.
543 543 """
544 544 def __init__(self, res):
545 545 self.res = res
546 546
547 547 class ooberror(object):
548 548 """wireproto reply: failure of a batch of operation
549 549
550 550 Something failed during a batch call. The error message is stored in
551 551 `self.message`.
552 552 """
553 553 def __init__(self, message):
554 554 self.message = message
555 555
556 556 def getdispatchrepo(repo, proto, command):
557 557 """Obtain the repo used for processing wire protocol commands.
558 558
559 559 The intent of this function is to serve as a monkeypatch point for
560 560 extensions that need commands to operate on different repo views under
561 561 specialized circumstances.
562 562 """
563 563 return repo.filtered('served')
564 564
565 565 def dispatch(repo, proto, command):
566 566 repo = getdispatchrepo(repo, proto, command)
567 567 func, spec = commands[command]
568 568 args = proto.getargs(spec)
569 569 return func(repo, proto, *args)
570 570
571 571 def options(cmd, keys, others):
572 572 opts = {}
573 573 for k in keys:
574 574 if k in others:
575 575 opts[k] = others[k]
576 576 del others[k]
577 577 if others:
578 578 util.stderr.write("warning: %s ignored unexpected arguments %s\n"
579 579 % (cmd, ",".join(others)))
580 580 return opts
581 581
582 582 def bundle1allowed(repo, action):
583 583 """Whether a bundle1 operation is allowed from the server.
584 584
585 585 Priority is:
586 586
587 587 1. server.bundle1gd.<action> (if generaldelta active)
588 588 2. server.bundle1.<action>
589 589 3. server.bundle1gd (if generaldelta active)
590 590 4. server.bundle1
591 591 """
592 592 ui = repo.ui
593 593 gd = 'generaldelta' in repo.requirements
594 594
595 595 if gd:
596 596 v = ui.configbool('server', 'bundle1gd.%s' % action, None)
597 597 if v is not None:
598 598 return v
599 599
600 600 v = ui.configbool('server', 'bundle1.%s' % action, None)
601 601 if v is not None:
602 602 return v
603 603
604 604 if gd:
605 605 v = ui.configbool('server', 'bundle1gd', None)
606 606 if v is not None:
607 607 return v
608 608
609 609 return ui.configbool('server', 'bundle1', True)
610 610
611 611 def supportedcompengines(ui, proto, role):
612 612 """Obtain the list of supported compression engines for a request."""
613 613 assert role in (util.CLIENTROLE, util.SERVERROLE)
614 614
615 615 compengines = util.compengines.supportedwireengines(role)
616 616
617 617 # Allow config to override default list and ordering.
618 618 if role == util.SERVERROLE:
619 619 configengines = ui.configlist('server', 'compressionengines')
620 620 config = 'server.compressionengines'
621 621 else:
622 622 # This is currently implemented mainly to facilitate testing. In most
623 623 # cases, the server should be in charge of choosing a compression engine
624 624 # because a server has the most to lose from a sub-optimal choice. (e.g.
625 625 # CPU DoS due to an expensive engine or a network DoS due to poor
626 626 # compression ratio).
627 627 configengines = ui.configlist('experimental',
628 628 'clientcompressionengines')
629 629 config = 'experimental.clientcompressionengines'
630 630
631 631 # No explicit config. Filter out the ones that aren't supposed to be
632 632 # advertised and return default ordering.
633 633 if not configengines:
634 634 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
635 635 return [e for e in compengines
636 636 if getattr(e.wireprotosupport(), attr) > 0]
637 637
638 638 # If compression engines are listed in the config, assume there is a good
639 639 # reason for it (like server operators wanting to achieve specific
640 640 # performance characteristics). So fail fast if the config references
641 641 # unusable compression engines.
642 642 validnames = set(e.name() for e in compengines)
643 643 invalidnames = set(e for e in configengines if e not in validnames)
644 644 if invalidnames:
645 645 raise error.Abort(_('invalid compression engine defined in %s: %s') %
646 646 (config, ', '.join(sorted(invalidnames))))
647 647
648 648 compengines = [e for e in compengines if e.name() in configengines]
649 649 compengines = sorted(compengines,
650 650 key=lambda e: configengines.index(e.name()))
651 651
652 652 if not compengines:
653 653 raise error.Abort(_('%s config option does not specify any known '
654 654 'compression engines') % config,
655 655 hint=_('usable compression engines: %s') %
656 656 ', '.sorted(validnames))
657 657
658 658 return compengines
659 659
660 660 # list of commands
661 661 commands = {}
662 662
663 663 def wireprotocommand(name, args=''):
664 664 """decorator for wire protocol command"""
665 665 def register(func):
666 666 commands[name] = (func, args)
667 667 return func
668 668 return register
669 669
670 670 @wireprotocommand('batch', 'cmds *')
671 671 def batch(repo, proto, cmds, others):
672 672 repo = repo.filtered("served")
673 673 res = []
674 674 for pair in cmds.split(';'):
675 675 op, args = pair.split(' ', 1)
676 676 vals = {}
677 677 for a in args.split(','):
678 678 if a:
679 679 n, v = a.split('=')
680 680 vals[unescapearg(n)] = unescapearg(v)
681 681 func, spec = commands[op]
682 682 if spec:
683 683 keys = spec.split()
684 684 data = {}
685 685 for k in keys:
686 686 if k == '*':
687 687 star = {}
688 688 for key in vals.keys():
689 689 if key not in keys:
690 690 star[key] = vals[key]
691 691 data['*'] = star
692 692 else:
693 693 data[k] = vals[k]
694 694 result = func(repo, proto, *[data[k] for k in keys])
695 695 else:
696 696 result = func(repo, proto)
697 697 if isinstance(result, ooberror):
698 698 return result
699 699 res.append(escapearg(result))
700 700 return ';'.join(res)
701 701
702 702 @wireprotocommand('between', 'pairs')
703 703 def between(repo, proto, pairs):
704 704 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
705 705 r = []
706 706 for b in repo.between(pairs):
707 707 r.append(encodelist(b) + "\n")
708 708 return "".join(r)
709 709
710 710 @wireprotocommand('branchmap')
711 711 def branchmap(repo, proto):
712 712 branchmap = repo.branchmap()
713 713 heads = []
714 714 for branch, nodes in branchmap.iteritems():
715 715 branchname = urlreq.quote(encoding.fromlocal(branch))
716 716 branchnodes = encodelist(nodes)
717 717 heads.append('%s %s' % (branchname, branchnodes))
718 718 return '\n'.join(heads)
719 719
720 720 @wireprotocommand('branches', 'nodes')
721 721 def branches(repo, proto, nodes):
722 722 nodes = decodelist(nodes)
723 723 r = []
724 724 for b in repo.branches(nodes):
725 725 r.append(encodelist(b) + "\n")
726 726 return "".join(r)
727 727
728 728 @wireprotocommand('clonebundles', '')
729 729 def clonebundles(repo, proto):
730 730 """Server command for returning info for available bundles to seed clones.
731 731
732 732 Clients will parse this response and determine what bundle to fetch.
733 733
734 734 Extensions may wrap this command to filter or dynamically emit data
735 735 depending on the request. e.g. you could advertise URLs for the closest
736 736 data center given the client's IP address.
737 737 """
738 738 return repo.opener.tryread('clonebundles.manifest')
739 739
740 740 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
741 741 'known', 'getbundle', 'unbundlehash', 'batch']
742 742
743 743 def _capabilities(repo, proto):
744 744 """return a list of capabilities for a repo
745 745
746 746 This function exists to allow extensions to easily wrap capabilities
747 747 computation
748 748
749 749 - returns a lists: easy to alter
750 750 - change done here will be propagated to both `capabilities` and `hello`
751 751 command without any other action needed.
752 752 """
753 753 # copy to prevent modification of the global list
754 754 caps = list(wireprotocaps)
755 755 if streamclone.allowservergeneration(repo.ui):
756 756 if repo.ui.configbool('server', 'preferuncompressed', False):
757 757 caps.append('stream-preferred')
758 758 requiredformats = repo.requirements & repo.supportedformats
759 759 # if our local revlogs are just revlogv1, add 'stream' cap
760 760 if not requiredformats - set(('revlogv1',)):
761 761 caps.append('stream')
762 762 # otherwise, add 'streamreqs' detailing our local revlog format
763 763 else:
764 764 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
765 765 if repo.ui.configbool('experimental', 'bundle2-advertise', True):
766 766 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
767 767 caps.append('bundle2=' + urlreq.quote(capsblob))
768 768 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
769 769
770 770 if proto.name == 'http':
771 771 caps.append('httpheader=%d' %
772 772 repo.ui.configint('server', 'maxhttpheaderlen', 1024))
773 773 if repo.ui.configbool('experimental', 'httppostargs', False):
774 774 caps.append('httppostargs')
775 775
776 776 # FUTURE advertise 0.2rx once support is implemented
777 777 # FUTURE advertise minrx and mintx after consulting config option
778 778 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
779 779
780 780 compengines = supportedcompengines(repo.ui, proto, util.SERVERROLE)
781 781 if compengines:
782 782 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
783 783 for e in compengines)
784 784 caps.append('compression=%s' % comptypes)
785 785
786 786 return caps
787 787
788 788 # If you are writing an extension and consider wrapping this function. Wrap
789 789 # `_capabilities` instead.
790 790 @wireprotocommand('capabilities')
791 791 def capabilities(repo, proto):
792 792 return ' '.join(_capabilities(repo, proto))
793 793
794 794 @wireprotocommand('changegroup', 'roots')
795 795 def changegroup(repo, proto, roots):
796 796 nodes = decodelist(roots)
797 797 cg = changegroupmod.changegroup(repo, nodes, 'serve')
798 798 return streamres(reader=cg, v1compressible=True)
799 799
800 800 @wireprotocommand('changegroupsubset', 'bases heads')
801 801 def changegroupsubset(repo, proto, bases, heads):
802 802 bases = decodelist(bases)
803 803 heads = decodelist(heads)
804 804 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
805 805 return streamres(reader=cg, v1compressible=True)
806 806
807 807 @wireprotocommand('debugwireargs', 'one two *')
808 808 def debugwireargs(repo, proto, one, two, others):
809 809 # only accept optional args from the known set
810 810 opts = options('debugwireargs', ['three', 'four'], others)
811 811 return repo.debugwireargs(one, two, **opts)
812 812
813 813 @wireprotocommand('getbundle', '*')
814 814 def getbundle(repo, proto, others):
815 815 opts = options('getbundle', gboptsmap.keys(), others)
816 816 for k, v in opts.iteritems():
817 817 keytype = gboptsmap[k]
818 818 if keytype == 'nodes':
819 819 opts[k] = decodelist(v)
820 820 elif keytype == 'csv':
821 821 opts[k] = list(v.split(','))
822 822 elif keytype == 'scsv':
823 823 opts[k] = set(v.split(','))
824 824 elif keytype == 'boolean':
825 825 # Client should serialize False as '0', which is a non-empty string
826 826 # so it evaluates as a True bool.
827 827 if v == '0':
828 828 opts[k] = False
829 829 else:
830 830 opts[k] = bool(v)
831 831 elif keytype != 'plain':
832 832 raise KeyError('unknown getbundle option type %s'
833 833 % keytype)
834 834
835 835 if not bundle1allowed(repo, 'pull'):
836 836 if not exchange.bundle2requested(opts.get('bundlecaps')):
837 return ooberror(bundle2required)
837 if proto.name == 'http':
838 return ooberror(bundle2required)
839 raise error.Abort(bundle2requiredmain,
840 hint=bundle2requiredhint)
838 841
839 842 chunks = exchange.getbundlechunks(repo, 'serve', **opts)
840 843 return streamres(gen=chunks, v1compressible=True)
841 844
842 845 @wireprotocommand('heads')
843 846 def heads(repo, proto):
844 847 h = repo.heads()
845 848 return encodelist(h) + "\n"
846 849
847 850 @wireprotocommand('hello')
848 851 def hello(repo, proto):
849 852 '''the hello command returns a set of lines describing various
850 853 interesting things about the server, in an RFC822-like format.
851 854 Currently the only one defined is "capabilities", which
852 855 consists of a line in the form:
853 856
854 857 capabilities: space separated list of tokens
855 858 '''
856 859 return "capabilities: %s\n" % (capabilities(repo, proto))
857 860
858 861 @wireprotocommand('listkeys', 'namespace')
859 862 def listkeys(repo, proto, namespace):
860 863 d = repo.listkeys(encoding.tolocal(namespace)).items()
861 864 return pushkeymod.encodekeys(d)
862 865
863 866 @wireprotocommand('lookup', 'key')
864 867 def lookup(repo, proto, key):
865 868 try:
866 869 k = encoding.tolocal(key)
867 870 c = repo[k]
868 871 r = c.hex()
869 872 success = 1
870 873 except Exception as inst:
871 874 r = str(inst)
872 875 success = 0
873 876 return "%s %s\n" % (success, r)
874 877
875 878 @wireprotocommand('known', 'nodes *')
876 879 def known(repo, proto, nodes, others):
877 880 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
878 881
879 882 @wireprotocommand('pushkey', 'namespace key old new')
880 883 def pushkey(repo, proto, namespace, key, old, new):
881 884 # compatibility with pre-1.8 clients which were accidentally
882 885 # sending raw binary nodes rather than utf-8-encoded hex
883 886 if len(new) == 20 and new.encode('string-escape') != new:
884 887 # looks like it could be a binary node
885 888 try:
886 889 new.decode('utf-8')
887 890 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
888 891 except UnicodeDecodeError:
889 892 pass # binary, leave unmodified
890 893 else:
891 894 new = encoding.tolocal(new) # normal path
892 895
893 896 if util.safehasattr(proto, 'restore'):
894 897
895 898 proto.redirect()
896 899
897 900 try:
898 901 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
899 902 encoding.tolocal(old), new) or False
900 903 except error.Abort:
901 904 r = False
902 905
903 906 output = proto.restore()
904 907
905 908 return '%s\n%s' % (int(r), output)
906 909
907 910 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
908 911 encoding.tolocal(old), new)
909 912 return '%s\n' % int(r)
910 913
911 914 @wireprotocommand('stream_out')
912 915 def stream(repo, proto):
913 916 '''If the server supports streaming clone, it advertises the "stream"
914 917 capability with a value representing the version and flags of the repo
915 918 it is serving. Client checks to see if it understands the format.
916 919 '''
917 920 if not streamclone.allowservergeneration(repo.ui):
918 921 return '1\n'
919 922
920 923 def getstream(it):
921 924 yield '0\n'
922 925 for chunk in it:
923 926 yield chunk
924 927
925 928 try:
926 929 # LockError may be raised before the first result is yielded. Don't
927 930 # emit output until we're sure we got the lock successfully.
928 931 it = streamclone.generatev1wireproto(repo)
929 932 return streamres(gen=getstream(it))
930 933 except error.LockError:
931 934 return '2\n'
932 935
933 936 @wireprotocommand('unbundle', 'heads')
934 937 def unbundle(repo, proto, heads):
935 938 their_heads = decodelist(heads)
936 939
937 940 try:
938 941 proto.redirect()
939 942
940 943 exchange.check_heads(repo, their_heads, 'preparing changes')
941 944
942 945 # write bundle data to temporary file because it can be big
943 946 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
944 947 fp = os.fdopen(fd, 'wb+')
945 948 r = 0
946 949 try:
947 950 proto.getfile(fp)
948 951 fp.seek(0)
949 952 gen = exchange.readbundle(repo.ui, fp, None)
950 953 if (isinstance(gen, changegroupmod.cg1unpacker)
951 954 and not bundle1allowed(repo, 'push')):
952 955 if proto.name == 'http':
953 956 # need to special case http because stderr do not get to
954 957 # the http client on failed push so we need to abuse some
955 958 # other error type to make sure the message get to the
956 959 # user.
957 960 return ooberror(bundle2required)
958 961 raise error.Abort(bundle2requiredmain,
959 962 hint=bundle2requiredhint)
960 963
961 964 r = exchange.unbundle(repo, gen, their_heads, 'serve',
962 965 proto._client())
963 966 if util.safehasattr(r, 'addpart'):
964 967 # The return looks streamable, we are in the bundle2 case and
965 968 # should return a stream.
966 969 return streamres(gen=r.getchunks())
967 970 return pushres(r)
968 971
969 972 finally:
970 973 fp.close()
971 974 os.unlink(tempname)
972 975
973 976 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
974 977 # handle non-bundle2 case first
975 978 if not getattr(exc, 'duringunbundle2', False):
976 979 try:
977 980 raise
978 981 except error.Abort:
979 982 # The old code we moved used util.stderr directly.
980 983 # We did not change it to minimise code change.
981 984 # This need to be moved to something proper.
982 985 # Feel free to do it.
983 986 util.stderr.write("abort: %s\n" % exc)
984 987 if exc.hint is not None:
985 988 util.stderr.write("(%s)\n" % exc.hint)
986 989 return pushres(0)
987 990 except error.PushRaced:
988 991 return pusherr(str(exc))
989 992
990 993 bundler = bundle2.bundle20(repo.ui)
991 994 for out in getattr(exc, '_bundle2salvagedoutput', ()):
992 995 bundler.addpart(out)
993 996 try:
994 997 try:
995 998 raise
996 999 except error.PushkeyFailed as exc:
997 1000 # check client caps
998 1001 remotecaps = getattr(exc, '_replycaps', None)
999 1002 if (remotecaps is not None
1000 1003 and 'pushkey' not in remotecaps.get('error', ())):
1001 1004 # no support remote side, fallback to Abort handler.
1002 1005 raise
1003 1006 part = bundler.newpart('error:pushkey')
1004 1007 part.addparam('in-reply-to', exc.partid)
1005 1008 if exc.namespace is not None:
1006 1009 part.addparam('namespace', exc.namespace, mandatory=False)
1007 1010 if exc.key is not None:
1008 1011 part.addparam('key', exc.key, mandatory=False)
1009 1012 if exc.new is not None:
1010 1013 part.addparam('new', exc.new, mandatory=False)
1011 1014 if exc.old is not None:
1012 1015 part.addparam('old', exc.old, mandatory=False)
1013 1016 if exc.ret is not None:
1014 1017 part.addparam('ret', exc.ret, mandatory=False)
1015 1018 except error.BundleValueError as exc:
1016 1019 errpart = bundler.newpart('error:unsupportedcontent')
1017 1020 if exc.parttype is not None:
1018 1021 errpart.addparam('parttype', exc.parttype)
1019 1022 if exc.params:
1020 1023 errpart.addparam('params', '\0'.join(exc.params))
1021 1024 except error.Abort as exc:
1022 1025 manargs = [('message', str(exc))]
1023 1026 advargs = []
1024 1027 if exc.hint is not None:
1025 1028 advargs.append(('hint', exc.hint))
1026 1029 bundler.addpart(bundle2.bundlepart('error:abort',
1027 1030 manargs, advargs))
1028 1031 except error.PushRaced as exc:
1029 1032 bundler.newpart('error:pushraced', [('message', str(exc))])
1030 1033 return streamres(gen=bundler.getchunks())
@@ -1,1126 +1,1136 b''
1 1 Test exchange of common information using bundle2
2 2
3 3
4 4 $ getmainid() {
5 5 > hg -R main log --template '{node}\n' --rev "$1"
6 6 > }
7 7
8 8 enable obsolescence
9 9
10 10 $ cp $HGRCPATH $TESTTMP/hgrc.orig
11 11 $ cat > $TESTTMP/bundle2-pushkey-hook.sh << EOF
12 12 > echo pushkey: lock state after \"\$HG_NAMESPACE\"
13 13 > hg debuglock
14 14 > EOF
15 15
16 16 $ cat >> $HGRCPATH << EOF
17 17 > [experimental]
18 18 > evolution=createmarkers,exchange
19 19 > bundle2-output-capture=True
20 20 > [ui]
21 21 > ssh=python "$TESTDIR/dummyssh"
22 22 > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
23 23 > [web]
24 24 > push_ssl = false
25 25 > allow_push = *
26 26 > [phases]
27 27 > publish=False
28 28 > [hooks]
29 29 > pretxnclose.tip = hg log -r tip -T "pre-close-tip:{node|short} {phase} {bookmarks}\n"
30 30 > txnclose.tip = hg log -r tip -T "postclose-tip:{node|short} {phase} {bookmarks}\n"
31 31 > txnclose.env = sh -c "HG_LOCAL= printenv.py txnclose"
32 32 > pushkey= sh "$TESTTMP/bundle2-pushkey-hook.sh"
33 33 > EOF
34 34
35 35 The extension requires a repo (currently unused)
36 36
37 37 $ hg init main
38 38 $ cd main
39 39 $ touch a
40 40 $ hg add a
41 41 $ hg commit -m 'a'
42 42 pre-close-tip:3903775176ed draft
43 43 postclose-tip:3903775176ed draft
44 44 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
45 45
46 46 $ hg unbundle $TESTDIR/bundles/rebase.hg
47 47 adding changesets
48 48 adding manifests
49 49 adding file changes
50 50 added 8 changesets with 7 changes to 7 files (+3 heads)
51 51 pre-close-tip:02de42196ebe draft
52 52 postclose-tip:02de42196ebe draft
53 53 txnclose hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNID=TXN:* HG_TXNNAME=unbundle (glob)
54 54 bundle:*/tests/bundles/rebase.hg HG_URL=bundle:*/tests/bundles/rebase.hg (glob)
55 55 (run 'hg heads' to see heads, 'hg merge' to merge)
56 56
57 57 $ cd ..
58 58
59 59 Real world exchange
60 60 =====================
61 61
62 62 Add more obsolescence information
63 63
64 64 $ hg -R main debugobsolete -d '0 0' 1111111111111111111111111111111111111111 `getmainid 9520eea781bc`
65 65 pre-close-tip:02de42196ebe draft
66 66 postclose-tip:02de42196ebe draft
67 67 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
68 68 $ hg -R main debugobsolete -d '0 0' 2222222222222222222222222222222222222222 `getmainid 24b6387c8c8c`
69 69 pre-close-tip:02de42196ebe draft
70 70 postclose-tip:02de42196ebe draft
71 71 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
72 72
73 73 clone --pull
74 74
75 75 $ hg -R main phase --public cd010b8cd998
76 76 pre-close-tip:02de42196ebe draft
77 77 postclose-tip:02de42196ebe draft
78 78 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
79 79 $ hg clone main other --pull --rev 9520eea781bc
80 80 adding changesets
81 81 adding manifests
82 82 adding file changes
83 83 added 2 changesets with 2 changes to 2 files
84 84 1 new obsolescence markers
85 85 pre-close-tip:9520eea781bc draft
86 86 postclose-tip:9520eea781bc draft
87 87 txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=9520eea781bcca16c1e15acc0ba14335a0e8e5ba HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
88 88 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
89 89 updating to branch default
90 90 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 91 $ hg -R other log -G
92 92 @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
93 93 |
94 94 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
95 95
96 96 $ hg -R other debugobsolete
97 97 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
98 98
99 99 pull
100 100
101 101 $ hg -R main phase --public 9520eea781bc
102 102 pre-close-tip:02de42196ebe draft
103 103 postclose-tip:02de42196ebe draft
104 104 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
105 105 $ hg -R other pull -r 24b6387c8c8c
106 106 pulling from $TESTTMP/main (glob)
107 107 searching for changes
108 108 adding changesets
109 109 adding manifests
110 110 adding file changes
111 111 added 1 changesets with 1 changes to 1 files (+1 heads)
112 112 1 new obsolescence markers
113 113 pre-close-tip:24b6387c8c8c draft
114 114 postclose-tip:24b6387c8c8c draft
115 115 txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_NODE_LAST=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
116 116 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
117 117 (run 'hg heads' to see heads, 'hg merge' to merge)
118 118 $ hg -R other log -G
119 119 o 2:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
120 120 |
121 121 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
122 122 |/
123 123 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
124 124
125 125 $ hg -R other debugobsolete
126 126 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
127 127 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
128 128
129 129 pull empty (with phase movement)
130 130
131 131 $ hg -R main phase --public 24b6387c8c8c
132 132 pre-close-tip:02de42196ebe draft
133 133 postclose-tip:02de42196ebe draft
134 134 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
135 135 $ hg -R other pull -r 24b6387c8c8c
136 136 pulling from $TESTTMP/main (glob)
137 137 no changes found
138 138 pre-close-tip:24b6387c8c8c public
139 139 postclose-tip:24b6387c8c8c public
140 140 txnclose hook: HG_NEW_OBSMARKERS=0 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
141 141 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
142 142 $ hg -R other log -G
143 143 o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
144 144 |
145 145 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
146 146 |/
147 147 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
148 148
149 149 $ hg -R other debugobsolete
150 150 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
151 151 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
152 152
153 153 pull empty
154 154
155 155 $ hg -R other pull -r 24b6387c8c8c
156 156 pulling from $TESTTMP/main (glob)
157 157 no changes found
158 158 pre-close-tip:24b6387c8c8c public
159 159 postclose-tip:24b6387c8c8c public
160 160 txnclose hook: HG_NEW_OBSMARKERS=0 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
161 161 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
162 162 $ hg -R other log -G
163 163 o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
164 164 |
165 165 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
166 166 |/
167 167 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
168 168
169 169 $ hg -R other debugobsolete
170 170 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
171 171 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
172 172
173 173 add extra data to test their exchange during push
174 174
175 175 $ hg -R main bookmark --rev eea13746799a book_eea1
176 176 pre-close-tip:02de42196ebe draft
177 177 postclose-tip:02de42196ebe draft
178 178 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
179 179 $ hg -R main debugobsolete -d '0 0' 3333333333333333333333333333333333333333 `getmainid eea13746799a`
180 180 pre-close-tip:02de42196ebe draft
181 181 postclose-tip:02de42196ebe draft
182 182 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
183 183 $ hg -R main bookmark --rev 02de42196ebe book_02de
184 184 pre-close-tip:02de42196ebe draft book_02de
185 185 postclose-tip:02de42196ebe draft book_02de
186 186 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
187 187 $ hg -R main debugobsolete -d '0 0' 4444444444444444444444444444444444444444 `getmainid 02de42196ebe`
188 188 pre-close-tip:02de42196ebe draft book_02de
189 189 postclose-tip:02de42196ebe draft book_02de
190 190 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
191 191 $ hg -R main bookmark --rev 42ccdea3bb16 book_42cc
192 192 pre-close-tip:02de42196ebe draft book_02de
193 193 postclose-tip:02de42196ebe draft book_02de
194 194 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
195 195 $ hg -R main debugobsolete -d '0 0' 5555555555555555555555555555555555555555 `getmainid 42ccdea3bb16`
196 196 pre-close-tip:02de42196ebe draft book_02de
197 197 postclose-tip:02de42196ebe draft book_02de
198 198 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
199 199 $ hg -R main bookmark --rev 5fddd98957c8 book_5fdd
200 200 pre-close-tip:02de42196ebe draft book_02de
201 201 postclose-tip:02de42196ebe draft book_02de
202 202 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
203 203 $ hg -R main debugobsolete -d '0 0' 6666666666666666666666666666666666666666 `getmainid 5fddd98957c8`
204 204 pre-close-tip:02de42196ebe draft book_02de
205 205 postclose-tip:02de42196ebe draft book_02de
206 206 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
207 207 $ hg -R main bookmark --rev 32af7686d403 book_32af
208 208 pre-close-tip:02de42196ebe draft book_02de
209 209 postclose-tip:02de42196ebe draft book_02de
210 210 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
211 211 $ hg -R main debugobsolete -d '0 0' 7777777777777777777777777777777777777777 `getmainid 32af7686d403`
212 212 pre-close-tip:02de42196ebe draft book_02de
213 213 postclose-tip:02de42196ebe draft book_02de
214 214 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
215 215
216 216 $ hg -R other bookmark --rev cd010b8cd998 book_eea1
217 217 pre-close-tip:24b6387c8c8c public
218 218 postclose-tip:24b6387c8c8c public
219 219 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
220 220 $ hg -R other bookmark --rev cd010b8cd998 book_02de
221 221 pre-close-tip:24b6387c8c8c public
222 222 postclose-tip:24b6387c8c8c public
223 223 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
224 224 $ hg -R other bookmark --rev cd010b8cd998 book_42cc
225 225 pre-close-tip:24b6387c8c8c public
226 226 postclose-tip:24b6387c8c8c public
227 227 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
228 228 $ hg -R other bookmark --rev cd010b8cd998 book_5fdd
229 229 pre-close-tip:24b6387c8c8c public
230 230 postclose-tip:24b6387c8c8c public
231 231 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
232 232 $ hg -R other bookmark --rev cd010b8cd998 book_32af
233 233 pre-close-tip:24b6387c8c8c public
234 234 postclose-tip:24b6387c8c8c public
235 235 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
236 236
237 237 $ hg -R main phase --public eea13746799a
238 238 pre-close-tip:02de42196ebe draft book_02de
239 239 postclose-tip:02de42196ebe draft book_02de
240 240 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
241 241
242 242 push
243 243 $ hg -R main push other --rev eea13746799a --bookmark book_eea1
244 244 pushing to other
245 245 searching for changes
246 246 remote: adding changesets
247 247 remote: adding manifests
248 248 remote: adding file changes
249 249 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
250 250 remote: 1 new obsolescence markers
251 251 remote: pre-close-tip:eea13746799a public book_eea1
252 252 remote: pushkey: lock state after "phases"
253 253 remote: lock: free
254 254 remote: wlock: free
255 255 remote: pushkey: lock state after "bookmarks"
256 256 remote: lock: free
257 257 remote: wlock: free
258 258 remote: postclose-tip:eea13746799a public book_eea1
259 259 remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_NODE_LAST=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=file:$TESTTMP/other (glob)
260 260 updating bookmark book_eea1
261 261 pre-close-tip:02de42196ebe draft book_02de
262 262 postclose-tip:02de42196ebe draft book_02de
263 263 txnclose hook: HG_SOURCE=push-response HG_TXNID=TXN:* HG_TXNNAME=push-response (glob)
264 264 file:/*/$TESTTMP/other HG_URL=file:$TESTTMP/other (glob)
265 265 $ hg -R other log -G
266 266 o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
267 267 |\
268 268 | o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
269 269 | |
270 270 @ | 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
271 271 |/
272 272 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de book_32af book_42cc book_5fdd A
273 273
274 274 $ hg -R other debugobsolete
275 275 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
276 276 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
277 277 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
278 278
279 279 pull over ssh
280 280
281 281 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --bookmark book_02de
282 282 pulling from ssh://user@dummy/main
283 283 searching for changes
284 284 adding changesets
285 285 adding manifests
286 286 adding file changes
287 287 added 1 changesets with 1 changes to 1 files (+1 heads)
288 288 1 new obsolescence markers
289 289 updating bookmark book_02de
290 290 pre-close-tip:02de42196ebe draft book_02de
291 291 postclose-tip:02de42196ebe draft book_02de
292 292 txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
293 293 ssh://user@dummy/main HG_URL=ssh://user@dummy/main
294 294 (run 'hg heads' to see heads, 'hg merge' to merge)
295 295 $ hg -R other debugobsolete
296 296 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
297 297 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
298 298 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
299 299 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
300 300
301 301 pull over http
302 302
303 303 $ hg serve -R main -p $HGPORT -d --pid-file=main.pid -E main-error.log
304 304 $ cat main.pid >> $DAEMON_PIDS
305 305
306 306 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16 --bookmark book_42cc
307 307 pulling from http://localhost:$HGPORT/
308 308 searching for changes
309 309 adding changesets
310 310 adding manifests
311 311 adding file changes
312 312 added 1 changesets with 1 changes to 1 files (+1 heads)
313 313 1 new obsolescence markers
314 314 updating bookmark book_42cc
315 315 pre-close-tip:42ccdea3bb16 draft book_42cc
316 316 postclose-tip:42ccdea3bb16 draft book_42cc
317 317 txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_NODE_LAST=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
318 318 http://localhost:$HGPORT/ HG_URL=http://localhost:$HGPORT/
319 319 (run 'hg heads .' to see heads, 'hg merge' to merge)
320 320 $ cat main-error.log
321 321 $ hg -R other debugobsolete
322 322 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
323 323 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
324 324 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
325 325 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
326 326 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
327 327
328 328 push over ssh
329 329
330 330 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8 --bookmark book_5fdd
331 331 pushing to ssh://user@dummy/other
332 332 searching for changes
333 333 remote: adding changesets
334 334 remote: adding manifests
335 335 remote: adding file changes
336 336 remote: added 1 changesets with 1 changes to 1 files
337 337 remote: 1 new obsolescence markers
338 338 remote: pre-close-tip:5fddd98957c8 draft book_5fdd
339 339 remote: pushkey: lock state after "bookmarks"
340 340 remote: lock: free
341 341 remote: wlock: free
342 342 remote: postclose-tip:5fddd98957c8 draft book_5fdd
343 343 remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_NODE_LAST=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_TXNID=TXN:* HG_TXNNAME=serve HG_URL=remote:ssh:127.0.0.1 (glob)
344 344 updating bookmark book_5fdd
345 345 pre-close-tip:02de42196ebe draft book_02de
346 346 postclose-tip:02de42196ebe draft book_02de
347 347 txnclose hook: HG_SOURCE=push-response HG_TXNID=TXN:* HG_TXNNAME=push-response (glob)
348 348 ssh://user@dummy/other HG_URL=ssh://user@dummy/other
349 349 $ hg -R other log -G
350 350 o 6:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
351 351 |
352 352 o 5:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
353 353 |
354 354 | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
355 355 | |
356 356 | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
357 357 | |/|
358 358 | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
359 359 |/ /
360 360 | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
361 361 |/
362 362 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af A
363 363
364 364 $ hg -R other debugobsolete
365 365 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
366 366 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
367 367 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
368 368 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
369 369 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
370 370 6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
371 371
372 372 push over http
373 373
374 374 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
375 375 $ cat other.pid >> $DAEMON_PIDS
376 376
377 377 $ hg -R main phase --public 32af7686d403
378 378 pre-close-tip:02de42196ebe draft book_02de
379 379 postclose-tip:02de42196ebe draft book_02de
380 380 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
381 381 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403 --bookmark book_32af
382 382 pushing to http://localhost:$HGPORT2/
383 383 searching for changes
384 384 remote: adding changesets
385 385 remote: adding manifests
386 386 remote: adding file changes
387 387 remote: added 1 changesets with 1 changes to 1 files
388 388 remote: 1 new obsolescence markers
389 389 remote: pre-close-tip:32af7686d403 public book_32af
390 390 remote: pushkey: lock state after "phases"
391 391 remote: lock: free
392 392 remote: wlock: free
393 393 remote: pushkey: lock state after "bookmarks"
394 394 remote: lock: free
395 395 remote: wlock: free
396 396 remote: postclose-tip:32af7686d403 public book_32af
397 397 remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=32af7686d403cf45b5d95f2d70cebea587ac806a HG_NODE_LAST=32af7686d403cf45b5d95f2d70cebea587ac806a HG_PHASES_MOVED=1 HG_SOURCE=serve HG_TXNID=TXN:* HG_TXNNAME=serve HG_URL=remote:http:127.0.0.1: (glob)
398 398 updating bookmark book_32af
399 399 pre-close-tip:02de42196ebe draft book_02de
400 400 postclose-tip:02de42196ebe draft book_02de
401 401 txnclose hook: HG_SOURCE=push-response HG_TXNID=TXN:* HG_TXNNAME=push-response (glob)
402 402 http://localhost:$HGPORT2/ HG_URL=http://localhost:$HGPORT2/
403 403 $ cat other-error.log
404 404
405 405 Check final content.
406 406
407 407 $ hg -R other log -G
408 408 o 7:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af D
409 409 |
410 410 o 6:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
411 411 |
412 412 o 5:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
413 413 |
414 414 | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
415 415 | |
416 416 | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
417 417 | |/|
418 418 | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
419 419 |/ /
420 420 | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
421 421 |/
422 422 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
423 423
424 424 $ hg -R other debugobsolete
425 425 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
426 426 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
427 427 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
428 428 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
429 429 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
430 430 6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
431 431 7777777777777777777777777777777777777777 32af7686d403cf45b5d95f2d70cebea587ac806a 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
432 432
433 433 (check that no 'pending' files remain)
434 434
435 435 $ ls -1 other/.hg/bookmarks*
436 436 other/.hg/bookmarks
437 437 $ ls -1 other/.hg/store/phaseroots*
438 438 other/.hg/store/phaseroots
439 439 $ ls -1 other/.hg/store/00changelog.i*
440 440 other/.hg/store/00changelog.i
441 441
442 442 Error Handling
443 443 ==============
444 444
445 445 Check that errors are properly returned to the client during push.
446 446
447 447 Setting up
448 448
449 449 $ cat > failpush.py << EOF
450 450 > """A small extension that makes push fails when using bundle2
451 451 >
452 452 > used to test error handling in bundle2
453 453 > """
454 454 >
455 455 > from mercurial import error
456 456 > from mercurial import bundle2
457 457 > from mercurial import exchange
458 458 > from mercurial import extensions
459 459 >
460 460 > def _pushbundle2failpart(pushop, bundler):
461 461 > reason = pushop.ui.config('failpush', 'reason', None)
462 462 > part = None
463 463 > if reason == 'abort':
464 464 > bundler.newpart('test:abort')
465 465 > if reason == 'unknown':
466 466 > bundler.newpart('test:unknown')
467 467 > if reason == 'race':
468 468 > # 20 Bytes of crap
469 469 > bundler.newpart('check:heads', data='01234567890123456789')
470 470 >
471 471 > @bundle2.parthandler("test:abort")
472 472 > def handleabort(op, part):
473 473 > raise error.Abort('Abandon ship!', hint="don't panic")
474 474 >
475 475 > def uisetup(ui):
476 476 > exchange.b2partsgenmapping['failpart'] = _pushbundle2failpart
477 477 > exchange.b2partsgenorder.insert(0, 'failpart')
478 478 >
479 479 > EOF
480 480
481 481 $ cd main
482 482 $ hg up tip
483 483 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
484 484 $ echo 'I' > I
485 485 $ hg add I
486 486 $ hg ci -m 'I'
487 487 pre-close-tip:e7ec4e813ba6 draft
488 488 postclose-tip:e7ec4e813ba6 draft
489 489 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
490 490 $ hg id
491 491 e7ec4e813ba6 tip
492 492 $ cd ..
493 493
494 494 $ cat << EOF >> $HGRCPATH
495 495 > [extensions]
496 496 > failpush=$TESTTMP/failpush.py
497 497 > EOF
498 498
499 499 $ killdaemons.py
500 500 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
501 501 $ cat other.pid >> $DAEMON_PIDS
502 502
503 503 Doing the actual push: Abort error
504 504
505 505 $ cat << EOF >> $HGRCPATH
506 506 > [failpush]
507 507 > reason = abort
508 508 > EOF
509 509
510 510 $ hg -R main push other -r e7ec4e813ba6
511 511 pushing to other
512 512 searching for changes
513 513 abort: Abandon ship!
514 514 (don't panic)
515 515 [255]
516 516
517 517 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
518 518 pushing to ssh://user@dummy/other
519 519 searching for changes
520 520 remote: Abandon ship!
521 521 remote: (don't panic)
522 522 abort: push failed on remote
523 523 [255]
524 524
525 525 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
526 526 pushing to http://localhost:$HGPORT2/
527 527 searching for changes
528 528 remote: Abandon ship!
529 529 remote: (don't panic)
530 530 abort: push failed on remote
531 531 [255]
532 532
533 533
534 534 Doing the actual push: unknown mandatory parts
535 535
536 536 $ cat << EOF >> $HGRCPATH
537 537 > [failpush]
538 538 > reason = unknown
539 539 > EOF
540 540
541 541 $ hg -R main push other -r e7ec4e813ba6
542 542 pushing to other
543 543 searching for changes
544 544 abort: missing support for test:unknown
545 545 [255]
546 546
547 547 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
548 548 pushing to ssh://user@dummy/other
549 549 searching for changes
550 550 abort: missing support for test:unknown
551 551 [255]
552 552
553 553 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
554 554 pushing to http://localhost:$HGPORT2/
555 555 searching for changes
556 556 abort: missing support for test:unknown
557 557 [255]
558 558
559 559 Doing the actual push: race
560 560
561 561 $ cat << EOF >> $HGRCPATH
562 562 > [failpush]
563 563 > reason = race
564 564 > EOF
565 565
566 566 $ hg -R main push other -r e7ec4e813ba6
567 567 pushing to other
568 568 searching for changes
569 569 abort: push failed:
570 570 'repository changed while pushing - please try again'
571 571 [255]
572 572
573 573 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
574 574 pushing to ssh://user@dummy/other
575 575 searching for changes
576 576 abort: push failed:
577 577 'repository changed while pushing - please try again'
578 578 [255]
579 579
580 580 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
581 581 pushing to http://localhost:$HGPORT2/
582 582 searching for changes
583 583 abort: push failed:
584 584 'repository changed while pushing - please try again'
585 585 [255]
586 586
587 587 Doing the actual push: hook abort
588 588
589 589 $ cat << EOF >> $HGRCPATH
590 590 > [failpush]
591 591 > reason =
592 592 > [hooks]
593 593 > pretxnclose.failpush = sh -c "echo 'You shall not pass!'; false"
594 594 > txnabort.failpush = sh -c "echo 'Cleaning up the mess...'"
595 595 > EOF
596 596
597 597 $ killdaemons.py
598 598 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
599 599 $ cat other.pid >> $DAEMON_PIDS
600 600
601 601 $ hg -R main push other -r e7ec4e813ba6
602 602 pushing to other
603 603 searching for changes
604 604 remote: adding changesets
605 605 remote: adding manifests
606 606 remote: adding file changes
607 607 remote: added 1 changesets with 1 changes to 1 files
608 608 remote: pre-close-tip:e7ec4e813ba6 draft
609 609 remote: You shall not pass!
610 610 remote: transaction abort!
611 611 remote: Cleaning up the mess...
612 612 remote: rollback completed
613 613 abort: pretxnclose.failpush hook exited with status 1
614 614 [255]
615 615
616 616 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
617 617 pushing to ssh://user@dummy/other
618 618 searching for changes
619 619 remote: adding changesets
620 620 remote: adding manifests
621 621 remote: adding file changes
622 622 remote: added 1 changesets with 1 changes to 1 files
623 623 remote: pre-close-tip:e7ec4e813ba6 draft
624 624 remote: You shall not pass!
625 625 remote: transaction abort!
626 626 remote: Cleaning up the mess...
627 627 remote: rollback completed
628 628 remote: pretxnclose.failpush hook exited with status 1
629 629 abort: push failed on remote
630 630 [255]
631 631
632 632 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
633 633 pushing to http://localhost:$HGPORT2/
634 634 searching for changes
635 635 remote: adding changesets
636 636 remote: adding manifests
637 637 remote: adding file changes
638 638 remote: added 1 changesets with 1 changes to 1 files
639 639 remote: pre-close-tip:e7ec4e813ba6 draft
640 640 remote: You shall not pass!
641 641 remote: transaction abort!
642 642 remote: Cleaning up the mess...
643 643 remote: rollback completed
644 644 remote: pretxnclose.failpush hook exited with status 1
645 645 abort: push failed on remote
646 646 [255]
647 647
648 648 (check that no 'pending' files remain)
649 649
650 650 $ ls -1 other/.hg/bookmarks*
651 651 other/.hg/bookmarks
652 652 $ ls -1 other/.hg/store/phaseroots*
653 653 other/.hg/store/phaseroots
654 654 $ ls -1 other/.hg/store/00changelog.i*
655 655 other/.hg/store/00changelog.i
656 656
657 657 Check error from hook during the unbundling process itself
658 658
659 659 $ cat << EOF >> $HGRCPATH
660 660 > pretxnchangegroup = sh -c "echo 'Fail early!'; false"
661 661 > EOF
662 662 $ killdaemons.py # reload http config
663 663 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
664 664 $ cat other.pid >> $DAEMON_PIDS
665 665
666 666 $ hg -R main push other -r e7ec4e813ba6
667 667 pushing to other
668 668 searching for changes
669 669 remote: adding changesets
670 670 remote: adding manifests
671 671 remote: adding file changes
672 672 remote: added 1 changesets with 1 changes to 1 files
673 673 remote: Fail early!
674 674 remote: transaction abort!
675 675 remote: Cleaning up the mess...
676 676 remote: rollback completed
677 677 abort: pretxnchangegroup hook exited with status 1
678 678 [255]
679 679 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
680 680 pushing to ssh://user@dummy/other
681 681 searching for changes
682 682 remote: adding changesets
683 683 remote: adding manifests
684 684 remote: adding file changes
685 685 remote: added 1 changesets with 1 changes to 1 files
686 686 remote: Fail early!
687 687 remote: transaction abort!
688 688 remote: Cleaning up the mess...
689 689 remote: rollback completed
690 690 remote: pretxnchangegroup hook exited with status 1
691 691 abort: push failed on remote
692 692 [255]
693 693 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
694 694 pushing to http://localhost:$HGPORT2/
695 695 searching for changes
696 696 remote: adding changesets
697 697 remote: adding manifests
698 698 remote: adding file changes
699 699 remote: added 1 changesets with 1 changes to 1 files
700 700 remote: Fail early!
701 701 remote: transaction abort!
702 702 remote: Cleaning up the mess...
703 703 remote: rollback completed
704 704 remote: pretxnchangegroup hook exited with status 1
705 705 abort: push failed on remote
706 706 [255]
707 707
708 708 Check output capture control.
709 709
710 710 (should be still forced for http, disabled for local and ssh)
711 711
712 712 $ cat >> $HGRCPATH << EOF
713 713 > [experimental]
714 714 > bundle2-output-capture=False
715 715 > EOF
716 716
717 717 $ hg -R main push other -r e7ec4e813ba6
718 718 pushing to other
719 719 searching for changes
720 720 adding changesets
721 721 adding manifests
722 722 adding file changes
723 723 added 1 changesets with 1 changes to 1 files
724 724 Fail early!
725 725 transaction abort!
726 726 Cleaning up the mess...
727 727 rollback completed
728 728 abort: pretxnchangegroup hook exited with status 1
729 729 [255]
730 730 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
731 731 pushing to ssh://user@dummy/other
732 732 searching for changes
733 733 remote: adding changesets
734 734 remote: adding manifests
735 735 remote: adding file changes
736 736 remote: added 1 changesets with 1 changes to 1 files
737 737 remote: Fail early!
738 738 remote: transaction abort!
739 739 remote: Cleaning up the mess...
740 740 remote: rollback completed
741 741 remote: pretxnchangegroup hook exited with status 1
742 742 abort: push failed on remote
743 743 [255]
744 744 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
745 745 pushing to http://localhost:$HGPORT2/
746 746 searching for changes
747 747 remote: adding changesets
748 748 remote: adding manifests
749 749 remote: adding file changes
750 750 remote: added 1 changesets with 1 changes to 1 files
751 751 remote: Fail early!
752 752 remote: transaction abort!
753 753 remote: Cleaning up the mess...
754 754 remote: rollback completed
755 755 remote: pretxnchangegroup hook exited with status 1
756 756 abort: push failed on remote
757 757 [255]
758 758
759 759 Check abort from mandatory pushkey
760 760
761 761 $ cat > mandatorypart.py << EOF
762 762 > from mercurial import exchange
763 763 > from mercurial import pushkey
764 764 > from mercurial import node
765 765 > from mercurial import error
766 766 > @exchange.b2partsgenerator('failingpuskey')
767 767 > def addfailingpushey(pushop, bundler):
768 768 > enc = pushkey.encode
769 769 > part = bundler.newpart('pushkey')
770 770 > part.addparam('namespace', enc('phases'))
771 771 > part.addparam('key', enc(pushop.repo['cd010b8cd998'].hex()))
772 772 > part.addparam('old', enc(str(0))) # successful update
773 773 > part.addparam('new', enc(str(0)))
774 774 > def fail(pushop, exc):
775 775 > raise error.Abort('Correct phase push failed (because hooks)')
776 776 > pushop.pkfailcb[part.id] = fail
777 777 > EOF
778 778 $ cat >> $HGRCPATH << EOF
779 779 > [hooks]
780 780 > pretxnchangegroup=
781 781 > pretxnclose.failpush=
782 782 > prepushkey.failpush = sh -c "echo 'do not push the key !'; false"
783 783 > [extensions]
784 784 > mandatorypart=$TESTTMP/mandatorypart.py
785 785 > EOF
786 786 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS # reload http config
787 787 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
788 788 $ cat other.pid >> $DAEMON_PIDS
789 789
790 790 (Failure from a hook)
791 791
792 792 $ hg -R main push other -r e7ec4e813ba6
793 793 pushing to other
794 794 searching for changes
795 795 adding changesets
796 796 adding manifests
797 797 adding file changes
798 798 added 1 changesets with 1 changes to 1 files
799 799 do not push the key !
800 800 pushkey-abort: prepushkey.failpush hook exited with status 1
801 801 transaction abort!
802 802 Cleaning up the mess...
803 803 rollback completed
804 804 abort: Correct phase push failed (because hooks)
805 805 [255]
806 806 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
807 807 pushing to ssh://user@dummy/other
808 808 searching for changes
809 809 remote: adding changesets
810 810 remote: adding manifests
811 811 remote: adding file changes
812 812 remote: added 1 changesets with 1 changes to 1 files
813 813 remote: do not push the key !
814 814 remote: pushkey-abort: prepushkey.failpush hook exited with status 1
815 815 remote: transaction abort!
816 816 remote: Cleaning up the mess...
817 817 remote: rollback completed
818 818 abort: Correct phase push failed (because hooks)
819 819 [255]
820 820 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
821 821 pushing to http://localhost:$HGPORT2/
822 822 searching for changes
823 823 remote: adding changesets
824 824 remote: adding manifests
825 825 remote: adding file changes
826 826 remote: added 1 changesets with 1 changes to 1 files
827 827 remote: do not push the key !
828 828 remote: pushkey-abort: prepushkey.failpush hook exited with status 1
829 829 remote: transaction abort!
830 830 remote: Cleaning up the mess...
831 831 remote: rollback completed
832 832 abort: Correct phase push failed (because hooks)
833 833 [255]
834 834
835 835 (Failure from a the pushkey)
836 836
837 837 $ cat > mandatorypart.py << EOF
838 838 > from mercurial import exchange
839 839 > from mercurial import pushkey
840 840 > from mercurial import node
841 841 > from mercurial import error
842 842 > @exchange.b2partsgenerator('failingpuskey')
843 843 > def addfailingpushey(pushop, bundler):
844 844 > enc = pushkey.encode
845 845 > part = bundler.newpart('pushkey')
846 846 > part.addparam('namespace', enc('phases'))
847 847 > part.addparam('key', enc(pushop.repo['cd010b8cd998'].hex()))
848 848 > part.addparam('old', enc(str(4))) # will fail
849 849 > part.addparam('new', enc(str(3)))
850 850 > def fail(pushop, exc):
851 851 > raise error.Abort('Clown phase push failed')
852 852 > pushop.pkfailcb[part.id] = fail
853 853 > EOF
854 854 $ cat >> $HGRCPATH << EOF
855 855 > [hooks]
856 856 > prepushkey.failpush =
857 857 > EOF
858 858 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS # reload http config
859 859 $ hg serve -R other -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
860 860 $ cat other.pid >> $DAEMON_PIDS
861 861
862 862 $ hg -R main push other -r e7ec4e813ba6
863 863 pushing to other
864 864 searching for changes
865 865 adding changesets
866 866 adding manifests
867 867 adding file changes
868 868 added 1 changesets with 1 changes to 1 files
869 869 transaction abort!
870 870 Cleaning up the mess...
871 871 rollback completed
872 872 pushkey: lock state after "phases"
873 873 lock: free
874 874 wlock: free
875 875 abort: Clown phase push failed
876 876 [255]
877 877 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
878 878 pushing to ssh://user@dummy/other
879 879 searching for changes
880 880 remote: adding changesets
881 881 remote: adding manifests
882 882 remote: adding file changes
883 883 remote: added 1 changesets with 1 changes to 1 files
884 884 remote: transaction abort!
885 885 remote: Cleaning up the mess...
886 886 remote: rollback completed
887 887 remote: pushkey: lock state after "phases"
888 888 remote: lock: free
889 889 remote: wlock: free
890 890 abort: Clown phase push failed
891 891 [255]
892 892 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
893 893 pushing to http://localhost:$HGPORT2/
894 894 searching for changes
895 895 remote: adding changesets
896 896 remote: adding manifests
897 897 remote: adding file changes
898 898 remote: added 1 changesets with 1 changes to 1 files
899 899 remote: transaction abort!
900 900 remote: Cleaning up the mess...
901 901 remote: rollback completed
902 902 remote: pushkey: lock state after "phases"
903 903 remote: lock: free
904 904 remote: wlock: free
905 905 abort: Clown phase push failed
906 906 [255]
907 907
908 908 Test lazily acquiring the lock during unbundle
909 909 $ cp $TESTTMP/hgrc.orig $HGRCPATH
910 910 $ cat >> $HGRCPATH <<EOF
911 911 > [ui]
912 912 > ssh=python "$TESTDIR/dummyssh"
913 913 > EOF
914 914
915 915 $ cat >> $TESTTMP/locktester.py <<EOF
916 916 > import os
917 917 > from mercurial import extensions, bundle2, util
918 918 > def checklock(orig, repo, *args, **kwargs):
919 919 > if repo.svfs.lexists("lock"):
920 920 > raise util.Abort("Lock should not be taken")
921 921 > return orig(repo, *args, **kwargs)
922 922 > def extsetup(ui):
923 923 > extensions.wrapfunction(bundle2, 'processbundle', checklock)
924 924 > EOF
925 925
926 926 $ hg init lazylock
927 927 $ cat >> lazylock/.hg/hgrc <<EOF
928 928 > [extensions]
929 929 > locktester=$TESTTMP/locktester.py
930 930 > EOF
931 931
932 932 $ hg clone -q ssh://user@dummy/lazylock lazylockclient
933 933 $ cd lazylockclient
934 934 $ touch a && hg ci -Aqm a
935 935 $ hg push
936 936 pushing to ssh://user@dummy/lazylock
937 937 searching for changes
938 938 remote: Lock should not be taken
939 939 abort: push failed on remote
940 940 [255]
941 941
942 942 $ cat >> ../lazylock/.hg/hgrc <<EOF
943 943 > [experimental]
944 944 > bundle2lazylocking=True
945 945 > EOF
946 946 $ hg push
947 947 pushing to ssh://user@dummy/lazylock
948 948 searching for changes
949 949 remote: adding changesets
950 950 remote: adding manifests
951 951 remote: adding file changes
952 952 remote: added 1 changesets with 1 changes to 1 files
953 953
954 954 $ cd ..
955 955
956 956 Servers can disable bundle1 for clone/pull operations
957 957
958 958 $ killdaemons.py
959 959 $ hg init bundle2onlyserver
960 960 $ cd bundle2onlyserver
961 961 $ cat > .hg/hgrc << EOF
962 962 > [server]
963 963 > bundle1.pull = false
964 964 > EOF
965 965
966 966 $ touch foo
967 967 $ hg -q commit -A -m initial
968 968
969 969 $ hg serve -p $HGPORT -d --pid-file=hg.pid
970 970 $ cat hg.pid >> $DAEMON_PIDS
971 971
972 972 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2
973 973 requesting all changes
974 974 abort: remote error:
975 975 incompatible Mercurial client; bundle2 required
976 976 (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
977 977 [255]
978 978 $ killdaemons.py
979 979 $ cd ..
980 980
981 981 bundle1 can still pull non-generaldelta repos when generaldelta bundle1 disabled
982 982
983 983 $ hg --config format.usegeneraldelta=false init notgdserver
984 984 $ cd notgdserver
985 985 $ cat > .hg/hgrc << EOF
986 986 > [server]
987 987 > bundle1gd.pull = false
988 988 > EOF
989 989
990 990 $ touch foo
991 991 $ hg -q commit -A -m initial
992 992 $ hg serve -p $HGPORT -d --pid-file=hg.pid
993 993 $ cat hg.pid >> $DAEMON_PIDS
994 994
995 995 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2-1
996 996 requesting all changes
997 997 adding changesets
998 998 adding manifests
999 999 adding file changes
1000 1000 added 1 changesets with 1 changes to 1 files
1001 1001 updating to branch default
1002 1002 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1003 1003
1004 1004 $ killdaemons.py
1005 1005 $ cd ../bundle2onlyserver
1006 1006
1007 1007 bundle1 pull can be disabled for generaldelta repos only
1008 1008
1009 1009 $ cat > .hg/hgrc << EOF
1010 1010 > [server]
1011 1011 > bundle1gd.pull = false
1012 1012 > EOF
1013 1013
1014 1014 $ hg serve -p $HGPORT -d --pid-file=hg.pid
1015 1015 $ cat hg.pid >> $DAEMON_PIDS
1016 1016 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2
1017 1017 requesting all changes
1018 1018 abort: remote error:
1019 1019 incompatible Mercurial client; bundle2 required
1020 1020 (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1021 1021 [255]
1022 1022
1023 1023 $ killdaemons.py
1024 1024
1025 1025 Verify the global server.bundle1 option works
1026 1026
1027 1027 $ cd ..
1028 1028 $ cat > bundle2onlyserver/.hg/hgrc << EOF
1029 1029 > [server]
1030 1030 > bundle1 = false
1031 1031 > EOF
1032 1032 $ hg -R bundle2onlyserver serve -p $HGPORT -d --pid-file=hg.pid
1033 1033 $ cat hg.pid >> $DAEMON_PIDS
1034 1034 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT not-bundle2
1035 1035 requesting all changes
1036 1036 abort: remote error:
1037 1037 incompatible Mercurial client; bundle2 required
1038 1038 (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1039 1039 [255]
1040 1040 $ killdaemons.py
1041 1041
1042 $ hg --config devel.legacy.exchange=bundle1 clone ssh://user@dummy/bundle2onlyserver not-bundle2-ssh
1043 requesting all changes
1044 adding changesets
1045 remote: abort: incompatible Mercurial client; bundle2 required
1046 remote: (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1047 transaction abort!
1048 rollback completed
1049 abort: stream ended unexpectedly (got 0 bytes, expected 4)
1050 [255]
1051
1042 1052 $ cat > bundle2onlyserver/.hg/hgrc << EOF
1043 1053 > [server]
1044 1054 > bundle1gd = false
1045 1055 > EOF
1046 1056 $ hg -R bundle2onlyserver serve -p $HGPORT -d --pid-file=hg.pid
1047 1057 $ cat hg.pid >> $DAEMON_PIDS
1048 1058
1049 1059 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2
1050 1060 requesting all changes
1051 1061 abort: remote error:
1052 1062 incompatible Mercurial client; bundle2 required
1053 1063 (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1054 1064 [255]
1055 1065
1056 1066 $ killdaemons.py
1057 1067
1058 1068 $ cd notgdserver
1059 1069 $ cat > .hg/hgrc << EOF
1060 1070 > [server]
1061 1071 > bundle1gd = false
1062 1072 > EOF
1063 1073 $ hg serve -p $HGPORT -d --pid-file=hg.pid
1064 1074 $ cat hg.pid >> $DAEMON_PIDS
1065 1075
1066 1076 $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2-2
1067 1077 requesting all changes
1068 1078 adding changesets
1069 1079 adding manifests
1070 1080 adding file changes
1071 1081 added 1 changesets with 1 changes to 1 files
1072 1082 updating to branch default
1073 1083 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1074 1084
1075 1085 $ killdaemons.py
1076 1086 $ cd ../bundle2onlyserver
1077 1087
1078 1088 Verify bundle1 pushes can be disabled
1079 1089
1080 1090 $ cat > .hg/hgrc << EOF
1081 1091 > [server]
1082 1092 > bundle1.push = false
1083 1093 > [web]
1084 1094 > allow_push = *
1085 1095 > push_ssl = false
1086 1096 > EOF
1087 1097
1088 1098 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E error.log
1089 1099 $ cat hg.pid >> $DAEMON_PIDS
1090 1100 $ cd ..
1091 1101
1092 1102 $ hg clone http://localhost:$HGPORT bundle2-only
1093 1103 requesting all changes
1094 1104 adding changesets
1095 1105 adding manifests
1096 1106 adding file changes
1097 1107 added 1 changesets with 1 changes to 1 files
1098 1108 updating to branch default
1099 1109 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1100 1110 $ cd bundle2-only
1101 1111 $ echo commit > foo
1102 1112 $ hg commit -m commit
1103 1113 $ hg --config devel.legacy.exchange=bundle1 push
1104 1114 pushing to http://localhost:$HGPORT/
1105 1115 searching for changes
1106 1116 abort: remote error:
1107 1117 incompatible Mercurial client; bundle2 required
1108 1118 (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1109 1119 [255]
1110 1120
1111 1121 (also check with ssh)
1112 1122
1113 1123 $ hg --config devel.legacy.exchange=bundle1 push ssh://user@dummy/bundle2onlyserver
1114 1124 pushing to ssh://user@dummy/bundle2onlyserver
1115 1125 searching for changes
1116 1126 remote: abort: incompatible Mercurial client; bundle2 required
1117 1127 remote: (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
1118 1128 [1]
1119 1129
1120 1130 $ hg push
1121 1131 pushing to http://localhost:$HGPORT/
1122 1132 searching for changes
1123 1133 remote: adding changesets
1124 1134 remote: adding manifests
1125 1135 remote: adding file changes
1126 1136 remote: added 1 changesets with 1 changes to 1 files
General Comments 0
You need to be logged in to leave comments. Login now