##// END OF EJS Templates
peer-request: include more details about batch commands...
Boris Feld -
r36963:4901d1e2 default
parent child Browse files
Show More
@@ -1,1120 +1,1127 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 os
12 12 import tempfile
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 bin,
17 17 hex,
18 18 nullid,
19 19 )
20 20
21 21 from . import (
22 22 bundle2,
23 23 changegroup as changegroupmod,
24 24 discovery,
25 25 encoding,
26 26 error,
27 27 exchange,
28 28 peer,
29 29 pushkey as pushkeymod,
30 30 pycompat,
31 31 repository,
32 32 streamclone,
33 33 util,
34 34 wireprototypes,
35 35 )
36 36
37 37 urlerr = util.urlerr
38 38 urlreq = util.urlreq
39 39
40 40 bytesresponse = wireprototypes.bytesresponse
41 41 ooberror = wireprototypes.ooberror
42 42 pushres = wireprototypes.pushres
43 43 pusherr = wireprototypes.pusherr
44 44 streamres = wireprototypes.streamres
45 45 streamres_legacy = wireprototypes.streamreslegacy
46 46
47 47 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
48 48 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
49 49 'IncompatibleClient')
50 50 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
51 51
52 52 class remoteiterbatcher(peer.iterbatcher):
53 53 def __init__(self, remote):
54 54 super(remoteiterbatcher, self).__init__()
55 55 self._remote = remote
56 56
57 57 def __getattr__(self, name):
58 58 # Validate this method is batchable, since submit() only supports
59 59 # batchable methods.
60 60 fn = getattr(self._remote, name)
61 61 if not getattr(fn, 'batchable', None):
62 62 raise error.ProgrammingError('Attempted to batch a non-batchable '
63 63 'call to %r' % name)
64 64
65 65 return super(remoteiterbatcher, self).__getattr__(name)
66 66
67 67 def submit(self):
68 68 """Break the batch request into many patch calls and pipeline them.
69 69
70 70 This is mostly valuable over http where request sizes can be
71 71 limited, but can be used in other places as well.
72 72 """
73 73 # 2-tuple of (command, arguments) that represents what will be
74 74 # sent over the wire.
75 75 requests = []
76 76
77 77 # 4-tuple of (command, final future, @batchable generator, remote
78 78 # future).
79 79 results = []
80 80
81 81 for command, args, opts, finalfuture in self.calls:
82 82 mtd = getattr(self._remote, command)
83 83 batchable = mtd.batchable(mtd.__self__, *args, **opts)
84 84
85 85 commandargs, fremote = next(batchable)
86 86 assert fremote
87 87 requests.append((command, commandargs))
88 88 results.append((command, finalfuture, batchable, fremote))
89 89
90 90 if requests:
91 91 self._resultiter = self._remote._submitbatch(requests)
92 92
93 93 self._results = results
94 94
95 95 def results(self):
96 96 for command, finalfuture, batchable, remotefuture in self._results:
97 97 # Get the raw result, set it in the remote future, feed it
98 98 # back into the @batchable generator so it can be decoded, and
99 99 # set the result on the final future to this value.
100 100 remoteresult = next(self._resultiter)
101 101 remotefuture.set(remoteresult)
102 102 finalfuture.set(next(batchable))
103 103
104 104 # Verify our @batchable generators only emit 2 values.
105 105 try:
106 106 next(batchable)
107 107 except StopIteration:
108 108 pass
109 109 else:
110 110 raise error.ProgrammingError('%s @batchable generator emitted '
111 111 'unexpected value count' % command)
112 112
113 113 yield finalfuture.value
114 114
115 115 # Forward a couple of names from peer to make wireproto interactions
116 116 # slightly more sensible.
117 117 batchable = peer.batchable
118 118 future = peer.future
119 119
120 120 # list of nodes encoding / decoding
121 121
122 122 def decodelist(l, sep=' '):
123 123 if l:
124 124 return [bin(v) for v in l.split(sep)]
125 125 return []
126 126
127 127 def encodelist(l, sep=' '):
128 128 try:
129 129 return sep.join(map(hex, l))
130 130 except TypeError:
131 131 raise
132 132
133 133 # batched call argument encoding
134 134
135 135 def escapearg(plain):
136 136 return (plain
137 137 .replace(':', ':c')
138 138 .replace(',', ':o')
139 139 .replace(';', ':s')
140 140 .replace('=', ':e'))
141 141
142 142 def unescapearg(escaped):
143 143 return (escaped
144 144 .replace(':e', '=')
145 145 .replace(':s', ';')
146 146 .replace(':o', ',')
147 147 .replace(':c', ':'))
148 148
149 149 def encodebatchcmds(req):
150 150 """Return a ``cmds`` argument value for the ``batch`` command."""
151 151 cmds = []
152 152 for op, argsdict in req:
153 153 # Old servers didn't properly unescape argument names. So prevent
154 154 # the sending of argument names that may not be decoded properly by
155 155 # servers.
156 156 assert all(escapearg(k) == k for k in argsdict)
157 157
158 158 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
159 159 for k, v in argsdict.iteritems())
160 160 cmds.append('%s %s' % (op, args))
161 161
162 162 return ';'.join(cmds)
163 163
164 164 # mapping of options accepted by getbundle and their types
165 165 #
166 166 # Meant to be extended by extensions. It is extensions responsibility to ensure
167 167 # such options are properly processed in exchange.getbundle.
168 168 #
169 169 # supported types are:
170 170 #
171 171 # :nodes: list of binary nodes
172 172 # :csv: list of comma-separated values
173 173 # :scsv: list of comma-separated values return as set
174 174 # :plain: string with no transformation needed.
175 175 gboptsmap = {'heads': 'nodes',
176 176 'bookmarks': 'boolean',
177 177 'common': 'nodes',
178 178 'obsmarkers': 'boolean',
179 179 'phases': 'boolean',
180 180 'bundlecaps': 'scsv',
181 181 'listkeys': 'csv',
182 182 'cg': 'boolean',
183 183 'cbattempted': 'boolean',
184 184 'stream': 'boolean',
185 185 }
186 186
187 187 # client side
188 188
189 189 class wirepeer(repository.legacypeer):
190 190 """Client-side interface for communicating with a peer repository.
191 191
192 192 Methods commonly call wire protocol commands of the same name.
193 193
194 194 See also httppeer.py and sshpeer.py for protocol-specific
195 195 implementations of this interface.
196 196 """
197 197 # Begin of basewirepeer interface.
198 198
199 199 def iterbatch(self):
200 200 return remoteiterbatcher(self)
201 201
202 202 @batchable
203 203 def lookup(self, key):
204 204 self.requirecap('lookup', _('look up remote revision'))
205 205 f = future()
206 206 yield {'key': encoding.fromlocal(key)}, f
207 207 d = f.value
208 208 success, data = d[:-1].split(" ", 1)
209 209 if int(success):
210 210 yield bin(data)
211 211 else:
212 212 self._abort(error.RepoError(data))
213 213
214 214 @batchable
215 215 def heads(self):
216 216 f = future()
217 217 yield {}, f
218 218 d = f.value
219 219 try:
220 220 yield decodelist(d[:-1])
221 221 except ValueError:
222 222 self._abort(error.ResponseError(_("unexpected response:"), d))
223 223
224 224 @batchable
225 225 def known(self, nodes):
226 226 f = future()
227 227 yield {'nodes': encodelist(nodes)}, f
228 228 d = f.value
229 229 try:
230 230 yield [bool(int(b)) for b in d]
231 231 except ValueError:
232 232 self._abort(error.ResponseError(_("unexpected response:"), d))
233 233
234 234 @batchable
235 235 def branchmap(self):
236 236 f = future()
237 237 yield {}, f
238 238 d = f.value
239 239 try:
240 240 branchmap = {}
241 241 for branchpart in d.splitlines():
242 242 branchname, branchheads = branchpart.split(' ', 1)
243 243 branchname = encoding.tolocal(urlreq.unquote(branchname))
244 244 branchheads = decodelist(branchheads)
245 245 branchmap[branchname] = branchheads
246 246 yield branchmap
247 247 except TypeError:
248 248 self._abort(error.ResponseError(_("unexpected response:"), d))
249 249
250 250 @batchable
251 251 def listkeys(self, namespace):
252 252 if not self.capable('pushkey'):
253 253 yield {}, None
254 254 f = future()
255 255 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
256 256 yield {'namespace': encoding.fromlocal(namespace)}, f
257 257 d = f.value
258 258 self.ui.debug('received listkey for "%s": %i bytes\n'
259 259 % (namespace, len(d)))
260 260 yield pushkeymod.decodekeys(d)
261 261
262 262 @batchable
263 263 def pushkey(self, namespace, key, old, new):
264 264 if not self.capable('pushkey'):
265 265 yield False, None
266 266 f = future()
267 267 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
268 268 yield {'namespace': encoding.fromlocal(namespace),
269 269 'key': encoding.fromlocal(key),
270 270 'old': encoding.fromlocal(old),
271 271 'new': encoding.fromlocal(new)}, f
272 272 d = f.value
273 273 d, output = d.split('\n', 1)
274 274 try:
275 275 d = bool(int(d))
276 276 except ValueError:
277 277 raise error.ResponseError(
278 278 _('push failed (unexpected response):'), d)
279 279 for l in output.splitlines(True):
280 280 self.ui.status(_('remote: '), l)
281 281 yield d
282 282
283 283 def stream_out(self):
284 284 return self._callstream('stream_out')
285 285
286 286 def getbundle(self, source, **kwargs):
287 287 kwargs = pycompat.byteskwargs(kwargs)
288 288 self.requirecap('getbundle', _('look up remote changes'))
289 289 opts = {}
290 290 bundlecaps = kwargs.get('bundlecaps')
291 291 if bundlecaps is not None:
292 292 kwargs['bundlecaps'] = sorted(bundlecaps)
293 293 else:
294 294 bundlecaps = () # kwargs could have it to None
295 295 for key, value in kwargs.iteritems():
296 296 if value is None:
297 297 continue
298 298 keytype = gboptsmap.get(key)
299 299 if keytype is None:
300 300 raise error.ProgrammingError(
301 301 'Unexpectedly None keytype for key %s' % key)
302 302 elif keytype == 'nodes':
303 303 value = encodelist(value)
304 304 elif keytype in ('csv', 'scsv'):
305 305 value = ','.join(value)
306 306 elif keytype == 'boolean':
307 307 value = '%i' % bool(value)
308 308 elif keytype != 'plain':
309 309 raise KeyError('unknown getbundle option type %s'
310 310 % keytype)
311 311 opts[key] = value
312 312 f = self._callcompressable("getbundle", **pycompat.strkwargs(opts))
313 313 if any((cap.startswith('HG2') for cap in bundlecaps)):
314 314 return bundle2.getunbundler(self.ui, f)
315 315 else:
316 316 return changegroupmod.cg1unpacker(f, 'UN')
317 317
318 318 def unbundle(self, cg, heads, url):
319 319 '''Send cg (a readable file-like object representing the
320 320 changegroup to push, typically a chunkbuffer object) to the
321 321 remote server as a bundle.
322 322
323 323 When pushing a bundle10 stream, return an integer indicating the
324 324 result of the push (see changegroup.apply()).
325 325
326 326 When pushing a bundle20 stream, return a bundle20 stream.
327 327
328 328 `url` is the url the client thinks it's pushing to, which is
329 329 visible to hooks.
330 330 '''
331 331
332 332 if heads != ['force'] and self.capable('unbundlehash'):
333 333 heads = encodelist(['hashed',
334 334 hashlib.sha1(''.join(sorted(heads))).digest()])
335 335 else:
336 336 heads = encodelist(heads)
337 337
338 338 if util.safehasattr(cg, 'deltaheader'):
339 339 # this a bundle10, do the old style call sequence
340 340 ret, output = self._callpush("unbundle", cg, heads=heads)
341 341 if ret == "":
342 342 raise error.ResponseError(
343 343 _('push failed:'), output)
344 344 try:
345 345 ret = int(ret)
346 346 except ValueError:
347 347 raise error.ResponseError(
348 348 _('push failed (unexpected response):'), ret)
349 349
350 350 for l in output.splitlines(True):
351 351 self.ui.status(_('remote: '), l)
352 352 else:
353 353 # bundle2 push. Send a stream, fetch a stream.
354 354 stream = self._calltwowaystream('unbundle', cg, heads=heads)
355 355 ret = bundle2.getunbundler(self.ui, stream)
356 356 return ret
357 357
358 358 # End of basewirepeer interface.
359 359
360 360 # Begin of baselegacywirepeer interface.
361 361
362 362 def branches(self, nodes):
363 363 n = encodelist(nodes)
364 364 d = self._call("branches", nodes=n)
365 365 try:
366 366 br = [tuple(decodelist(b)) for b in d.splitlines()]
367 367 return br
368 368 except ValueError:
369 369 self._abort(error.ResponseError(_("unexpected response:"), d))
370 370
371 371 def between(self, pairs):
372 372 batch = 8 # avoid giant requests
373 373 r = []
374 374 for i in xrange(0, len(pairs), batch):
375 375 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
376 376 d = self._call("between", pairs=n)
377 377 try:
378 378 r.extend(l and decodelist(l) or [] for l in d.splitlines())
379 379 except ValueError:
380 380 self._abort(error.ResponseError(_("unexpected response:"), d))
381 381 return r
382 382
383 383 def changegroup(self, nodes, kind):
384 384 n = encodelist(nodes)
385 385 f = self._callcompressable("changegroup", roots=n)
386 386 return changegroupmod.cg1unpacker(f, 'UN')
387 387
388 388 def changegroupsubset(self, bases, heads, kind):
389 389 self.requirecap('changegroupsubset', _('look up remote changes'))
390 390 bases = encodelist(bases)
391 391 heads = encodelist(heads)
392 392 f = self._callcompressable("changegroupsubset",
393 393 bases=bases, heads=heads)
394 394 return changegroupmod.cg1unpacker(f, 'UN')
395 395
396 396 # End of baselegacywirepeer interface.
397 397
398 398 def _submitbatch(self, req):
399 399 """run batch request <req> on the server
400 400
401 401 Returns an iterator of the raw responses from the server.
402 402 """
403 ui = self.ui
404 if ui.debugflag and ui.configbool('devel', 'debug.peer-request'):
405 ui.debug('devel-peer-request: batched-content\n')
406 for op, args in req:
407 msg = 'devel-peer-request: - %s (%d arguments)\n'
408 ui.debug(msg % (op, len(args)))
409
403 410 rsp = self._callstream("batch", cmds=encodebatchcmds(req))
404 411 chunk = rsp.read(1024)
405 412 work = [chunk]
406 413 while chunk:
407 414 while ';' not in chunk and chunk:
408 415 chunk = rsp.read(1024)
409 416 work.append(chunk)
410 417 merged = ''.join(work)
411 418 while ';' in merged:
412 419 one, merged = merged.split(';', 1)
413 420 yield unescapearg(one)
414 421 chunk = rsp.read(1024)
415 422 work = [merged, chunk]
416 423 yield unescapearg(''.join(work))
417 424
418 425 def _submitone(self, op, args):
419 426 return self._call(op, **pycompat.strkwargs(args))
420 427
421 428 def debugwireargs(self, one, two, three=None, four=None, five=None):
422 429 # don't pass optional arguments left at their default value
423 430 opts = {}
424 431 if three is not None:
425 432 opts[r'three'] = three
426 433 if four is not None:
427 434 opts[r'four'] = four
428 435 return self._call('debugwireargs', one=one, two=two, **opts)
429 436
430 437 def _call(self, cmd, **args):
431 438 """execute <cmd> on the server
432 439
433 440 The command is expected to return a simple string.
434 441
435 442 returns the server reply as a string."""
436 443 raise NotImplementedError()
437 444
438 445 def _callstream(self, cmd, **args):
439 446 """execute <cmd> on the server
440 447
441 448 The command is expected to return a stream. Note that if the
442 449 command doesn't return a stream, _callstream behaves
443 450 differently for ssh and http peers.
444 451
445 452 returns the server reply as a file like object.
446 453 """
447 454 raise NotImplementedError()
448 455
449 456 def _callcompressable(self, cmd, **args):
450 457 """execute <cmd> on the server
451 458
452 459 The command is expected to return a stream.
453 460
454 461 The stream may have been compressed in some implementations. This
455 462 function takes care of the decompression. This is the only difference
456 463 with _callstream.
457 464
458 465 returns the server reply as a file like object.
459 466 """
460 467 raise NotImplementedError()
461 468
462 469 def _callpush(self, cmd, fp, **args):
463 470 """execute a <cmd> on server
464 471
465 472 The command is expected to be related to a push. Push has a special
466 473 return method.
467 474
468 475 returns the server reply as a (ret, output) tuple. ret is either
469 476 empty (error) or a stringified int.
470 477 """
471 478 raise NotImplementedError()
472 479
473 480 def _calltwowaystream(self, cmd, fp, **args):
474 481 """execute <cmd> on server
475 482
476 483 The command will send a stream to the server and get a stream in reply.
477 484 """
478 485 raise NotImplementedError()
479 486
480 487 def _abort(self, exception):
481 488 """clearly abort the wire protocol connection and raise the exception
482 489 """
483 490 raise NotImplementedError()
484 491
485 492 # server side
486 493
487 494 # wire protocol command can either return a string or one of these classes.
488 495
489 496 def getdispatchrepo(repo, proto, command):
490 497 """Obtain the repo used for processing wire protocol commands.
491 498
492 499 The intent of this function is to serve as a monkeypatch point for
493 500 extensions that need commands to operate on different repo views under
494 501 specialized circumstances.
495 502 """
496 503 return repo.filtered('served')
497 504
498 505 def dispatch(repo, proto, command):
499 506 repo = getdispatchrepo(repo, proto, command)
500 507 func, spec = commands[command]
501 508 args = proto.getargs(spec)
502 509 return func(repo, proto, *args)
503 510
504 511 def options(cmd, keys, others):
505 512 opts = {}
506 513 for k in keys:
507 514 if k in others:
508 515 opts[k] = others[k]
509 516 del others[k]
510 517 if others:
511 518 util.stderr.write("warning: %s ignored unexpected arguments %s\n"
512 519 % (cmd, ",".join(others)))
513 520 return opts
514 521
515 522 def bundle1allowed(repo, action):
516 523 """Whether a bundle1 operation is allowed from the server.
517 524
518 525 Priority is:
519 526
520 527 1. server.bundle1gd.<action> (if generaldelta active)
521 528 2. server.bundle1.<action>
522 529 3. server.bundle1gd (if generaldelta active)
523 530 4. server.bundle1
524 531 """
525 532 ui = repo.ui
526 533 gd = 'generaldelta' in repo.requirements
527 534
528 535 if gd:
529 536 v = ui.configbool('server', 'bundle1gd.%s' % action)
530 537 if v is not None:
531 538 return v
532 539
533 540 v = ui.configbool('server', 'bundle1.%s' % action)
534 541 if v is not None:
535 542 return v
536 543
537 544 if gd:
538 545 v = ui.configbool('server', 'bundle1gd')
539 546 if v is not None:
540 547 return v
541 548
542 549 return ui.configbool('server', 'bundle1')
543 550
544 551 def supportedcompengines(ui, role):
545 552 """Obtain the list of supported compression engines for a request."""
546 553 assert role in (util.CLIENTROLE, util.SERVERROLE)
547 554
548 555 compengines = util.compengines.supportedwireengines(role)
549 556
550 557 # Allow config to override default list and ordering.
551 558 if role == util.SERVERROLE:
552 559 configengines = ui.configlist('server', 'compressionengines')
553 560 config = 'server.compressionengines'
554 561 else:
555 562 # This is currently implemented mainly to facilitate testing. In most
556 563 # cases, the server should be in charge of choosing a compression engine
557 564 # because a server has the most to lose from a sub-optimal choice. (e.g.
558 565 # CPU DoS due to an expensive engine or a network DoS due to poor
559 566 # compression ratio).
560 567 configengines = ui.configlist('experimental',
561 568 'clientcompressionengines')
562 569 config = 'experimental.clientcompressionengines'
563 570
564 571 # No explicit config. Filter out the ones that aren't supposed to be
565 572 # advertised and return default ordering.
566 573 if not configengines:
567 574 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
568 575 return [e for e in compengines
569 576 if getattr(e.wireprotosupport(), attr) > 0]
570 577
571 578 # If compression engines are listed in the config, assume there is a good
572 579 # reason for it (like server operators wanting to achieve specific
573 580 # performance characteristics). So fail fast if the config references
574 581 # unusable compression engines.
575 582 validnames = set(e.name() for e in compengines)
576 583 invalidnames = set(e for e in configengines if e not in validnames)
577 584 if invalidnames:
578 585 raise error.Abort(_('invalid compression engine defined in %s: %s') %
579 586 (config, ', '.join(sorted(invalidnames))))
580 587
581 588 compengines = [e for e in compengines if e.name() in configengines]
582 589 compengines = sorted(compengines,
583 590 key=lambda e: configengines.index(e.name()))
584 591
585 592 if not compengines:
586 593 raise error.Abort(_('%s config option does not specify any known '
587 594 'compression engines') % config,
588 595 hint=_('usable compression engines: %s') %
589 596 ', '.sorted(validnames))
590 597
591 598 return compengines
592 599
593 600 class commandentry(object):
594 601 """Represents a declared wire protocol command."""
595 602 def __init__(self, func, args='', transports=None,
596 603 permission='push'):
597 604 self.func = func
598 605 self.args = args
599 606 self.transports = transports or set()
600 607 self.permission = permission
601 608
602 609 def _merge(self, func, args):
603 610 """Merge this instance with an incoming 2-tuple.
604 611
605 612 This is called when a caller using the old 2-tuple API attempts
606 613 to replace an instance. The incoming values are merged with
607 614 data not captured by the 2-tuple and a new instance containing
608 615 the union of the two objects is returned.
609 616 """
610 617 return commandentry(func, args=args, transports=set(self.transports),
611 618 permission=self.permission)
612 619
613 620 # Old code treats instances as 2-tuples. So expose that interface.
614 621 def __iter__(self):
615 622 yield self.func
616 623 yield self.args
617 624
618 625 def __getitem__(self, i):
619 626 if i == 0:
620 627 return self.func
621 628 elif i == 1:
622 629 return self.args
623 630 else:
624 631 raise IndexError('can only access elements 0 and 1')
625 632
626 633 class commanddict(dict):
627 634 """Container for registered wire protocol commands.
628 635
629 636 It behaves like a dict. But __setitem__ is overwritten to allow silent
630 637 coercion of values from 2-tuples for API compatibility.
631 638 """
632 639 def __setitem__(self, k, v):
633 640 if isinstance(v, commandentry):
634 641 pass
635 642 # Cast 2-tuples to commandentry instances.
636 643 elif isinstance(v, tuple):
637 644 if len(v) != 2:
638 645 raise ValueError('command tuples must have exactly 2 elements')
639 646
640 647 # It is common for extensions to wrap wire protocol commands via
641 648 # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
642 649 # doing this aren't aware of the new API that uses objects to store
643 650 # command entries, we automatically merge old state with new.
644 651 if k in self:
645 652 v = self[k]._merge(v[0], v[1])
646 653 else:
647 654 # Use default values from @wireprotocommand.
648 655 v = commandentry(v[0], args=v[1],
649 656 transports=set(wireprototypes.TRANSPORTS),
650 657 permission='push')
651 658 else:
652 659 raise ValueError('command entries must be commandentry instances '
653 660 'or 2-tuples')
654 661
655 662 return super(commanddict, self).__setitem__(k, v)
656 663
657 664 def commandavailable(self, command, proto):
658 665 """Determine if a command is available for the requested protocol."""
659 666 assert proto.name in wireprototypes.TRANSPORTS
660 667
661 668 entry = self.get(command)
662 669
663 670 if not entry:
664 671 return False
665 672
666 673 if proto.name not in entry.transports:
667 674 return False
668 675
669 676 return True
670 677
671 678 # Constants specifying which transports a wire protocol command should be
672 679 # available on. For use with @wireprotocommand.
673 680 POLICY_ALL = 'all'
674 681 POLICY_V1_ONLY = 'v1-only'
675 682 POLICY_V2_ONLY = 'v2-only'
676 683
677 684 commands = commanddict()
678 685
679 686 def wireprotocommand(name, args='', transportpolicy=POLICY_ALL,
680 687 permission='push'):
681 688 """Decorator to declare a wire protocol command.
682 689
683 690 ``name`` is the name of the wire protocol command being provided.
684 691
685 692 ``args`` is a space-delimited list of named arguments that the command
686 693 accepts. ``*`` is a special value that says to accept all arguments.
687 694
688 695 ``transportpolicy`` is a POLICY_* constant denoting which transports
689 696 this wire protocol command should be exposed to. By default, commands
690 697 are exposed to all wire protocol transports.
691 698
692 699 ``permission`` defines the permission type needed to run this command.
693 700 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
694 701 respectively. Default is to assume command requires ``push`` permissions
695 702 because otherwise commands not declaring their permissions could modify
696 703 a repository that is supposed to be read-only.
697 704 """
698 705 if transportpolicy == POLICY_ALL:
699 706 transports = set(wireprototypes.TRANSPORTS)
700 707 elif transportpolicy == POLICY_V1_ONLY:
701 708 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
702 709 if v['version'] == 1}
703 710 elif transportpolicy == POLICY_V2_ONLY:
704 711 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
705 712 if v['version'] == 2}
706 713 else:
707 714 raise error.ProgrammingError('invalid transport policy value: %s' %
708 715 transportpolicy)
709 716
710 717 if permission not in ('push', 'pull'):
711 718 raise error.ProgrammingError('invalid wire protocol permission; '
712 719 'got %s; expected "push" or "pull"' %
713 720 permission)
714 721
715 722 def register(func):
716 723 commands[name] = commandentry(func, args=args, transports=transports,
717 724 permission=permission)
718 725 return func
719 726 return register
720 727
721 728 # TODO define a more appropriate permissions type to use for this.
722 729 @wireprotocommand('batch', 'cmds *', permission='pull')
723 730 def batch(repo, proto, cmds, others):
724 731 repo = repo.filtered("served")
725 732 res = []
726 733 for pair in cmds.split(';'):
727 734 op, args = pair.split(' ', 1)
728 735 vals = {}
729 736 for a in args.split(','):
730 737 if a:
731 738 n, v = a.split('=')
732 739 vals[unescapearg(n)] = unescapearg(v)
733 740 func, spec = commands[op]
734 741
735 742 # Validate that client has permissions to perform this command.
736 743 perm = commands[op].permission
737 744 assert perm in ('push', 'pull')
738 745 proto.checkperm(perm)
739 746
740 747 if spec:
741 748 keys = spec.split()
742 749 data = {}
743 750 for k in keys:
744 751 if k == '*':
745 752 star = {}
746 753 for key in vals.keys():
747 754 if key not in keys:
748 755 star[key] = vals[key]
749 756 data['*'] = star
750 757 else:
751 758 data[k] = vals[k]
752 759 result = func(repo, proto, *[data[k] for k in keys])
753 760 else:
754 761 result = func(repo, proto)
755 762 if isinstance(result, ooberror):
756 763 return result
757 764
758 765 # For now, all batchable commands must return bytesresponse or
759 766 # raw bytes (for backwards compatibility).
760 767 assert isinstance(result, (bytesresponse, bytes))
761 768 if isinstance(result, bytesresponse):
762 769 result = result.data
763 770 res.append(escapearg(result))
764 771
765 772 return bytesresponse(';'.join(res))
766 773
767 774 @wireprotocommand('between', 'pairs', transportpolicy=POLICY_V1_ONLY,
768 775 permission='pull')
769 776 def between(repo, proto, pairs):
770 777 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
771 778 r = []
772 779 for b in repo.between(pairs):
773 780 r.append(encodelist(b) + "\n")
774 781
775 782 return bytesresponse(''.join(r))
776 783
777 784 @wireprotocommand('branchmap', permission='pull')
778 785 def branchmap(repo, proto):
779 786 branchmap = repo.branchmap()
780 787 heads = []
781 788 for branch, nodes in branchmap.iteritems():
782 789 branchname = urlreq.quote(encoding.fromlocal(branch))
783 790 branchnodes = encodelist(nodes)
784 791 heads.append('%s %s' % (branchname, branchnodes))
785 792
786 793 return bytesresponse('\n'.join(heads))
787 794
788 795 @wireprotocommand('branches', 'nodes', transportpolicy=POLICY_V1_ONLY,
789 796 permission='pull')
790 797 def branches(repo, proto, nodes):
791 798 nodes = decodelist(nodes)
792 799 r = []
793 800 for b in repo.branches(nodes):
794 801 r.append(encodelist(b) + "\n")
795 802
796 803 return bytesresponse(''.join(r))
797 804
798 805 @wireprotocommand('clonebundles', '', permission='pull')
799 806 def clonebundles(repo, proto):
800 807 """Server command for returning info for available bundles to seed clones.
801 808
802 809 Clients will parse this response and determine what bundle to fetch.
803 810
804 811 Extensions may wrap this command to filter or dynamically emit data
805 812 depending on the request. e.g. you could advertise URLs for the closest
806 813 data center given the client's IP address.
807 814 """
808 815 return bytesresponse(repo.vfs.tryread('clonebundles.manifest'))
809 816
810 817 wireprotocaps = ['lookup', 'branchmap', 'pushkey',
811 818 'known', 'getbundle', 'unbundlehash', 'batch']
812 819
813 820 def _capabilities(repo, proto):
814 821 """return a list of capabilities for a repo
815 822
816 823 This function exists to allow extensions to easily wrap capabilities
817 824 computation
818 825
819 826 - returns a lists: easy to alter
820 827 - change done here will be propagated to both `capabilities` and `hello`
821 828 command without any other action needed.
822 829 """
823 830 # copy to prevent modification of the global list
824 831 caps = list(wireprotocaps)
825 832
826 833 # Command of same name as capability isn't exposed to version 1 of
827 834 # transports. So conditionally add it.
828 835 if commands.commandavailable('changegroupsubset', proto):
829 836 caps.append('changegroupsubset')
830 837
831 838 if streamclone.allowservergeneration(repo):
832 839 if repo.ui.configbool('server', 'preferuncompressed'):
833 840 caps.append('stream-preferred')
834 841 requiredformats = repo.requirements & repo.supportedformats
835 842 # if our local revlogs are just revlogv1, add 'stream' cap
836 843 if not requiredformats - {'revlogv1'}:
837 844 caps.append('stream')
838 845 # otherwise, add 'streamreqs' detailing our local revlog format
839 846 else:
840 847 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
841 848 if repo.ui.configbool('experimental', 'bundle2-advertise'):
842 849 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role='server'))
843 850 caps.append('bundle2=' + urlreq.quote(capsblob))
844 851 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
845 852
846 853 return proto.addcapabilities(repo, caps)
847 854
848 855 # If you are writing an extension and consider wrapping this function. Wrap
849 856 # `_capabilities` instead.
850 857 @wireprotocommand('capabilities', permission='pull')
851 858 def capabilities(repo, proto):
852 859 return bytesresponse(' '.join(_capabilities(repo, proto)))
853 860
854 861 @wireprotocommand('changegroup', 'roots', transportpolicy=POLICY_V1_ONLY,
855 862 permission='pull')
856 863 def changegroup(repo, proto, roots):
857 864 nodes = decodelist(roots)
858 865 outgoing = discovery.outgoing(repo, missingroots=nodes,
859 866 missingheads=repo.heads())
860 867 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
861 868 gen = iter(lambda: cg.read(32768), '')
862 869 return streamres(gen=gen)
863 870
864 871 @wireprotocommand('changegroupsubset', 'bases heads',
865 872 transportpolicy=POLICY_V1_ONLY,
866 873 permission='pull')
867 874 def changegroupsubset(repo, proto, bases, heads):
868 875 bases = decodelist(bases)
869 876 heads = decodelist(heads)
870 877 outgoing = discovery.outgoing(repo, missingroots=bases,
871 878 missingheads=heads)
872 879 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
873 880 gen = iter(lambda: cg.read(32768), '')
874 881 return streamres(gen=gen)
875 882
876 883 @wireprotocommand('debugwireargs', 'one two *',
877 884 permission='pull')
878 885 def debugwireargs(repo, proto, one, two, others):
879 886 # only accept optional args from the known set
880 887 opts = options('debugwireargs', ['three', 'four'], others)
881 888 return bytesresponse(repo.debugwireargs(one, two,
882 889 **pycompat.strkwargs(opts)))
883 890
884 891 @wireprotocommand('getbundle', '*', permission='pull')
885 892 def getbundle(repo, proto, others):
886 893 opts = options('getbundle', gboptsmap.keys(), others)
887 894 for k, v in opts.iteritems():
888 895 keytype = gboptsmap[k]
889 896 if keytype == 'nodes':
890 897 opts[k] = decodelist(v)
891 898 elif keytype == 'csv':
892 899 opts[k] = list(v.split(','))
893 900 elif keytype == 'scsv':
894 901 opts[k] = set(v.split(','))
895 902 elif keytype == 'boolean':
896 903 # Client should serialize False as '0', which is a non-empty string
897 904 # so it evaluates as a True bool.
898 905 if v == '0':
899 906 opts[k] = False
900 907 else:
901 908 opts[k] = bool(v)
902 909 elif keytype != 'plain':
903 910 raise KeyError('unknown getbundle option type %s'
904 911 % keytype)
905 912
906 913 if not bundle1allowed(repo, 'pull'):
907 914 if not exchange.bundle2requested(opts.get('bundlecaps')):
908 915 if proto.name == 'http-v1':
909 916 return ooberror(bundle2required)
910 917 raise error.Abort(bundle2requiredmain,
911 918 hint=bundle2requiredhint)
912 919
913 920 prefercompressed = True
914 921
915 922 try:
916 923 if repo.ui.configbool('server', 'disablefullbundle'):
917 924 # Check to see if this is a full clone.
918 925 clheads = set(repo.changelog.heads())
919 926 changegroup = opts.get('cg', True)
920 927 heads = set(opts.get('heads', set()))
921 928 common = set(opts.get('common', set()))
922 929 common.discard(nullid)
923 930 if changegroup and not common and clheads == heads:
924 931 raise error.Abort(
925 932 _('server has pull-based clones disabled'),
926 933 hint=_('remove --pull if specified or upgrade Mercurial'))
927 934
928 935 info, chunks = exchange.getbundlechunks(repo, 'serve',
929 936 **pycompat.strkwargs(opts))
930 937 prefercompressed = info.get('prefercompressed', True)
931 938 except error.Abort as exc:
932 939 # cleanly forward Abort error to the client
933 940 if not exchange.bundle2requested(opts.get('bundlecaps')):
934 941 if proto.name == 'http-v1':
935 942 return ooberror(pycompat.bytestr(exc) + '\n')
936 943 raise # cannot do better for bundle1 + ssh
937 944 # bundle2 request expect a bundle2 reply
938 945 bundler = bundle2.bundle20(repo.ui)
939 946 manargs = [('message', pycompat.bytestr(exc))]
940 947 advargs = []
941 948 if exc.hint is not None:
942 949 advargs.append(('hint', exc.hint))
943 950 bundler.addpart(bundle2.bundlepart('error:abort',
944 951 manargs, advargs))
945 952 chunks = bundler.getchunks()
946 953 prefercompressed = False
947 954
948 955 return streamres(gen=chunks, prefer_uncompressed=not prefercompressed)
949 956
950 957 @wireprotocommand('heads', permission='pull')
951 958 def heads(repo, proto):
952 959 h = repo.heads()
953 960 return bytesresponse(encodelist(h) + '\n')
954 961
955 962 @wireprotocommand('hello', permission='pull')
956 963 def hello(repo, proto):
957 964 """Called as part of SSH handshake to obtain server info.
958 965
959 966 Returns a list of lines describing interesting things about the
960 967 server, in an RFC822-like format.
961 968
962 969 Currently, the only one defined is ``capabilities``, which consists of a
963 970 line of space separated tokens describing server abilities:
964 971
965 972 capabilities: <token0> <token1> <token2>
966 973 """
967 974 caps = capabilities(repo, proto).data
968 975 return bytesresponse('capabilities: %s\n' % caps)
969 976
970 977 @wireprotocommand('listkeys', 'namespace', permission='pull')
971 978 def listkeys(repo, proto, namespace):
972 979 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
973 980 return bytesresponse(pushkeymod.encodekeys(d))
974 981
975 982 @wireprotocommand('lookup', 'key', permission='pull')
976 983 def lookup(repo, proto, key):
977 984 try:
978 985 k = encoding.tolocal(key)
979 986 c = repo[k]
980 987 r = c.hex()
981 988 success = 1
982 989 except Exception as inst:
983 990 r = util.forcebytestr(inst)
984 991 success = 0
985 992 return bytesresponse('%d %s\n' % (success, r))
986 993
987 994 @wireprotocommand('known', 'nodes *', permission='pull')
988 995 def known(repo, proto, nodes, others):
989 996 v = ''.join(b and '1' or '0' for b in repo.known(decodelist(nodes)))
990 997 return bytesresponse(v)
991 998
992 999 @wireprotocommand('pushkey', 'namespace key old new', permission='push')
993 1000 def pushkey(repo, proto, namespace, key, old, new):
994 1001 # compatibility with pre-1.8 clients which were accidentally
995 1002 # sending raw binary nodes rather than utf-8-encoded hex
996 1003 if len(new) == 20 and util.escapestr(new) != new:
997 1004 # looks like it could be a binary node
998 1005 try:
999 1006 new.decode('utf-8')
1000 1007 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
1001 1008 except UnicodeDecodeError:
1002 1009 pass # binary, leave unmodified
1003 1010 else:
1004 1011 new = encoding.tolocal(new) # normal path
1005 1012
1006 1013 with proto.mayberedirectstdio() as output:
1007 1014 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
1008 1015 encoding.tolocal(old), new) or False
1009 1016
1010 1017 output = output.getvalue() if output else ''
1011 1018 return bytesresponse('%d\n%s' % (int(r), output))
1012 1019
1013 1020 @wireprotocommand('stream_out', permission='pull')
1014 1021 def stream(repo, proto):
1015 1022 '''If the server supports streaming clone, it advertises the "stream"
1016 1023 capability with a value representing the version and flags of the repo
1017 1024 it is serving. Client checks to see if it understands the format.
1018 1025 '''
1019 1026 return streamres_legacy(streamclone.generatev1wireproto(repo))
1020 1027
1021 1028 @wireprotocommand('unbundle', 'heads', permission='push')
1022 1029 def unbundle(repo, proto, heads):
1023 1030 their_heads = decodelist(heads)
1024 1031
1025 1032 with proto.mayberedirectstdio() as output:
1026 1033 try:
1027 1034 exchange.check_heads(repo, their_heads, 'preparing changes')
1028 1035
1029 1036 # write bundle data to temporary file because it can be big
1030 1037 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1031 1038 fp = os.fdopen(fd, r'wb+')
1032 1039 r = 0
1033 1040 try:
1034 1041 proto.forwardpayload(fp)
1035 1042 fp.seek(0)
1036 1043 gen = exchange.readbundle(repo.ui, fp, None)
1037 1044 if (isinstance(gen, changegroupmod.cg1unpacker)
1038 1045 and not bundle1allowed(repo, 'push')):
1039 1046 if proto.name == 'http-v1':
1040 1047 # need to special case http because stderr do not get to
1041 1048 # the http client on failed push so we need to abuse
1042 1049 # some other error type to make sure the message get to
1043 1050 # the user.
1044 1051 return ooberror(bundle2required)
1045 1052 raise error.Abort(bundle2requiredmain,
1046 1053 hint=bundle2requiredhint)
1047 1054
1048 1055 r = exchange.unbundle(repo, gen, their_heads, 'serve',
1049 1056 proto.client())
1050 1057 if util.safehasattr(r, 'addpart'):
1051 1058 # The return looks streamable, we are in the bundle2 case
1052 1059 # and should return a stream.
1053 1060 return streamres_legacy(gen=r.getchunks())
1054 1061 return pushres(r, output.getvalue() if output else '')
1055 1062
1056 1063 finally:
1057 1064 fp.close()
1058 1065 os.unlink(tempname)
1059 1066
1060 1067 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
1061 1068 # handle non-bundle2 case first
1062 1069 if not getattr(exc, 'duringunbundle2', False):
1063 1070 try:
1064 1071 raise
1065 1072 except error.Abort:
1066 1073 # The old code we moved used util.stderr directly.
1067 1074 # We did not change it to minimise code change.
1068 1075 # This need to be moved to something proper.
1069 1076 # Feel free to do it.
1070 1077 util.stderr.write("abort: %s\n" % exc)
1071 1078 if exc.hint is not None:
1072 1079 util.stderr.write("(%s)\n" % exc.hint)
1073 1080 return pushres(0, output.getvalue() if output else '')
1074 1081 except error.PushRaced:
1075 1082 return pusherr(pycompat.bytestr(exc),
1076 1083 output.getvalue() if output else '')
1077 1084
1078 1085 bundler = bundle2.bundle20(repo.ui)
1079 1086 for out in getattr(exc, '_bundle2salvagedoutput', ()):
1080 1087 bundler.addpart(out)
1081 1088 try:
1082 1089 try:
1083 1090 raise
1084 1091 except error.PushkeyFailed as exc:
1085 1092 # check client caps
1086 1093 remotecaps = getattr(exc, '_replycaps', None)
1087 1094 if (remotecaps is not None
1088 1095 and 'pushkey' not in remotecaps.get('error', ())):
1089 1096 # no support remote side, fallback to Abort handler.
1090 1097 raise
1091 1098 part = bundler.newpart('error:pushkey')
1092 1099 part.addparam('in-reply-to', exc.partid)
1093 1100 if exc.namespace is not None:
1094 1101 part.addparam('namespace', exc.namespace,
1095 1102 mandatory=False)
1096 1103 if exc.key is not None:
1097 1104 part.addparam('key', exc.key, mandatory=False)
1098 1105 if exc.new is not None:
1099 1106 part.addparam('new', exc.new, mandatory=False)
1100 1107 if exc.old is not None:
1101 1108 part.addparam('old', exc.old, mandatory=False)
1102 1109 if exc.ret is not None:
1103 1110 part.addparam('ret', exc.ret, mandatory=False)
1104 1111 except error.BundleValueError as exc:
1105 1112 errpart = bundler.newpart('error:unsupportedcontent')
1106 1113 if exc.parttype is not None:
1107 1114 errpart.addparam('parttype', exc.parttype)
1108 1115 if exc.params:
1109 1116 errpart.addparam('params', '\0'.join(exc.params))
1110 1117 except error.Abort as exc:
1111 1118 manargs = [('message', util.forcebytestr(exc))]
1112 1119 advargs = []
1113 1120 if exc.hint is not None:
1114 1121 advargs.append(('hint', exc.hint))
1115 1122 bundler.addpart(bundle2.bundlepart('error:abort',
1116 1123 manargs, advargs))
1117 1124 except error.PushRaced as exc:
1118 1125 bundler.newpart('error:pushraced',
1119 1126 [('message', util.forcebytestr(exc))])
1120 1127 return streamres_legacy(gen=bundler.getchunks())
@@ -1,552 +1,555 b''
1 1 #require killdaemons serve
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo foo>foo
6 6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
7 7 $ echo foo>foo.d/foo
8 8 $ echo bar>foo.d/bAr.hg.d/BaR
9 9 $ echo bar>foo.d/baR.d.hg/bAR
10 10 $ hg commit -A -m 1
11 11 adding foo
12 12 adding foo.d/bAr.hg.d/BaR
13 13 adding foo.d/baR.d.hg/bAR
14 14 adding foo.d/foo
15 15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
16 16 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
17 17
18 18 Test server address cannot be reused
19 19
20 20 $ hg serve -p $HGPORT1 2>&1
21 21 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
22 22 [255]
23 23
24 24 $ cd ..
25 25 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
26 26
27 27 clone via stream
28 28
29 29 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
30 30 streaming all changes
31 31 6 files to transfer, 606 bytes of data
32 32 transferred * bytes in * seconds (*/sec) (glob)
33 33 searching for changes
34 34 no changes found
35 35 updating to branch default
36 36 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 37 $ hg verify -R copy
38 38 checking changesets
39 39 checking manifests
40 40 crosschecking files in changesets and manifests
41 41 checking files
42 42 4 files, 1 changesets, 4 total revisions
43 43
44 44 try to clone via stream, should use pull instead
45 45
46 46 $ hg clone --stream http://localhost:$HGPORT1/ copy2
47 47 warning: stream clone requested but server has them disabled
48 48 requesting all changes
49 49 adding changesets
50 50 adding manifests
51 51 adding file changes
52 52 added 1 changesets with 4 changes to 4 files
53 53 new changesets 8b6053c928fe
54 54 updating to branch default
55 55 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 56
57 57 try to clone via stream but missing requirements, so should use pull instead
58 58
59 59 $ cat > $TESTTMP/removesupportedformat.py << EOF
60 60 > from mercurial import localrepo
61 61 > def extsetup(ui):
62 62 > localrepo.localrepository.supportedformats.remove('generaldelta')
63 63 > EOF
64 64
65 65 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
66 66 warning: stream clone requested but client is missing requirements: generaldelta
67 67 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
68 68 requesting all changes
69 69 adding changesets
70 70 adding manifests
71 71 adding file changes
72 72 added 1 changesets with 4 changes to 4 files
73 73 new changesets 8b6053c928fe
74 74 updating to branch default
75 75 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 76
77 77 clone via pull
78 78
79 79 $ hg clone http://localhost:$HGPORT1/ copy-pull
80 80 requesting all changes
81 81 adding changesets
82 82 adding manifests
83 83 adding file changes
84 84 added 1 changesets with 4 changes to 4 files
85 85 new changesets 8b6053c928fe
86 86 updating to branch default
87 87 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
88 88 $ hg verify -R copy-pull
89 89 checking changesets
90 90 checking manifests
91 91 crosschecking files in changesets and manifests
92 92 checking files
93 93 4 files, 1 changesets, 4 total revisions
94 94 $ cd test
95 95 $ echo bar > bar
96 96 $ hg commit -A -d '1 0' -m 2
97 97 adding bar
98 98 $ cd ..
99 99
100 100 clone over http with --update
101 101
102 102 $ hg clone http://localhost:$HGPORT1/ updated --update 0
103 103 requesting all changes
104 104 adding changesets
105 105 adding manifests
106 106 adding file changes
107 107 added 2 changesets with 5 changes to 5 files
108 108 new changesets 8b6053c928fe:5fed3813f7f5
109 109 updating to branch default
110 110 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 111 $ hg log -r . -R updated
112 112 changeset: 0:8b6053c928fe
113 113 user: test
114 114 date: Thu Jan 01 00:00:00 1970 +0000
115 115 summary: 1
116 116
117 117 $ rm -rf updated
118 118
119 119 incoming via HTTP
120 120
121 121 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
122 122 adding changesets
123 123 adding manifests
124 124 adding file changes
125 125 added 1 changesets with 4 changes to 4 files
126 126 new changesets 8b6053c928fe
127 127 updating to branch default
128 128 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 129 $ cd partial
130 130 $ touch LOCAL
131 131 $ hg ci -qAm LOCAL
132 132 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
133 133 comparing with http://localhost:$HGPORT1/
134 134 searching for changes
135 135 2
136 136 $ cd ..
137 137
138 138 pull
139 139
140 140 $ cd copy-pull
141 141 $ cat >> .hg/hgrc <<EOF
142 142 > [hooks]
143 143 > changegroup = sh -c "printenv.py changegroup"
144 144 > EOF
145 145 $ hg pull
146 146 pulling from http://localhost:$HGPORT1/
147 147 searching for changes
148 148 adding changesets
149 149 adding manifests
150 150 adding file changes
151 151 added 1 changesets with 1 changes to 1 files
152 152 new changesets 5fed3813f7f5
153 153 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
154 154 (run 'hg update' to get a working copy)
155 155 $ cd ..
156 156
157 157 clone from invalid URL
158 158
159 159 $ hg clone http://localhost:$HGPORT/bad
160 160 abort: HTTP Error 404: Not Found
161 161 [255]
162 162
163 163 test http authentication
164 164 + use the same server to test server side streaming preference
165 165
166 166 $ cd test
167 167 $ cat << EOT > userpass.py
168 168 > import base64
169 169 > from mercurial.hgweb import common
170 170 > def perform_authentication(hgweb, req, op):
171 171 > auth = req.headers.get('Authorization')
172 172 > if not auth:
173 173 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
174 174 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
175 175 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
176 176 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
177 177 > def extsetup():
178 178 > common.permhooks.insert(0, perform_authentication)
179 179 > EOT
180 180 $ hg serve --config extensions.x=userpass.py -p $HGPORT2 -d --pid-file=pid \
181 181 > --config server.preferuncompressed=True \
182 182 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
183 183 $ cat pid >> $DAEMON_PIDS
184 184
185 185 $ cat << EOF > get_pass.py
186 186 > import getpass
187 187 > def newgetpass(arg):
188 188 > return "pass"
189 189 > getpass.getpass = newgetpass
190 190 > EOF
191 191
192 192 $ hg id http://localhost:$HGPORT2/
193 193 abort: http authorization required for http://localhost:$HGPORT2/
194 194 [255]
195 195 $ hg id http://localhost:$HGPORT2/
196 196 abort: http authorization required for http://localhost:$HGPORT2/
197 197 [255]
198 198 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
199 199 http authorization required for http://localhost:$HGPORT2/
200 200 realm: mercurial
201 201 user: user
202 202 password: 5fed3813f7f5
203 203 $ hg id http://user:pass@localhost:$HGPORT2/
204 204 5fed3813f7f5
205 205 $ echo '[auth]' >> .hg/hgrc
206 206 $ echo 'l.schemes=http' >> .hg/hgrc
207 207 $ echo 'l.prefix=lo' >> .hg/hgrc
208 208 $ echo 'l.username=user' >> .hg/hgrc
209 209 $ echo 'l.password=pass' >> .hg/hgrc
210 210 $ hg id http://localhost:$HGPORT2/
211 211 5fed3813f7f5
212 212 $ hg id http://localhost:$HGPORT2/
213 213 5fed3813f7f5
214 214 $ hg id http://user@localhost:$HGPORT2/
215 215 5fed3813f7f5
216 216 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
217 217 streaming all changes
218 218 7 files to transfer, 916 bytes of data
219 219 transferred * bytes in * seconds (*/sec) (glob)
220 220 searching for changes
221 221 no changes found
222 222 updating to branch default
223 223 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
224 224 --pull should override server's preferuncompressed
225 225 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
226 226 requesting all changes
227 227 adding changesets
228 228 adding manifests
229 229 adding file changes
230 230 added 2 changesets with 5 changes to 5 files
231 231 new changesets 8b6053c928fe:5fed3813f7f5
232 232 updating to branch default
233 233 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
234 234
235 235 $ hg id http://user2@localhost:$HGPORT2/
236 236 abort: http authorization required for http://localhost:$HGPORT2/
237 237 [255]
238 238 $ hg id http://user:pass2@localhost:$HGPORT2/
239 239 abort: HTTP Error 403: no
240 240 [255]
241 241
242 242 $ hg -R dest tag -r tip top
243 243 $ hg -R dest push http://user:pass@localhost:$HGPORT2/
244 244 pushing to http://user:***@localhost:$HGPORT2/
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 1 changes to 1 files
250 250 $ hg rollback -q
251 251 $ hg -R dest push http://user:pass@localhost:$HGPORT2/ --debug --config devel.debug.peer-request=yes
252 252 pushing to http://user:***@localhost:$HGPORT2/
253 253 using http://localhost:$HGPORT2/
254 254 http auth: user user, password ****
255 255 sending capabilities command
256 256 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=capabilities
257 257 http auth: user user, password ****
258 258 devel-peer-request: finished in *.???? seconds (200) (glob)
259 259 query 1; heads
260 devel-peer-request: batched-content
261 devel-peer-request: - heads (0 arguments)
262 devel-peer-request: - known (1 arguments)
260 263 sending batch command
261 264 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=batch
262 265 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
263 266 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
264 267 devel-peer-request: 68 bytes of commands arguments in headers
265 268 devel-peer-request: finished in *.???? seconds (200) (glob)
266 269 searching for changes
267 270 all remote heads known locally
268 271 preparing listkeys for "phases"
269 272 sending listkeys command
270 273 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
271 274 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
272 275 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
273 276 devel-peer-request: 16 bytes of commands arguments in headers
274 277 devel-peer-request: finished in *.???? seconds (200) (glob)
275 278 received listkey for "phases": 58 bytes
276 279 checking for updated bookmarks
277 280 preparing listkeys for "bookmarks"
278 281 sending listkeys command
279 282 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
280 283 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
281 284 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
282 285 devel-peer-request: 19 bytes of commands arguments in headers
283 286 devel-peer-request: finished in *.???? seconds (200) (glob)
284 287 received listkey for "bookmarks": 0 bytes
285 288 sending branchmap command
286 289 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
287 290 devel-peer-request: Vary X-HgProto-1
288 291 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
289 292 devel-peer-request: finished in *.???? seconds (200) (glob)
290 293 sending branchmap command
291 294 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
292 295 devel-peer-request: Vary X-HgProto-1
293 296 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
294 297 devel-peer-request: finished in *.???? seconds (200) (glob)
295 298 preparing listkeys for "bookmarks"
296 299 sending listkeys command
297 300 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
298 301 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
299 302 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
300 303 devel-peer-request: 19 bytes of commands arguments in headers
301 304 devel-peer-request: finished in *.???? seconds (200) (glob)
302 305 received listkey for "bookmarks": 0 bytes
303 306 1 changesets found
304 307 list of changesets:
305 308 7f4e523d01f2cc3765ac8934da3d14db775ff872
306 309 bundle2-output-bundle: "HG20", 5 parts total
307 310 bundle2-output-part: "replycaps" 188 bytes payload
308 311 bundle2-output-part: "check:phases" 24 bytes payload
309 312 bundle2-output-part: "check:heads" streamed payload
310 313 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
311 314 bundle2-output-part: "phase-heads" 24 bytes payload
312 315 sending unbundle command
313 316 sending 996 bytes
314 317 devel-peer-request: POST http://localhost:$HGPORT2/?cmd=unbundle
315 318 devel-peer-request: Content-length 996
316 319 devel-peer-request: Content-type application/mercurial-0.1
317 320 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
318 321 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
319 322 devel-peer-request: 16 bytes of commands arguments in headers
320 323 devel-peer-request: 996 bytes of data
321 324 devel-peer-request: finished in *.???? seconds (200) (glob)
322 325 bundle2-input-bundle: no-transaction
323 326 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
324 327 bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
325 328 bundle2-input-part: total payload size 100
326 329 remote: adding changesets
327 330 remote: adding manifests
328 331 remote: adding file changes
329 332 remote: added 1 changesets with 1 changes to 1 files
330 333 bundle2-input-part: "output" (advisory) supported
331 334 bundle2-input-bundle: 2 parts total
332 335 preparing listkeys for "phases"
333 336 sending listkeys command
334 337 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
335 338 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
336 339 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
337 340 devel-peer-request: 16 bytes of commands arguments in headers
338 341 devel-peer-request: finished in *.???? seconds (200) (glob)
339 342 received listkey for "phases": 15 bytes
340 343 $ hg rollback -q
341 344
342 345 $ sed 's/.*] "/"/' < ../access.log
343 346 "GET /?cmd=capabilities HTTP/1.1" 401 -
344 347 "GET /?cmd=capabilities HTTP/1.1" 401 -
345 348 "GET /?cmd=capabilities HTTP/1.1" 401 -
346 349 "GET /?cmd=capabilities HTTP/1.1" 200 -
347 350 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
348 351 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
349 352 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
350 353 "GET /?cmd=capabilities HTTP/1.1" 401 -
351 354 "GET /?cmd=capabilities HTTP/1.1" 200 -
352 355 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
353 356 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
354 357 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
355 358 "GET /?cmd=capabilities HTTP/1.1" 401 -
356 359 "GET /?cmd=capabilities HTTP/1.1" 200 -
357 360 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
358 361 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
359 362 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
360 363 "GET /?cmd=capabilities HTTP/1.1" 401 -
361 364 "GET /?cmd=capabilities HTTP/1.1" 200 -
362 365 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
363 366 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
364 367 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
365 368 "GET /?cmd=capabilities HTTP/1.1" 401 -
366 369 "GET /?cmd=capabilities HTTP/1.1" 200 -
367 370 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
368 371 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
369 372 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
370 373 "GET /?cmd=capabilities HTTP/1.1" 401 -
371 374 "GET /?cmd=capabilities HTTP/1.1" 200 -
372 375 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
373 376 "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
374 377 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
375 378 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
376 379 "GET /?cmd=capabilities HTTP/1.1" 401 -
377 380 "GET /?cmd=capabilities HTTP/1.1" 200 -
378 381 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
379 382 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
380 383 "GET /?cmd=capabilities HTTP/1.1" 401 -
381 384 "GET /?cmd=capabilities HTTP/1.1" 401 -
382 385 "GET /?cmd=capabilities HTTP/1.1" 403 -
383 386 "GET /?cmd=capabilities HTTP/1.1" 401 -
384 387 "GET /?cmd=capabilities HTTP/1.1" 200 -
385 388 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
386 389 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
387 390 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
388 391 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
389 392 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
390 393 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
391 394 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
392 395 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
393 396 "GET /?cmd=capabilities HTTP/1.1" 401 -
394 397 "GET /?cmd=capabilities HTTP/1.1" 200 -
395 398 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
396 399 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
397 400 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
398 401 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
399 402 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
400 403 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
401 404 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
402 405 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
403 406
404 407 $ cd ..
405 408
406 409 clone of serve with repo in root and unserved subrepo (issue2970)
407 410
408 411 $ hg --cwd test init sub
409 412 $ echo empty > test/sub/empty
410 413 $ hg --cwd test/sub add empty
411 414 $ hg --cwd test/sub commit -qm 'add empty'
412 415 $ hg --cwd test/sub tag -r 0 something
413 416 $ echo sub = sub > test/.hgsub
414 417 $ hg --cwd test add .hgsub
415 418 $ hg --cwd test commit -qm 'add subrepo'
416 419 $ hg clone http://localhost:$HGPORT noslash-clone
417 420 requesting all changes
418 421 adding changesets
419 422 adding manifests
420 423 adding file changes
421 424 added 3 changesets with 7 changes to 7 files
422 425 new changesets 8b6053c928fe:56f9bc90cce6
423 426 updating to branch default
424 427 abort: HTTP Error 404: Not Found
425 428 [255]
426 429 $ hg clone http://localhost:$HGPORT/ slash-clone
427 430 requesting all changes
428 431 adding changesets
429 432 adding manifests
430 433 adding file changes
431 434 added 3 changesets with 7 changes to 7 files
432 435 new changesets 8b6053c928fe:56f9bc90cce6
433 436 updating to branch default
434 437 abort: HTTP Error 404: Not Found
435 438 [255]
436 439
437 440 check error log
438 441
439 442 $ cat error.log
440 443
441 444 check abort error reporting while pulling/cloning
442 445
443 446 $ $RUNTESTDIR/killdaemons.py
444 447 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
445 448 $ cat hg3.pid >> $DAEMON_PIDS
446 449 $ hg clone http://localhost:$HGPORT/ abort-clone
447 450 requesting all changes
448 451 remote: abort: this is an exercise
449 452 abort: pull failed on remote
450 453 [255]
451 454 $ cat error.log
452 455
453 456 disable pull-based clones
454 457
455 458 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
456 459 $ cat hg4.pid >> $DAEMON_PIDS
457 460 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
458 461 requesting all changes
459 462 remote: abort: server has pull-based clones disabled
460 463 abort: pull failed on remote
461 464 (remove --pull if specified or upgrade Mercurial)
462 465 [255]
463 466
464 467 ... but keep stream clones working
465 468
466 469 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
467 470 streaming all changes
468 471 * files to transfer, * of data (glob)
469 472 transferred * in * seconds (*/sec) (glob)
470 473 searching for changes
471 474 no changes found
472 475 $ cat error.log
473 476
474 477 ... and also keep partial clones and pulls working
475 478 $ hg clone http://localhost:$HGPORT1 --rev 0 test-partial-clone
476 479 adding changesets
477 480 adding manifests
478 481 adding file changes
479 482 added 1 changesets with 4 changes to 4 files
480 483 new changesets 8b6053c928fe
481 484 updating to branch default
482 485 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 486 $ hg pull -R test-partial-clone
484 487 pulling from http://localhost:$HGPORT1/
485 488 searching for changes
486 489 adding changesets
487 490 adding manifests
488 491 adding file changes
489 492 added 2 changesets with 3 changes to 3 files
490 493 new changesets 5fed3813f7f5:56f9bc90cce6
491 494 (run 'hg update' to get a working copy)
492 495
493 496 corrupt cookies file should yield a warning
494 497
495 498 $ cat > $TESTTMP/cookies.txt << EOF
496 499 > bad format
497 500 > EOF
498 501
499 502 $ hg --config auth.cookiefile=$TESTTMP/cookies.txt id http://localhost:$HGPORT/
500 503 (error loading cookie file $TESTTMP/cookies.txt: '*/cookies.txt' does not look like a Netscape format cookies file; continuing without cookies) (glob)
501 504 56f9bc90cce6
502 505
503 506 $ killdaemons.py
504 507
505 508 Create dummy authentication handler that looks for cookies. It doesn't do anything
506 509 useful. It just raises an HTTP 500 with details about the Cookie request header.
507 510 We raise HTTP 500 because its message is printed in the abort message.
508 511
509 512 $ cat > cookieauth.py << EOF
510 513 > from mercurial import util
511 514 > from mercurial.hgweb import common
512 515 > def perform_authentication(hgweb, req, op):
513 516 > cookie = req.headers.get('Cookie')
514 517 > if not cookie:
515 518 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'no-cookie')
516 519 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'Cookie: %s' % cookie)
517 520 > def extsetup():
518 521 > common.permhooks.insert(0, perform_authentication)
519 522 > EOF
520 523
521 524 $ hg serve --config extensions.cookieauth=cookieauth.py -R test -p $HGPORT -d --pid-file=pid
522 525 $ cat pid > $DAEMON_PIDS
523 526
524 527 Request without cookie sent should fail due to lack of cookie
525 528
526 529 $ hg id http://localhost:$HGPORT
527 530 abort: HTTP Error 500: no-cookie
528 531 [255]
529 532
530 533 Populate a cookies file
531 534
532 535 $ cat > cookies.txt << EOF
533 536 > # HTTP Cookie File
534 537 > # Expiration is 2030-01-01 at midnight
535 538 > .example.com TRUE / FALSE 1893456000 hgkey examplevalue
536 539 > EOF
537 540
538 541 Should not send a cookie for another domain
539 542
540 543 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
541 544 abort: HTTP Error 500: no-cookie
542 545 [255]
543 546
544 547 Add a cookie entry for our test server and verify it is sent
545 548
546 549 $ cat >> cookies.txt << EOF
547 550 > localhost.local FALSE / FALSE 1893456000 hgkey localhostvalue
548 551 > EOF
549 552
550 553 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
551 554 abort: HTTP Error 500: Cookie: hgkey=localhostvalue
552 555 [255]
@@ -1,640 +1,643 b''
1 1 #testcases sshv1 sshv2
2 2
3 3 #if sshv2
4 4 $ cat >> $HGRCPATH << EOF
5 5 > [experimental]
6 6 > sshpeer.advertise-v2 = true
7 7 > sshserver.support-v2 = true
8 8 > EOF
9 9 #endif
10 10
11 11 This test tries to exercise the ssh functionality with a dummy script
12 12
13 13 $ cat <<EOF >> $HGRCPATH
14 14 > [format]
15 15 > usegeneraldelta=yes
16 16 > EOF
17 17
18 18 creating 'remote' repo
19 19
20 20 $ hg init remote
21 21 $ cd remote
22 22 $ echo this > foo
23 23 $ echo this > fooO
24 24 $ hg ci -A -m "init" foo fooO
25 25
26 26 insert a closed branch (issue4428)
27 27
28 28 $ hg up null
29 29 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
30 30 $ hg branch closed
31 31 marked working directory as branch closed
32 32 (branches are permanent and global, did you want a bookmark?)
33 33 $ hg ci -mc0
34 34 $ hg ci --close-branch -mc1
35 35 $ hg up -q default
36 36
37 37 configure for serving
38 38
39 39 $ cat <<EOF > .hg/hgrc
40 40 > [server]
41 41 > uncompressed = True
42 42 >
43 43 > [hooks]
44 44 > changegroup = sh -c "printenv.py changegroup-in-remote 0 ../dummylog"
45 45 > EOF
46 46 $ cd ..
47 47
48 48 repo not found error
49 49
50 50 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
51 51 remote: abort: repository nonexistent not found!
52 52 abort: no suitable response from remote hg!
53 53 [255]
54 54
55 55 non-existent absolute path
56 56
57 57 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
58 58 remote: abort: repository $TESTTMP/nonexistent not found!
59 59 abort: no suitable response from remote hg!
60 60 [255]
61 61
62 62 clone remote via stream
63 63
64 64 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
65 65 streaming all changes
66 66 4 files to transfer, 602 bytes of data
67 67 transferred 602 bytes in * seconds (*) (glob)
68 68 searching for changes
69 69 no changes found
70 70 updating to branch default
71 71 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 72 $ cd local-stream
73 73 $ hg verify
74 74 checking changesets
75 75 checking manifests
76 76 crosschecking files in changesets and manifests
77 77 checking files
78 78 2 files, 3 changesets, 2 total revisions
79 79 $ hg branches
80 80 default 0:1160648e36ce
81 81 $ cd ..
82 82
83 83 clone bookmarks via stream
84 84
85 85 $ hg -R local-stream book mybook
86 86 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
87 87 streaming all changes
88 88 4 files to transfer, 602 bytes of data
89 89 transferred 602 bytes in * seconds (*) (glob)
90 90 searching for changes
91 91 no changes found
92 92 updating to branch default
93 93 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 94 $ cd stream2
95 95 $ hg book
96 96 mybook 0:1160648e36ce
97 97 $ cd ..
98 98 $ rm -rf local-stream stream2
99 99
100 100 clone remote via pull
101 101
102 102 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
103 103 requesting all changes
104 104 adding changesets
105 105 adding manifests
106 106 adding file changes
107 107 added 3 changesets with 2 changes to 2 files
108 108 new changesets 1160648e36ce:ad076bfb429d
109 109 updating to branch default
110 110 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 111
112 112 verify
113 113
114 114 $ cd local
115 115 $ hg verify
116 116 checking changesets
117 117 checking manifests
118 118 crosschecking files in changesets and manifests
119 119 checking files
120 120 2 files, 3 changesets, 2 total revisions
121 121 $ cat >> .hg/hgrc <<EOF
122 122 > [hooks]
123 123 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
124 124 > EOF
125 125
126 126 empty default pull
127 127
128 128 $ hg paths
129 129 default = ssh://user@dummy/remote
130 130 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
131 131 pulling from ssh://user@dummy/remote
132 132 searching for changes
133 133 no changes found
134 134
135 135 pull from wrong ssh URL
136 136
137 137 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
138 138 pulling from ssh://user@dummy/doesnotexist
139 139 remote: abort: repository doesnotexist not found!
140 140 abort: no suitable response from remote hg!
141 141 [255]
142 142
143 143 local change
144 144
145 145 $ echo bleah > foo
146 146 $ hg ci -m "add"
147 147
148 148 updating rc
149 149
150 150 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
151 151 $ echo "[ui]" >> .hg/hgrc
152 152 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
153 153
154 154 find outgoing
155 155
156 156 $ hg out ssh://user@dummy/remote
157 157 comparing with ssh://user@dummy/remote
158 158 searching for changes
159 159 changeset: 3:a28a9d1a809c
160 160 tag: tip
161 161 parent: 0:1160648e36ce
162 162 user: test
163 163 date: Thu Jan 01 00:00:00 1970 +0000
164 164 summary: add
165 165
166 166
167 167 find incoming on the remote side
168 168
169 169 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
170 170 comparing with ssh://user@dummy/local
171 171 searching for changes
172 172 changeset: 3:a28a9d1a809c
173 173 tag: tip
174 174 parent: 0:1160648e36ce
175 175 user: test
176 176 date: Thu Jan 01 00:00:00 1970 +0000
177 177 summary: add
178 178
179 179
180 180 find incoming on the remote side (using absolute path)
181 181
182 182 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
183 183 comparing with ssh://user@dummy/$TESTTMP/local
184 184 searching for changes
185 185 changeset: 3:a28a9d1a809c
186 186 tag: tip
187 187 parent: 0:1160648e36ce
188 188 user: test
189 189 date: Thu Jan 01 00:00:00 1970 +0000
190 190 summary: add
191 191
192 192
193 193 push
194 194
195 195 $ hg push
196 196 pushing to ssh://user@dummy/remote
197 197 searching for changes
198 198 remote: adding changesets
199 199 remote: adding manifests
200 200 remote: adding file changes
201 201 remote: added 1 changesets with 1 changes to 1 files
202 202 $ cd ../remote
203 203
204 204 check remote tip
205 205
206 206 $ hg tip
207 207 changeset: 3:a28a9d1a809c
208 208 tag: tip
209 209 parent: 0:1160648e36ce
210 210 user: test
211 211 date: Thu Jan 01 00:00:00 1970 +0000
212 212 summary: add
213 213
214 214 $ hg verify
215 215 checking changesets
216 216 checking manifests
217 217 crosschecking files in changesets and manifests
218 218 checking files
219 219 2 files, 4 changesets, 3 total revisions
220 220 $ hg cat -r tip foo
221 221 bleah
222 222 $ echo z > z
223 223 $ hg ci -A -m z z
224 224 created new head
225 225
226 226 test pushkeys and bookmarks
227 227
228 228 $ cd ../local
229 229 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
230 230 bookmarks
231 231 namespaces
232 232 phases
233 233 $ hg book foo -r 0
234 234 $ hg out -B
235 235 comparing with ssh://user@dummy/remote
236 236 searching for changed bookmarks
237 237 foo 1160648e36ce
238 238 $ hg push -B foo
239 239 pushing to ssh://user@dummy/remote
240 240 searching for changes
241 241 no changes found
242 242 exporting bookmark foo
243 243 [1]
244 244 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
245 245 foo 1160648e36cec0054048a7edc4110c6f84fde594
246 246 $ hg book -f foo
247 247 $ hg push --traceback
248 248 pushing to ssh://user@dummy/remote
249 249 searching for changes
250 250 no changes found
251 251 updating bookmark foo
252 252 [1]
253 253 $ hg book -d foo
254 254 $ hg in -B
255 255 comparing with ssh://user@dummy/remote
256 256 searching for changed bookmarks
257 257 foo a28a9d1a809c
258 258 $ hg book -f -r 0 foo
259 259 $ hg pull -B foo
260 260 pulling from ssh://user@dummy/remote
261 261 no changes found
262 262 updating bookmark foo
263 263 $ hg book -d foo
264 264 $ hg push -B foo
265 265 pushing to ssh://user@dummy/remote
266 266 searching for changes
267 267 no changes found
268 268 deleting remote bookmark foo
269 269 [1]
270 270
271 271 a bad, evil hook that prints to stdout
272 272
273 273 $ cat <<EOF > $TESTTMP/badhook
274 274 > import sys
275 275 > sys.stdout.write("KABOOM\n")
276 276 > EOF
277 277
278 278 $ cat <<EOF > $TESTTMP/badpyhook.py
279 279 > import sys
280 280 > def hook(ui, repo, hooktype, **kwargs):
281 281 > sys.stdout.write("KABOOM IN PROCESS\n")
282 282 > EOF
283 283
284 284 $ cat <<EOF >> ../remote/.hg/hgrc
285 285 > [hooks]
286 286 > changegroup.stdout = $PYTHON $TESTTMP/badhook
287 287 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
288 288 > EOF
289 289 $ echo r > r
290 290 $ hg ci -A -m z r
291 291
292 292 push should succeed even though it has an unexpected response
293 293
294 294 $ hg push
295 295 pushing to ssh://user@dummy/remote
296 296 searching for changes
297 297 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
298 298 remote: adding changesets
299 299 remote: adding manifests
300 300 remote: adding file changes
301 301 remote: added 1 changesets with 1 changes to 1 files
302 302 remote: KABOOM
303 303 remote: KABOOM IN PROCESS
304 304 $ hg -R ../remote heads
305 305 changeset: 5:1383141674ec
306 306 tag: tip
307 307 parent: 3:a28a9d1a809c
308 308 user: test
309 309 date: Thu Jan 01 00:00:00 1970 +0000
310 310 summary: z
311 311
312 312 changeset: 4:6c0482d977a3
313 313 parent: 0:1160648e36ce
314 314 user: test
315 315 date: Thu Jan 01 00:00:00 1970 +0000
316 316 summary: z
317 317
318 318
319 319 clone bookmarks
320 320
321 321 $ hg -R ../remote bookmark test
322 322 $ hg -R ../remote bookmarks
323 323 * test 4:6c0482d977a3
324 324 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
325 325 requesting all changes
326 326 adding changesets
327 327 adding manifests
328 328 adding file changes
329 329 added 6 changesets with 5 changes to 4 files (+1 heads)
330 330 new changesets 1160648e36ce:1383141674ec
331 331 updating to branch default
332 332 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
333 333 $ hg -R local-bookmarks bookmarks
334 334 test 4:6c0482d977a3
335 335
336 336 passwords in ssh urls are not supported
337 337 (we use a glob here because different Python versions give different
338 338 results here)
339 339
340 340 $ hg push ssh://user:erroneouspwd@dummy/remote
341 341 pushing to ssh://user:*@dummy/remote (glob)
342 342 abort: password in URL not supported!
343 343 [255]
344 344
345 345 $ cd ..
346 346
347 347 hide outer repo
348 348 $ hg init
349 349
350 350 Test remote paths with spaces (issue2983):
351 351
352 352 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
353 353 $ touch "$TESTTMP/a repo/test"
354 354 $ hg -R 'a repo' commit -A -m "test"
355 355 adding test
356 356 $ hg -R 'a repo' tag tag
357 357 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
358 358 73649e48688a
359 359
360 360 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
361 361 abort: unknown revision 'noNoNO'!
362 362 [255]
363 363
364 364 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
365 365
366 366 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
367 367 destination directory: a repo
368 368 abort: destination 'a repo' is not empty
369 369 [255]
370 370
371 371 Make sure hg is really paranoid in serve --stdio mode. It used to be
372 372 possible to get a debugger REPL by specifying a repo named --debugger.
373 373 $ hg -R --debugger serve --stdio
374 374 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
375 375 [255]
376 376 $ hg -R --config=ui.debugger=yes serve --stdio
377 377 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
378 378 [255]
379 379 Abbreviations of 'serve' also don't work, to avoid shenanigans.
380 380 $ hg -R narf serv --stdio
381 381 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
382 382 [255]
383 383
384 384 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
385 385 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
386 386 parameters:
387 387
388 388 $ cat > ssh.sh << EOF
389 389 > userhost="\$1"
390 390 > SSH_ORIGINAL_COMMAND="\$2"
391 391 > export SSH_ORIGINAL_COMMAND
392 392 > PYTHONPATH="$PYTHONPATH"
393 393 > export PYTHONPATH
394 394 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
395 395 > EOF
396 396
397 397 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
398 398 73649e48688a
399 399
400 400 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
401 401 remote: Illegal repository "$TESTTMP/a'repo"
402 402 abort: no suitable response from remote hg!
403 403 [255]
404 404
405 405 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
406 406 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
407 407 abort: no suitable response from remote hg!
408 408 [255]
409 409
410 410 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" $PYTHON "$TESTDIR/../contrib/hg-ssh"
411 411 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
412 412 [255]
413 413
414 414 Test hg-ssh in read-only mode:
415 415
416 416 $ cat > ssh.sh << EOF
417 417 > userhost="\$1"
418 418 > SSH_ORIGINAL_COMMAND="\$2"
419 419 > export SSH_ORIGINAL_COMMAND
420 420 > PYTHONPATH="$PYTHONPATH"
421 421 > export PYTHONPATH
422 422 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
423 423 > EOF
424 424
425 425 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
426 426 requesting all changes
427 427 adding changesets
428 428 adding manifests
429 429 adding file changes
430 430 added 6 changesets with 5 changes to 4 files (+1 heads)
431 431 new changesets 1160648e36ce:1383141674ec
432 432 updating to branch default
433 433 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
434 434
435 435 $ cd read-only-local
436 436 $ echo "baz" > bar
437 437 $ hg ci -A -m "unpushable commit" bar
438 438 $ hg push --ssh "sh ../ssh.sh"
439 439 pushing to ssh://user@dummy/*/remote (glob)
440 440 searching for changes
441 441 remote: Permission denied
442 442 remote: pretxnopen.hg-ssh hook failed
443 443 abort: push failed on remote
444 444 [255]
445 445
446 446 $ cd ..
447 447
448 448 stderr from remote commands should be printed before stdout from local code (issue4336)
449 449
450 450 $ hg clone remote stderr-ordering
451 451 updating to branch default
452 452 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
453 453 $ cd stderr-ordering
454 454 $ cat >> localwrite.py << EOF
455 455 > from mercurial import exchange, extensions
456 456 >
457 457 > def wrappedpush(orig, repo, *args, **kwargs):
458 458 > res = orig(repo, *args, **kwargs)
459 459 > repo.ui.write('local stdout\n')
460 460 > return res
461 461 >
462 462 > def extsetup(ui):
463 463 > extensions.wrapfunction(exchange, 'push', wrappedpush)
464 464 > EOF
465 465
466 466 $ cat >> .hg/hgrc << EOF
467 467 > [paths]
468 468 > default-push = ssh://user@dummy/remote
469 469 > [ui]
470 470 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
471 471 > [extensions]
472 472 > localwrite = localwrite.py
473 473 > EOF
474 474
475 475 $ echo localwrite > foo
476 476 $ hg commit -m 'testing localwrite'
477 477 $ hg push
478 478 pushing to ssh://user@dummy/remote
479 479 searching for changes
480 480 remote: adding changesets
481 481 remote: adding manifests
482 482 remote: adding file changes
483 483 remote: added 1 changesets with 1 changes to 1 files
484 484 remote: KABOOM
485 485 remote: KABOOM IN PROCESS
486 486 local stdout
487 487
488 488 debug output
489 489
490 490 $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes
491 491 pulling from ssh://user@dummy/remote
492 492 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
493 493 sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
494 494 devel-peer-request: hello
495 495 sending hello command
496 496 devel-peer-request: between
497 497 devel-peer-request: pairs: 81 bytes
498 498 sending between command
499 499 remote: 384 (sshv1 !)
500 500 protocol upgraded to exp-ssh-v2-0001 (sshv2 !)
501 501 remote: capabilities: lookup branchmap pushkey known getbundle unbundlehash batch changegroupsubset streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN
502 502 remote: 1 (sshv1 !)
503 503 query 1; heads
504 devel-peer-request: batched-content
505 devel-peer-request: - heads (0 arguments)
506 devel-peer-request: - known (1 arguments)
504 507 devel-peer-request: batch
505 508 devel-peer-request: cmds: 141 bytes
506 509 sending batch command
507 510 searching for changes
508 511 all remote heads known locally
509 512 no changes found
510 513 devel-peer-request: getbundle
511 514 devel-peer-request: bookmarks: 1 bytes
512 515 devel-peer-request: bundlecaps: 247 bytes
513 516 devel-peer-request: cg: 1 bytes
514 517 devel-peer-request: common: 122 bytes
515 518 devel-peer-request: heads: 122 bytes
516 519 devel-peer-request: listkeys: 9 bytes
517 520 devel-peer-request: phases: 1 bytes
518 521 sending getbundle command
519 522 bundle2-input-bundle: with-transaction
520 523 bundle2-input-part: "bookmarks" supported
521 524 bundle2-input-part: total payload size 26
522 525 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
523 526 bundle2-input-part: total payload size 45
524 527 bundle2-input-part: "phase-heads" supported
525 528 bundle2-input-part: total payload size 72
526 529 bundle2-input-bundle: 2 parts total
527 530 checking for updated bookmarks
528 531
529 532 $ cd ..
530 533
531 534 $ cat dummylog
532 535 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
533 536 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
534 537 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
535 538 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
536 539 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
537 540 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
538 541 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
539 542 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
540 543 Got arguments 1:user@dummy 2:hg -R local serve --stdio
541 544 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
542 545 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
543 546 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
544 547 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
545 548 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
546 549 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
547 550 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
548 551 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
549 552 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
550 553 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
551 554 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
552 555 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
553 556 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
554 557 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
555 558 Got arguments 1:user@dummy 2:hg init 'a repo'
556 559 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
557 560 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
558 561 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
559 562 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
560 563 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
561 564 changegroup-in-remote hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:ssh:$LOCALIP
562 565 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
563 566
564 567 remote hook failure is attributed to remote
565 568
566 569 $ cat > $TESTTMP/failhook << EOF
567 570 > def hook(ui, repo, **kwargs):
568 571 > ui.write('hook failure!\n')
569 572 > ui.flush()
570 573 > return 1
571 574 > EOF
572 575
573 576 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
574 577
575 578 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
576 579 $ cd hookout
577 580 $ touch hookfailure
578 581 $ hg -q commit -A -m 'remote hook failure'
579 582 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
580 583 pushing to ssh://user@dummy/remote
581 584 searching for changes
582 585 remote: adding changesets
583 586 remote: adding manifests
584 587 remote: adding file changes
585 588 remote: added 1 changesets with 1 changes to 1 files
586 589 remote: hook failure!
587 590 remote: transaction abort!
588 591 remote: rollback completed
589 592 remote: pretxnchangegroup.fail hook failed
590 593 abort: push failed on remote
591 594 [255]
592 595
593 596 abort during pull is properly reported as such
594 597
595 598 $ echo morefoo >> ../remote/foo
596 599 $ hg -R ../remote commit --message "more foo to be pulled"
597 600 $ cat >> ../remote/.hg/hgrc << EOF
598 601 > [extensions]
599 602 > crash = ${TESTDIR}/crashgetbundler.py
600 603 > EOF
601 604 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
602 605 pulling from ssh://user@dummy/remote
603 606 searching for changes
604 607 remote: abort: this is an exercise
605 608 abort: pull failed on remote
606 609 [255]
607 610
608 611 abort with no error hint when there is a ssh problem when pulling
609 612
610 613 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
611 614 pulling from ssh://brokenrepository/
612 615 abort: no suitable response from remote hg!
613 616 [255]
614 617
615 618 abort with configured error hint when there is a ssh problem when pulling
616 619
617 620 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
618 621 > --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html"
619 622 pulling from ssh://brokenrepository/
620 623 abort: no suitable response from remote hg!
621 624 (Please see http://company/internalwiki/ssh.html)
622 625 [255]
623 626
624 627 test that custom environment is passed down to ssh executable
625 628 $ cat >>dumpenv <<EOF
626 629 > #! /bin/sh
627 630 > echo \$VAR >&2
628 631 > EOF
629 632 $ chmod +x dumpenv
630 633 $ hg pull ssh://something --config ui.ssh="sh dumpenv"
631 634 pulling from ssh://something/
632 635 remote:
633 636 abort: no suitable response from remote hg!
634 637 [255]
635 638 $ hg pull ssh://something --config ui.ssh="sh dumpenv" --config sshenv.VAR=17
636 639 pulling from ssh://something/
637 640 remote: 17
638 641 abort: no suitable response from remote hg!
639 642 [255]
640 643
General Comments 0
You need to be logged in to leave comments. Login now