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