##// END OF EJS Templates
bundle2: allow bundle2 for pulling over the wire...
Pierre-Yves David -
r21069:0a9cae23 default
parent child Browse files
Show More
@@ -1,785 +1,790 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 import urllib, tempfile, os, sys
9 9 from i18n import _
10 10 from node import bin, hex
11 import changegroup as changegroupmod
11 import changegroup as changegroupmod, bundle2
12 12 import peer, error, encoding, util, store, exchange
13 13
14 14
15 15 class abstractserverproto(object):
16 16 """abstract class that summarizes the protocol API
17 17
18 18 Used as reference and documentation.
19 19 """
20 20
21 21 def getargs(self, args):
22 22 """return the value for arguments in <args>
23 23
24 24 returns a list of values (same order as <args>)"""
25 25 raise NotImplementedError()
26 26
27 27 def getfile(self, fp):
28 28 """write the whole content of a file into a file like object
29 29
30 30 The file is in the form::
31 31
32 32 (<chunk-size>\n<chunk>)+0\n
33 33
34 34 chunk size is the ascii version of the int.
35 35 """
36 36 raise NotImplementedError()
37 37
38 38 def redirect(self):
39 39 """may setup interception for stdout and stderr
40 40
41 41 See also the `restore` method."""
42 42 raise NotImplementedError()
43 43
44 44 # If the `redirect` function does install interception, the `restore`
45 45 # function MUST be defined. If interception is not used, this function
46 46 # MUST NOT be defined.
47 47 #
48 48 # left commented here on purpose
49 49 #
50 50 #def restore(self):
51 51 # """reinstall previous stdout and stderr and return intercepted stdout
52 52 # """
53 53 # raise NotImplementedError()
54 54
55 55 def groupchunks(self, cg):
56 56 """return 4096 chunks from a changegroup object
57 57
58 58 Some protocols may have compressed the contents."""
59 59 raise NotImplementedError()
60 60
61 61 # abstract batching support
62 62
63 63 class future(object):
64 64 '''placeholder for a value to be set later'''
65 65 def set(self, value):
66 66 if util.safehasattr(self, 'value'):
67 67 raise error.RepoError("future is already set")
68 68 self.value = value
69 69
70 70 class batcher(object):
71 71 '''base class for batches of commands submittable in a single request
72 72
73 73 All methods invoked on instances of this class are simply queued and
74 74 return a a future for the result. Once you call submit(), all the queued
75 75 calls are performed and the results set in their respective futures.
76 76 '''
77 77 def __init__(self):
78 78 self.calls = []
79 79 def __getattr__(self, name):
80 80 def call(*args, **opts):
81 81 resref = future()
82 82 self.calls.append((name, args, opts, resref,))
83 83 return resref
84 84 return call
85 85 def submit(self):
86 86 pass
87 87
88 88 class localbatch(batcher):
89 89 '''performs the queued calls directly'''
90 90 def __init__(self, local):
91 91 batcher.__init__(self)
92 92 self.local = local
93 93 def submit(self):
94 94 for name, args, opts, resref in self.calls:
95 95 resref.set(getattr(self.local, name)(*args, **opts))
96 96
97 97 class remotebatch(batcher):
98 98 '''batches the queued calls; uses as few roundtrips as possible'''
99 99 def __init__(self, remote):
100 100 '''remote must support _submitbatch(encbatch) and
101 101 _submitone(op, encargs)'''
102 102 batcher.__init__(self)
103 103 self.remote = remote
104 104 def submit(self):
105 105 req, rsp = [], []
106 106 for name, args, opts, resref in self.calls:
107 107 mtd = getattr(self.remote, name)
108 108 batchablefn = getattr(mtd, 'batchable', None)
109 109 if batchablefn is not None:
110 110 batchable = batchablefn(mtd.im_self, *args, **opts)
111 111 encargsorres, encresref = batchable.next()
112 112 if encresref:
113 113 req.append((name, encargsorres,))
114 114 rsp.append((batchable, encresref, resref,))
115 115 else:
116 116 resref.set(encargsorres)
117 117 else:
118 118 if req:
119 119 self._submitreq(req, rsp)
120 120 req, rsp = [], []
121 121 resref.set(mtd(*args, **opts))
122 122 if req:
123 123 self._submitreq(req, rsp)
124 124 def _submitreq(self, req, rsp):
125 125 encresults = self.remote._submitbatch(req)
126 126 for encres, r in zip(encresults, rsp):
127 127 batchable, encresref, resref = r
128 128 encresref.set(encres)
129 129 resref.set(batchable.next())
130 130
131 131 def batchable(f):
132 132 '''annotation for batchable methods
133 133
134 134 Such methods must implement a coroutine as follows:
135 135
136 136 @batchable
137 137 def sample(self, one, two=None):
138 138 # Handle locally computable results first:
139 139 if not one:
140 140 yield "a local result", None
141 141 # Build list of encoded arguments suitable for your wire protocol:
142 142 encargs = [('one', encode(one),), ('two', encode(two),)]
143 143 # Create future for injection of encoded result:
144 144 encresref = future()
145 145 # Return encoded arguments and future:
146 146 yield encargs, encresref
147 147 # Assuming the future to be filled with the result from the batched
148 148 # request now. Decode it:
149 149 yield decode(encresref.value)
150 150
151 151 The decorator returns a function which wraps this coroutine as a plain
152 152 method, but adds the original method as an attribute called "batchable",
153 153 which is used by remotebatch to split the call into separate encoding and
154 154 decoding phases.
155 155 '''
156 156 def plain(*args, **opts):
157 157 batchable = f(*args, **opts)
158 158 encargsorres, encresref = batchable.next()
159 159 if not encresref:
160 160 return encargsorres # a local result in this case
161 161 self = args[0]
162 162 encresref.set(self._submitone(f.func_name, encargsorres))
163 163 return batchable.next()
164 164 setattr(plain, 'batchable', f)
165 165 return plain
166 166
167 167 # list of nodes encoding / decoding
168 168
169 169 def decodelist(l, sep=' '):
170 170 if l:
171 171 return map(bin, l.split(sep))
172 172 return []
173 173
174 174 def encodelist(l, sep=' '):
175 175 return sep.join(map(hex, l))
176 176
177 177 # batched call argument encoding
178 178
179 179 def escapearg(plain):
180 180 return (plain
181 181 .replace(':', '::')
182 182 .replace(',', ':,')
183 183 .replace(';', ':;')
184 184 .replace('=', ':='))
185 185
186 186 def unescapearg(escaped):
187 187 return (escaped
188 188 .replace(':=', '=')
189 189 .replace(':;', ';')
190 190 .replace(':,', ',')
191 191 .replace('::', ':'))
192 192
193 193 # client side
194 194
195 195 class wirepeer(peer.peerrepository):
196 196
197 197 def batch(self):
198 198 return remotebatch(self)
199 199 def _submitbatch(self, req):
200 200 cmds = []
201 201 for op, argsdict in req:
202 202 args = ','.join('%s=%s' % p for p in argsdict.iteritems())
203 203 cmds.append('%s %s' % (op, args))
204 204 rsp = self._call("batch", cmds=';'.join(cmds))
205 205 return rsp.split(';')
206 206 def _submitone(self, op, args):
207 207 return self._call(op, **args)
208 208
209 209 @batchable
210 210 def lookup(self, key):
211 211 self.requirecap('lookup', _('look up remote revision'))
212 212 f = future()
213 213 yield {'key': encoding.fromlocal(key)}, f
214 214 d = f.value
215 215 success, data = d[:-1].split(" ", 1)
216 216 if int(success):
217 217 yield bin(data)
218 218 self._abort(error.RepoError(data))
219 219
220 220 @batchable
221 221 def heads(self):
222 222 f = future()
223 223 yield {}, f
224 224 d = f.value
225 225 try:
226 226 yield decodelist(d[:-1])
227 227 except ValueError:
228 228 self._abort(error.ResponseError(_("unexpected response:"), d))
229 229
230 230 @batchable
231 231 def known(self, nodes):
232 232 f = future()
233 233 yield {'nodes': encodelist(nodes)}, f
234 234 d = f.value
235 235 try:
236 236 yield [bool(int(f)) for f in d]
237 237 except ValueError:
238 238 self._abort(error.ResponseError(_("unexpected response:"), d))
239 239
240 240 @batchable
241 241 def branchmap(self):
242 242 f = future()
243 243 yield {}, f
244 244 d = f.value
245 245 try:
246 246 branchmap = {}
247 247 for branchpart in d.splitlines():
248 248 branchname, branchheads = branchpart.split(' ', 1)
249 249 branchname = encoding.tolocal(urllib.unquote(branchname))
250 250 branchheads = decodelist(branchheads)
251 251 branchmap[branchname] = branchheads
252 252 yield branchmap
253 253 except TypeError:
254 254 self._abort(error.ResponseError(_("unexpected response:"), d))
255 255
256 256 def branches(self, nodes):
257 257 n = encodelist(nodes)
258 258 d = self._call("branches", nodes=n)
259 259 try:
260 260 br = [tuple(decodelist(b)) for b in d.splitlines()]
261 261 return br
262 262 except ValueError:
263 263 self._abort(error.ResponseError(_("unexpected response:"), d))
264 264
265 265 def between(self, pairs):
266 266 batch = 8 # avoid giant requests
267 267 r = []
268 268 for i in xrange(0, len(pairs), batch):
269 269 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
270 270 d = self._call("between", pairs=n)
271 271 try:
272 272 r.extend(l and decodelist(l) or [] for l in d.splitlines())
273 273 except ValueError:
274 274 self._abort(error.ResponseError(_("unexpected response:"), d))
275 275 return r
276 276
277 277 @batchable
278 278 def pushkey(self, namespace, key, old, new):
279 279 if not self.capable('pushkey'):
280 280 yield False, None
281 281 f = future()
282 282 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
283 283 yield {'namespace': encoding.fromlocal(namespace),
284 284 'key': encoding.fromlocal(key),
285 285 'old': encoding.fromlocal(old),
286 286 'new': encoding.fromlocal(new)}, f
287 287 d = f.value
288 288 d, output = d.split('\n', 1)
289 289 try:
290 290 d = bool(int(d))
291 291 except ValueError:
292 292 raise error.ResponseError(
293 293 _('push failed (unexpected response):'), d)
294 294 for l in output.splitlines(True):
295 295 self.ui.status(_('remote: '), l)
296 296 yield d
297 297
298 298 @batchable
299 299 def listkeys(self, namespace):
300 300 if not self.capable('pushkey'):
301 301 yield {}, None
302 302 f = future()
303 303 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
304 304 yield {'namespace': encoding.fromlocal(namespace)}, f
305 305 d = f.value
306 306 r = {}
307 307 for l in d.splitlines():
308 308 k, v = l.split('\t')
309 309 r[encoding.tolocal(k)] = encoding.tolocal(v)
310 310 yield r
311 311
312 312 def stream_out(self):
313 313 return self._callstream('stream_out')
314 314
315 315 def changegroup(self, nodes, kind):
316 316 n = encodelist(nodes)
317 317 f = self._callcompressable("changegroup", roots=n)
318 318 return changegroupmod.unbundle10(f, 'UN')
319 319
320 320 def changegroupsubset(self, bases, heads, kind):
321 321 self.requirecap('changegroupsubset', _('look up remote changes'))
322 322 bases = encodelist(bases)
323 323 heads = encodelist(heads)
324 324 f = self._callcompressable("changegroupsubset",
325 325 bases=bases, heads=heads)
326 326 return changegroupmod.unbundle10(f, 'UN')
327 327
328 328 def getbundle(self, source, heads=None, common=None, bundlecaps=None):
329 329 self.requirecap('getbundle', _('look up remote changes'))
330 330 opts = {}
331 331 if heads is not None:
332 332 opts['heads'] = encodelist(heads)
333 333 if common is not None:
334 334 opts['common'] = encodelist(common)
335 335 if bundlecaps is not None:
336 336 opts['bundlecaps'] = ','.join(bundlecaps)
337 337 f = self._callcompressable("getbundle", **opts)
338 return changegroupmod.unbundle10(f, 'UN')
338 if bundlecaps is not None and 'HG20' in bundlecaps:
339 return bundle2.unbundle20(self.ui, f)
340 else:
341 return changegroupmod.unbundle10(f, 'UN')
339 342
340 343 def unbundle(self, cg, heads, source):
341 344 '''Send cg (a readable file-like object representing the
342 345 changegroup to push, typically a chunkbuffer object) to the
343 346 remote server as a bundle. Return an integer indicating the
344 347 result of the push (see localrepository.addchangegroup()).'''
345 348
346 349 if heads != ['force'] and self.capable('unbundlehash'):
347 350 heads = encodelist(['hashed',
348 351 util.sha1(''.join(sorted(heads))).digest()])
349 352 else:
350 353 heads = encodelist(heads)
351 354
352 355 ret, output = self._callpush("unbundle", cg, heads=heads)
353 356 if ret == "":
354 357 raise error.ResponseError(
355 358 _('push failed:'), output)
356 359 try:
357 360 ret = int(ret)
358 361 except ValueError:
359 362 raise error.ResponseError(
360 363 _('push failed (unexpected response):'), ret)
361 364
362 365 for l in output.splitlines(True):
363 366 self.ui.status(_('remote: '), l)
364 367 return ret
365 368
366 369 def debugwireargs(self, one, two, three=None, four=None, five=None):
367 370 # don't pass optional arguments left at their default value
368 371 opts = {}
369 372 if three is not None:
370 373 opts['three'] = three
371 374 if four is not None:
372 375 opts['four'] = four
373 376 return self._call('debugwireargs', one=one, two=two, **opts)
374 377
375 378 def _call(self, cmd, **args):
376 379 """execute <cmd> on the server
377 380
378 381 The command is expected to return a simple string.
379 382
380 383 returns the server reply as a string."""
381 384 raise NotImplementedError()
382 385
383 386 def _callstream(self, cmd, **args):
384 387 """execute <cmd> on the server
385 388
386 389 The command is expected to return a stream.
387 390
388 391 returns the server reply as a file like object."""
389 392 raise NotImplementedError()
390 393
391 394 def _callcompressable(self, cmd, **args):
392 395 """execute <cmd> on the server
393 396
394 397 The command is expected to return a stream.
395 398
396 399 The stream may have been compressed in some implementations. This
397 400 function takes care of the decompression. This is the only difference
398 401 with _callstream.
399 402
400 403 returns the server reply as a file like object.
401 404 """
402 405 raise NotImplementedError()
403 406
404 407 def _callpush(self, cmd, fp, **args):
405 408 """execute a <cmd> on server
406 409
407 410 The command is expected to be related to a push. Push has a special
408 411 return method.
409 412
410 413 returns the server reply as a (ret, output) tuple. ret is either
411 414 empty (error) or a stringified int.
412 415 """
413 416 raise NotImplementedError()
414 417
415 418 def _abort(self, exception):
416 419 """clearly abort the wire protocol connection and raise the exception
417 420 """
418 421 raise NotImplementedError()
419 422
420 423 # server side
421 424
422 425 # wire protocol command can either return a string or one of these classes.
423 426 class streamres(object):
424 427 """wireproto reply: binary stream
425 428
426 429 The call was successful and the result is a stream.
427 430 Iterate on the `self.gen` attribute to retrieve chunks.
428 431 """
429 432 def __init__(self, gen):
430 433 self.gen = gen
431 434
432 435 class pushres(object):
433 436 """wireproto reply: success with simple integer return
434 437
435 438 The call was successful and returned an integer contained in `self.res`.
436 439 """
437 440 def __init__(self, res):
438 441 self.res = res
439 442
440 443 class pusherr(object):
441 444 """wireproto reply: failure
442 445
443 446 The call failed. The `self.res` attribute contains the error message.
444 447 """
445 448 def __init__(self, res):
446 449 self.res = res
447 450
448 451 class ooberror(object):
449 452 """wireproto reply: failure of a batch of operation
450 453
451 454 Something failed during a batch call. The error message is stored in
452 455 `self.message`.
453 456 """
454 457 def __init__(self, message):
455 458 self.message = message
456 459
457 460 def dispatch(repo, proto, command):
458 461 repo = repo.filtered("served")
459 462 func, spec = commands[command]
460 463 args = proto.getargs(spec)
461 464 return func(repo, proto, *args)
462 465
463 466 def options(cmd, keys, others):
464 467 opts = {}
465 468 for k in keys:
466 469 if k in others:
467 470 opts[k] = others[k]
468 471 del others[k]
469 472 if others:
470 473 sys.stderr.write("abort: %s got unexpected arguments %s\n"
471 474 % (cmd, ",".join(others)))
472 475 return opts
473 476
474 477 # list of commands
475 478 commands = {}
476 479
477 480 def wireprotocommand(name, args=''):
478 481 """decorator for wire protocol command"""
479 482 def register(func):
480 483 commands[name] = (func, args)
481 484 return func
482 485 return register
483 486
484 487 @wireprotocommand('batch', 'cmds *')
485 488 def batch(repo, proto, cmds, others):
486 489 repo = repo.filtered("served")
487 490 res = []
488 491 for pair in cmds.split(';'):
489 492 op, args = pair.split(' ', 1)
490 493 vals = {}
491 494 for a in args.split(','):
492 495 if a:
493 496 n, v = a.split('=')
494 497 vals[n] = unescapearg(v)
495 498 func, spec = commands[op]
496 499 if spec:
497 500 keys = spec.split()
498 501 data = {}
499 502 for k in keys:
500 503 if k == '*':
501 504 star = {}
502 505 for key in vals.keys():
503 506 if key not in keys:
504 507 star[key] = vals[key]
505 508 data['*'] = star
506 509 else:
507 510 data[k] = vals[k]
508 511 result = func(repo, proto, *[data[k] for k in keys])
509 512 else:
510 513 result = func(repo, proto)
511 514 if isinstance(result, ooberror):
512 515 return result
513 516 res.append(escapearg(result))
514 517 return ';'.join(res)
515 518
516 519 @wireprotocommand('between', 'pairs')
517 520 def between(repo, proto, pairs):
518 521 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
519 522 r = []
520 523 for b in repo.between(pairs):
521 524 r.append(encodelist(b) + "\n")
522 525 return "".join(r)
523 526
524 527 @wireprotocommand('branchmap')
525 528 def branchmap(repo, proto):
526 529 branchmap = repo.branchmap()
527 530 heads = []
528 531 for branch, nodes in branchmap.iteritems():
529 532 branchname = urllib.quote(encoding.fromlocal(branch))
530 533 branchnodes = encodelist(nodes)
531 534 heads.append('%s %s' % (branchname, branchnodes))
532 535 return '\n'.join(heads)
533 536
534 537 @wireprotocommand('branches', 'nodes')
535 538 def branches(repo, proto, nodes):
536 539 nodes = decodelist(nodes)
537 540 r = []
538 541 for b in repo.branches(nodes):
539 542 r.append(encodelist(b) + "\n")
540 543 return "".join(r)
541 544
542 545
543 546 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
544 547 'known', 'getbundle', 'unbundlehash', 'batch']
545 548
546 549 def _capabilities(repo, proto):
547 550 """return a list of capabilities for a repo
548 551
549 552 This function exists to allow extensions to easily wrap capabilities
550 553 computation
551 554
552 555 - returns a lists: easy to alter
553 556 - change done here will be propagated to both `capabilities` and `hello`
554 557 command without any other action needed.
555 558 """
556 559 # copy to prevent modification of the global list
557 560 caps = list(wireprotocaps)
558 561 if _allowstream(repo.ui):
559 562 if repo.ui.configbool('server', 'preferuncompressed', False):
560 563 caps.append('stream-preferred')
561 564 requiredformats = repo.requirements & repo.supportedformats
562 565 # if our local revlogs are just revlogv1, add 'stream' cap
563 566 if not requiredformats - set(('revlogv1',)):
564 567 caps.append('stream')
565 568 # otherwise, add 'streamreqs' detailing our local revlog format
566 569 else:
567 570 caps.append('streamreqs=%s' % ','.join(requiredformats))
571 if repo.ui.configbool('server', 'bundle2', False):
572 caps.append('bundle2')
568 573 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
569 574 caps.append('httpheader=1024')
570 575 return caps
571 576
572 577 # If you are writing an extension and consider wrapping this function. Wrap
573 578 # `_capabilities` instead.
574 579 @wireprotocommand('capabilities')
575 580 def capabilities(repo, proto):
576 581 return ' '.join(_capabilities(repo, proto))
577 582
578 583 @wireprotocommand('changegroup', 'roots')
579 584 def changegroup(repo, proto, roots):
580 585 nodes = decodelist(roots)
581 586 cg = changegroupmod.changegroup(repo, nodes, 'serve')
582 587 return streamres(proto.groupchunks(cg))
583 588
584 589 @wireprotocommand('changegroupsubset', 'bases heads')
585 590 def changegroupsubset(repo, proto, bases, heads):
586 591 bases = decodelist(bases)
587 592 heads = decodelist(heads)
588 593 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
589 594 return streamres(proto.groupchunks(cg))
590 595
591 596 @wireprotocommand('debugwireargs', 'one two *')
592 597 def debugwireargs(repo, proto, one, two, others):
593 598 # only accept optional args from the known set
594 599 opts = options('debugwireargs', ['three', 'four'], others)
595 600 return repo.debugwireargs(one, two, **opts)
596 601
597 602 @wireprotocommand('getbundle', '*')
598 603 def getbundle(repo, proto, others):
599 604 opts = options('getbundle', ['heads', 'common', 'bundlecaps'], others)
600 605 for k, v in opts.iteritems():
601 606 if k in ('heads', 'common'):
602 607 opts[k] = decodelist(v)
603 608 elif k == 'bundlecaps':
604 609 opts[k] = set(v.split(','))
605 cg = changegroupmod.getbundle(repo, 'serve', **opts)
610 cg = exchange.getbundle(repo, 'serve', **opts)
606 611 return streamres(proto.groupchunks(cg))
607 612
608 613 @wireprotocommand('heads')
609 614 def heads(repo, proto):
610 615 h = repo.heads()
611 616 return encodelist(h) + "\n"
612 617
613 618 @wireprotocommand('hello')
614 619 def hello(repo, proto):
615 620 '''the hello command returns a set of lines describing various
616 621 interesting things about the server, in an RFC822-like format.
617 622 Currently the only one defined is "capabilities", which
618 623 consists of a line in the form:
619 624
620 625 capabilities: space separated list of tokens
621 626 '''
622 627 return "capabilities: %s\n" % (capabilities(repo, proto))
623 628
624 629 @wireprotocommand('listkeys', 'namespace')
625 630 def listkeys(repo, proto, namespace):
626 631 d = repo.listkeys(encoding.tolocal(namespace)).items()
627 632 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
628 633 for k, v in d])
629 634 return t
630 635
631 636 @wireprotocommand('lookup', 'key')
632 637 def lookup(repo, proto, key):
633 638 try:
634 639 k = encoding.tolocal(key)
635 640 c = repo[k]
636 641 r = c.hex()
637 642 success = 1
638 643 except Exception, inst:
639 644 r = str(inst)
640 645 success = 0
641 646 return "%s %s\n" % (success, r)
642 647
643 648 @wireprotocommand('known', 'nodes *')
644 649 def known(repo, proto, nodes, others):
645 650 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
646 651
647 652 @wireprotocommand('pushkey', 'namespace key old new')
648 653 def pushkey(repo, proto, namespace, key, old, new):
649 654 # compatibility with pre-1.8 clients which were accidentally
650 655 # sending raw binary nodes rather than utf-8-encoded hex
651 656 if len(new) == 20 and new.encode('string-escape') != new:
652 657 # looks like it could be a binary node
653 658 try:
654 659 new.decode('utf-8')
655 660 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
656 661 except UnicodeDecodeError:
657 662 pass # binary, leave unmodified
658 663 else:
659 664 new = encoding.tolocal(new) # normal path
660 665
661 666 if util.safehasattr(proto, 'restore'):
662 667
663 668 proto.redirect()
664 669
665 670 try:
666 671 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
667 672 encoding.tolocal(old), new) or False
668 673 except util.Abort:
669 674 r = False
670 675
671 676 output = proto.restore()
672 677
673 678 return '%s\n%s' % (int(r), output)
674 679
675 680 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
676 681 encoding.tolocal(old), new)
677 682 return '%s\n' % int(r)
678 683
679 684 def _allowstream(ui):
680 685 return ui.configbool('server', 'uncompressed', True, untrusted=True)
681 686
682 687 def _walkstreamfiles(repo):
683 688 # this is it's own function so extensions can override it
684 689 return repo.store.walk()
685 690
686 691 @wireprotocommand('stream_out')
687 692 def stream(repo, proto):
688 693 '''If the server supports streaming clone, it advertises the "stream"
689 694 capability with a value representing the version and flags of the repo
690 695 it is serving. Client checks to see if it understands the format.
691 696
692 697 The format is simple: the server writes out a line with the amount
693 698 of files, then the total amount of bytes to be transferred (separated
694 699 by a space). Then, for each file, the server first writes the filename
695 700 and file size (separated by the null character), then the file contents.
696 701 '''
697 702
698 703 if not _allowstream(repo.ui):
699 704 return '1\n'
700 705
701 706 entries = []
702 707 total_bytes = 0
703 708 try:
704 709 # get consistent snapshot of repo, lock during scan
705 710 lock = repo.lock()
706 711 try:
707 712 repo.ui.debug('scanning\n')
708 713 for name, ename, size in _walkstreamfiles(repo):
709 714 if size:
710 715 entries.append((name, size))
711 716 total_bytes += size
712 717 finally:
713 718 lock.release()
714 719 except error.LockError:
715 720 return '2\n' # error: 2
716 721
717 722 def streamer(repo, entries, total):
718 723 '''stream out all metadata files in repository.'''
719 724 yield '0\n' # success
720 725 repo.ui.debug('%d files, %d bytes to transfer\n' %
721 726 (len(entries), total_bytes))
722 727 yield '%d %d\n' % (len(entries), total_bytes)
723 728
724 729 sopener = repo.sopener
725 730 oldaudit = sopener.mustaudit
726 731 debugflag = repo.ui.debugflag
727 732 sopener.mustaudit = False
728 733
729 734 try:
730 735 for name, size in entries:
731 736 if debugflag:
732 737 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
733 738 # partially encode name over the wire for backwards compat
734 739 yield '%s\0%d\n' % (store.encodedir(name), size)
735 740 if size <= 65536:
736 741 fp = sopener(name)
737 742 try:
738 743 data = fp.read(size)
739 744 finally:
740 745 fp.close()
741 746 yield data
742 747 else:
743 748 for chunk in util.filechunkiter(sopener(name), limit=size):
744 749 yield chunk
745 750 # replace with "finally:" when support for python 2.4 has been dropped
746 751 except Exception:
747 752 sopener.mustaudit = oldaudit
748 753 raise
749 754 sopener.mustaudit = oldaudit
750 755
751 756 return streamres(streamer(repo, entries, total_bytes))
752 757
753 758 @wireprotocommand('unbundle', 'heads')
754 759 def unbundle(repo, proto, heads):
755 760 their_heads = decodelist(heads)
756 761
757 762 try:
758 763 proto.redirect()
759 764
760 765 exchange.check_heads(repo, their_heads, 'preparing changes')
761 766
762 767 # write bundle data to temporary file because it can be big
763 768 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
764 769 fp = os.fdopen(fd, 'wb+')
765 770 r = 0
766 771 try:
767 772 proto.getfile(fp)
768 773 fp.seek(0)
769 774 gen = exchange.readbundle(repo.ui, fp, None)
770 775 r = exchange.unbundle(repo, gen, their_heads, 'serve',
771 776 proto._client())
772 777 return pushres(r)
773 778
774 779 finally:
775 780 fp.close()
776 781 os.unlink(tempname)
777 782 except util.Abort, inst:
778 783 # The old code we moved used sys.stderr directly.
779 784 # We did not change it to minimise code change.
780 785 # This need to be moved to something proper.
781 786 # Feel free to do it.
782 787 sys.stderr.write("abort: %s\n" % inst)
783 788 return pushres(0)
784 789 except exchange.PushRaced, exc:
785 790 return pusherr(str(exc))
@@ -1,684 +1,714 b''
1 1
2 2 Create an extension to test bundle2 API
3 3
4 4 $ cat > bundle2.py << EOF
5 5 > """A small extension to test bundle2 implementation
6 6 >
7 7 > Current bundle2 implementation is far too limited to be used in any core
8 8 > code. We still need to be able to test it while it grow up.
9 9 > """
10 10 >
11 11 > import sys
12 12 > from mercurial import cmdutil
13 13 > from mercurial import util
14 14 > from mercurial import bundle2
15 15 > from mercurial import scmutil
16 16 > from mercurial import discovery
17 17 > from mercurial import changegroup
18 18 > cmdtable = {}
19 19 > command = cmdutil.command(cmdtable)
20 20 >
21 21 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
22 22 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
23 23 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
24 24 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
25 25 >
26 26 > @bundle2.parthandler('test:song')
27 27 > def songhandler(op, part):
28 28 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
29 29 > op.ui.write('The choir starts singing:\n')
30 30 > verses = 0
31 31 > for line in part.read().split('\n'):
32 32 > op.ui.write(' %s\n' % line)
33 33 > verses += 1
34 34 > op.records.add('song', {'verses': verses})
35 35 >
36 36 > @bundle2.parthandler('test:ping')
37 37 > def pinghandler(op, part):
38 38 > op.ui.write('received ping request (id %i)\n' % part.id)
39 39 > if op.reply is not None:
40 40 > rpart = bundle2.bundlepart('test:pong',
41 41 > [('in-reply-to', str(part.id))])
42 42 > op.reply.addpart(rpart)
43 43 >
44 44 > @command('bundle2',
45 45 > [('', 'param', [], 'stream level parameter'),
46 46 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
47 47 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
48 48 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
49 49 > '[OUTPUTFILE]')
50 50 > def cmdbundle2(ui, repo, path=None, **opts):
51 51 > """write a bundle2 container on standard ouput"""
52 52 > bundler = bundle2.bundle20(ui)
53 53 > for p in opts['param']:
54 54 > p = p.split('=', 1)
55 55 > try:
56 56 > bundler.addparam(*p)
57 57 > except ValueError, exc:
58 58 > raise util.Abort('%s' % exc)
59 59 >
60 60 > revs = opts['rev']
61 61 > if 'rev' in opts:
62 62 > revs = scmutil.revrange(repo, opts['rev'])
63 63 > if revs:
64 64 > # very crude version of a changegroup part creation
65 65 > bundled = repo.revs('%ld::%ld', revs, revs)
66 66 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
67 67 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
68 68 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
69 69 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
70 70 > part = bundle2.bundlepart('changegroup', data=cg.getchunks())
71 71 > bundler.addpart(part)
72 72 >
73 73 > if opts['parts']:
74 74 > part = bundle2.bundlepart('test:empty')
75 75 > bundler.addpart(part)
76 76 > # add a second one to make sure we handle multiple parts
77 77 > part = bundle2.bundlepart('test:empty')
78 78 > bundler.addpart(part)
79 79 > part = bundle2.bundlepart('test:song', data=ELEPHANTSSONG)
80 80 > bundler.addpart(part)
81 81 > part = bundle2.bundlepart('test:math',
82 82 > [('pi', '3.14'), ('e', '2.72')],
83 83 > [('cooking', 'raw')],
84 84 > '42')
85 85 > bundler.addpart(part)
86 86 > if opts['unknown']:
87 87 > part = bundle2.bundlepart('test:UNKNOWN',
88 88 > data='some random content')
89 89 > bundler.addpart(part)
90 90 > if opts['parts']:
91 91 > part = bundle2.bundlepart('test:ping')
92 92 > bundler.addpart(part)
93 93 >
94 94 > if path is None:
95 95 > file = sys.stdout
96 96 > else:
97 97 > file = open(path, 'w')
98 98 >
99 99 > for chunk in bundler.getchunks():
100 100 > file.write(chunk)
101 101 >
102 102 > @command('unbundle2', [], '')
103 103 > def cmdunbundle2(ui, repo, replypath=None):
104 104 > """process a bundle2 stream from stdin on the current repo"""
105 105 > try:
106 106 > tr = None
107 107 > lock = repo.lock()
108 108 > tr = repo.transaction('processbundle')
109 109 > try:
110 110 > unbundler = bundle2.unbundle20(ui, sys.stdin)
111 111 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
112 112 > tr.close()
113 113 > except KeyError, exc:
114 114 > raise util.Abort('missing support for %s' % exc)
115 115 > finally:
116 116 > if tr is not None:
117 117 > tr.release()
118 118 > lock.release()
119 119 > remains = sys.stdin.read()
120 120 > ui.write('%i unread bytes\n' % len(remains))
121 121 > if op.records['song']:
122 122 > totalverses = sum(r['verses'] for r in op.records['song'])
123 123 > ui.write('%i total verses sung\n' % totalverses)
124 124 > for rec in op.records['changegroup']:
125 125 > ui.write('addchangegroup return: %i\n' % rec['return'])
126 126 > if op.reply is not None and replypath is not None:
127 127 > file = open(replypath, 'w')
128 128 > for chunk in op.reply.getchunks():
129 129 > file.write(chunk)
130 130 >
131 131 > @command('statbundle2', [], '')
132 132 > def cmdstatbundle2(ui, repo):
133 133 > """print statistic on the bundle2 container read from stdin"""
134 134 > unbundler = bundle2.unbundle20(ui, sys.stdin)
135 135 > try:
136 136 > params = unbundler.params
137 137 > except KeyError, exc:
138 138 > raise util.Abort('unknown parameters: %s' % exc)
139 139 > ui.write('options count: %i\n' % len(params))
140 140 > for key in sorted(params):
141 141 > ui.write('- %s\n' % key)
142 142 > value = params[key]
143 143 > if value is not None:
144 144 > ui.write(' %s\n' % value)
145 145 > count = 0
146 146 > for p in unbundler:
147 147 > count += 1
148 148 > ui.write(' :%s:\n' % p.type)
149 149 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
150 150 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
151 151 > ui.write(' payload: %i bytes\n' % len(p.read()))
152 152 > ui.write('parts count: %i\n' % count)
153 153 > EOF
154 154 $ cat >> $HGRCPATH << EOF
155 155 > [extensions]
156 156 > bundle2=$TESTTMP/bundle2.py
157 157 > [server]
158 158 > bundle2=True
159 > [ui]
160 > ssh=python "$TESTDIR/dummyssh"
159 161 > EOF
160 162
161 163 The extension requires a repo (currently unused)
162 164
163 165 $ hg init main
164 166 $ cd main
165 167 $ touch a
166 168 $ hg add a
167 169 $ hg commit -m 'a'
168 170
169 171
170 172 Empty bundle
171 173 =================
172 174
173 175 - no option
174 176 - no parts
175 177
176 178 Test bundling
177 179
178 180 $ hg bundle2
179 181 HG20\x00\x00\x00\x00 (no-eol) (esc)
180 182
181 183 Test unbundling
182 184
183 185 $ hg bundle2 | hg statbundle2
184 186 options count: 0
185 187 parts count: 0
186 188
187 189 Test old style bundle are detected and refused
188 190
189 191 $ hg bundle --all ../bundle.hg
190 192 1 changesets found
191 193 $ hg statbundle2 < ../bundle.hg
192 194 abort: unknown bundle version 10
193 195 [255]
194 196
195 197 Test parameters
196 198 =================
197 199
198 200 - some options
199 201 - no parts
200 202
201 203 advisory parameters, no value
202 204 -------------------------------
203 205
204 206 Simplest possible parameters form
205 207
206 208 Test generation simple option
207 209
208 210 $ hg bundle2 --param 'caution'
209 211 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
210 212
211 213 Test unbundling
212 214
213 215 $ hg bundle2 --param 'caution' | hg statbundle2
214 216 options count: 1
215 217 - caution
216 218 parts count: 0
217 219
218 220 Test generation multiple option
219 221
220 222 $ hg bundle2 --param 'caution' --param 'meal'
221 223 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
222 224
223 225 Test unbundling
224 226
225 227 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
226 228 options count: 2
227 229 - caution
228 230 - meal
229 231 parts count: 0
230 232
231 233 advisory parameters, with value
232 234 -------------------------------
233 235
234 236 Test generation
235 237
236 238 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
237 239 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
238 240
239 241 Test unbundling
240 242
241 243 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
242 244 options count: 3
243 245 - caution
244 246 - elephants
245 247 - meal
246 248 vegan
247 249 parts count: 0
248 250
249 251 parameter with special char in value
250 252 ---------------------------------------------------
251 253
252 254 Test generation
253 255
254 256 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
255 257 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
256 258
257 259 Test unbundling
258 260
259 261 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
260 262 options count: 2
261 263 - e|! 7/
262 264 babar%#==tutu
263 265 - simple
264 266 parts count: 0
265 267
266 268 Test unknown mandatory option
267 269 ---------------------------------------------------
268 270
269 271 $ hg bundle2 --param 'Gravity' | hg statbundle2
270 272 abort: unknown parameters: 'Gravity'
271 273 [255]
272 274
273 275 Test debug output
274 276 ---------------------------------------------------
275 277
276 278 bundling debug
277 279
278 280 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
279 281 start emission of HG20 stream
280 282 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
281 283 start of parts
282 284 end of bundle
283 285
284 286 file content is ok
285 287
286 288 $ cat ../out.hg2
287 289 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
288 290
289 291 unbundling debug
290 292
291 293 $ hg statbundle2 --debug < ../out.hg2
292 294 start processing of HG20 stream
293 295 reading bundle2 stream parameters
294 296 ignoring unknown parameter 'e|! 7/'
295 297 ignoring unknown parameter 'simple'
296 298 options count: 2
297 299 - e|! 7/
298 300 babar%#==tutu
299 301 - simple
300 302 start extraction of bundle2 parts
301 303 part header size: 0
302 304 end of bundle2 stream
303 305 parts count: 0
304 306
305 307
306 308 Test buggy input
307 309 ---------------------------------------------------
308 310
309 311 empty parameter name
310 312
311 313 $ hg bundle2 --param '' --quiet
312 314 abort: empty parameter name
313 315 [255]
314 316
315 317 bad parameter name
316 318
317 319 $ hg bundle2 --param 42babar
318 320 abort: non letter first character: '42babar'
319 321 [255]
320 322
321 323
322 324 Test part
323 325 =================
324 326
325 327 $ hg bundle2 --parts ../parts.hg2 --debug
326 328 start emission of HG20 stream
327 329 bundle parameter:
328 330 start of parts
329 331 bundle part: "test:empty"
330 332 bundle part: "test:empty"
331 333 bundle part: "test:song"
332 334 bundle part: "test:math"
333 335 bundle part: "test:ping"
334 336 end of bundle
335 337
336 338 $ cat ../parts.hg2
337 339 HG20\x00\x00\x00\x11 (esc)
338 340 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
339 341 test:empty\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x10 test:song\x00\x00\x00\x02\x00\x00\x00\x00\x00\xb2Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko (esc)
340 342 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
341 343 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00+ test:math\x00\x00\x00\x03\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
342 344
343 345
344 346 $ hg statbundle2 < ../parts.hg2
345 347 options count: 0
346 348 :test:empty:
347 349 mandatory: 0
348 350 advisory: 0
349 351 payload: 0 bytes
350 352 :test:empty:
351 353 mandatory: 0
352 354 advisory: 0
353 355 payload: 0 bytes
354 356 :test:song:
355 357 mandatory: 0
356 358 advisory: 0
357 359 payload: 178 bytes
358 360 :test:math:
359 361 mandatory: 2
360 362 advisory: 1
361 363 payload: 2 bytes
362 364 :test:ping:
363 365 mandatory: 0
364 366 advisory: 0
365 367 payload: 0 bytes
366 368 parts count: 5
367 369
368 370 $ hg statbundle2 --debug < ../parts.hg2
369 371 start processing of HG20 stream
370 372 reading bundle2 stream parameters
371 373 options count: 0
372 374 start extraction of bundle2 parts
373 375 part header size: 17
374 376 part type: "test:empty"
375 377 part id: "0"
376 378 part parameters: 0
377 379 :test:empty:
378 380 mandatory: 0
379 381 advisory: 0
380 382 payload chunk size: 0
381 383 payload: 0 bytes
382 384 part header size: 17
383 385 part type: "test:empty"
384 386 part id: "1"
385 387 part parameters: 0
386 388 :test:empty:
387 389 mandatory: 0
388 390 advisory: 0
389 391 payload chunk size: 0
390 392 payload: 0 bytes
391 393 part header size: 16
392 394 part type: "test:song"
393 395 part id: "2"
394 396 part parameters: 0
395 397 :test:song:
396 398 mandatory: 0
397 399 advisory: 0
398 400 payload chunk size: 178
399 401 payload chunk size: 0
400 402 payload: 178 bytes
401 403 part header size: 43
402 404 part type: "test:math"
403 405 part id: "3"
404 406 part parameters: 3
405 407 :test:math:
406 408 mandatory: 2
407 409 advisory: 1
408 410 payload chunk size: 2
409 411 payload chunk size: 0
410 412 payload: 2 bytes
411 413 part header size: 16
412 414 part type: "test:ping"
413 415 part id: "4"
414 416 part parameters: 0
415 417 :test:ping:
416 418 mandatory: 0
417 419 advisory: 0
418 420 payload chunk size: 0
419 421 payload: 0 bytes
420 422 part header size: 0
421 423 end of bundle2 stream
422 424 parts count: 5
423 425
424 426 Test actual unbundling of test part
425 427 =======================================
426 428
427 429 Process the bundle
428 430
429 431 $ hg unbundle2 --debug < ../parts.hg2
430 432 start processing of HG20 stream
431 433 reading bundle2 stream parameters
432 434 start extraction of bundle2 parts
433 435 part header size: 17
434 436 part type: "test:empty"
435 437 part id: "0"
436 438 part parameters: 0
437 439 ignoring unknown advisory part 'test:empty'
438 440 payload chunk size: 0
439 441 part header size: 17
440 442 part type: "test:empty"
441 443 part id: "1"
442 444 part parameters: 0
443 445 ignoring unknown advisory part 'test:empty'
444 446 payload chunk size: 0
445 447 part header size: 16
446 448 part type: "test:song"
447 449 part id: "2"
448 450 part parameters: 0
449 451 found a handler for part 'test:song'
450 452 The choir starts singing:
451 453 payload chunk size: 178
452 454 payload chunk size: 0
453 455 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
454 456 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
455 457 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
456 458 part header size: 43
457 459 part type: "test:math"
458 460 part id: "3"
459 461 part parameters: 3
460 462 ignoring unknown advisory part 'test:math'
461 463 payload chunk size: 2
462 464 payload chunk size: 0
463 465 part header size: 16
464 466 part type: "test:ping"
465 467 part id: "4"
466 468 part parameters: 0
467 469 found a handler for part 'test:ping'
468 470 received ping request (id 4)
469 471 payload chunk size: 0
470 472 part header size: 0
471 473 end of bundle2 stream
472 474 0 unread bytes
473 475 3 total verses sung
474 476
475 477 Unbundle with an unknown mandatory part
476 478 (should abort)
477 479
478 480 $ hg bundle2 --parts --unknown ../unknown.hg2
479 481
480 482 $ hg unbundle2 < ../unknown.hg2
481 483 The choir starts singing:
482 484 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
483 485 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
484 486 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
485 487 0 unread bytes
486 488 abort: missing support for 'test:unknown'
487 489 [255]
488 490
489 491 unbundle with a reply
490 492
491 493 $ hg unbundle2 ../reply.hg2 < ../parts.hg2
492 494 The choir starts singing:
493 495 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
494 496 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
495 497 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
496 498 received ping request (id 4)
497 499 0 unread bytes
498 500 3 total verses sung
499 501
500 502 The reply is a bundle
501 503
502 504 $ cat ../reply.hg2
503 505 HG20\x00\x00\x00\x1e test:pong\x00\x00\x00\x00\x01\x00\x0b\x01in-reply-to4\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
504 506
505 507 The reply is valid
506 508
507 509 $ hg statbundle2 < ../reply.hg2
508 510 options count: 0
509 511 :test:pong:
510 512 mandatory: 1
511 513 advisory: 0
512 514 payload: 0 bytes
513 515 parts count: 1
514 516
515 517 Support for changegroup
516 518 ===================================
517 519
518 520 $ hg unbundle $TESTDIR/bundles/rebase.hg
519 521 adding changesets
520 522 adding manifests
521 523 adding file changes
522 524 added 8 changesets with 7 changes to 7 files (+3 heads)
523 525 (run 'hg heads' to see heads, 'hg merge' to merge)
524 526
525 527 $ hg log -G
526 528 o changeset: 8:02de42196ebe
527 529 | tag: tip
528 530 | parent: 6:24b6387c8c8c
529 531 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
530 532 | date: Sat Apr 30 15:24:48 2011 +0200
531 533 | summary: H
532 534 |
533 535 | o changeset: 7:eea13746799a
534 536 |/| parent: 6:24b6387c8c8c
535 537 | | parent: 5:9520eea781bc
536 538 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
537 539 | | date: Sat Apr 30 15:24:48 2011 +0200
538 540 | | summary: G
539 541 | |
540 542 o | changeset: 6:24b6387c8c8c
541 543 | | parent: 1:cd010b8cd998
542 544 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
543 545 | | date: Sat Apr 30 15:24:48 2011 +0200
544 546 | | summary: F
545 547 | |
546 548 | o changeset: 5:9520eea781bc
547 549 |/ parent: 1:cd010b8cd998
548 550 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
549 551 | date: Sat Apr 30 15:24:48 2011 +0200
550 552 | summary: E
551 553 |
552 554 | o changeset: 4:32af7686d403
553 555 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
554 556 | | date: Sat Apr 30 15:24:48 2011 +0200
555 557 | | summary: D
556 558 | |
557 559 | o changeset: 3:5fddd98957c8
558 560 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
559 561 | | date: Sat Apr 30 15:24:48 2011 +0200
560 562 | | summary: C
561 563 | |
562 564 | o changeset: 2:42ccdea3bb16
563 565 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
564 566 | date: Sat Apr 30 15:24:48 2011 +0200
565 567 | summary: B
566 568 |
567 569 o changeset: 1:cd010b8cd998
568 570 parent: -1:000000000000
569 571 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
570 572 date: Sat Apr 30 15:24:48 2011 +0200
571 573 summary: A
572 574
573 575 @ changeset: 0:3903775176ed
574 576 user: test
575 577 date: Thu Jan 01 00:00:00 1970 +0000
576 578 summary: a
577 579
578 580
579 581 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
580 582 4 changesets found
581 583 list of changesets:
582 584 32af7686d403cf45b5d95f2d70cebea587ac806a
583 585 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
584 586 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
585 587 02de42196ebee42ef284b6780a87cdc96e8eaab6
586 588 start emission of HG20 stream
587 589 bundle parameter:
588 590 start of parts
589 591 bundle part: "changegroup"
590 592 bundling: 1/4 changesets (25.00%)
591 593 bundling: 2/4 changesets (50.00%)
592 594 bundling: 3/4 changesets (75.00%)
593 595 bundling: 4/4 changesets (100.00%)
594 596 bundling: 1/4 manifests (25.00%)
595 597 bundling: 2/4 manifests (50.00%)
596 598 bundling: 3/4 manifests (75.00%)
597 599 bundling: 4/4 manifests (100.00%)
598 600 bundling: D 1/3 files (33.33%)
599 601 bundling: E 2/3 files (66.67%)
600 602 bundling: H 3/3 files (100.00%)
601 603 end of bundle
602 604
603 605 $ cat ../rev.hg2
604 606 HG20\x00\x00\x00\x12\x0bchangegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x13\x00\x00\x00\xa42\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j_\xdd\xd9\x89W\xc8\xa5JMCm\xfe\x1d\xa9\xd8\x7f!\xa1\xb9{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c (esc)
605 607 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
606 608 \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01D\x00\x00\x00\xa4\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xcd\x01\x0b\x8c\xd9\x98\xf3\x98\x1aZ\x81\x15\xf9O\x8d\xa4\xabP`\x89\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)4dece9c826f69490507b98c6383a3009b295837d (esc)
607 609 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
608 610 \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01E\x00\x00\x00\xa2\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)365b93d57fdf4814e2b5911d6bacff2b12014441 (esc)
609 611 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01G\x00\x00\x00\xa4\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
610 612 \x87\xcd\xc9n\x8e\xaa\xb6$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
611 613 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
612 614 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
613 615 \x00\x00\x00g\x00\x00\x00h\x00\x00\x00\x01H\x00\x00\x00\x00\x00\x00\x00\x8bn\x1fLG\xec\xb53\xff\xd0\xc8\xe5,\xdc\x88\xaf\xb6\xcd9\xe2\x0cf\xa5\xa0\x18\x17\xfd\xf5#\x9c'8\x02\xb5\xb7a\x8d\x05\x1c\x89\xe4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x81\x00\x00\x00\x81\x00\x00\x00+D\x00c3f1ca2924c16a19b0656a84900e504e5b0aec2d (esc)
614 616 \x00\x00\x00\x8bM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0 \xb2\x95\x83}\x00}\x8c\x9d\x88\x84\x13%\xf5\xc6\xb0cq\xb3[N\x8a+\x1a\x83\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00+\x00\x00\x00\xac\x00\x00\x00+E\x009c6fd0350a6c0d0c49d4a9c5017cf07043f54e58 (esc)
615 617 \x00\x00\x00\x8b6[\x93\xd5\x7f\xdfH\x14\xe2\xb5\x91\x1dk\xac\xff+\x12\x01DA(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xceM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0 \xb2\x95\x83}\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00V\x00\x00\x00V\x00\x00\x00+F\x0022bfcfd62a21a3287edbd4d656218d0f525ed76a (esc)
616 618 \x00\x00\x00\x97\x8b\xeeH\xed\xc71\x85A\xfc\x00\x13\xeeA\xb0\x89'j\x8c$\xbf(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xce\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
617 619 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00+\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x81\x00\x00\x00\x81\x00\x00\x00+H\x008500189e74a9e0475e822093bc7db0d631aeb0b4 (esc)
618 620 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
619 621 \xec-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02D (esc)
620 622 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
621 623 l\r (no-eol) (esc)
622 624 \x0cI\xd4\xa9\xc5\x01|\xf0pC\xf5NX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02E (esc)
623 625 \x00\x00\x00\x00\x00\x00\x00\x05H\x00\x00\x00b\x85\x00\x18\x9et\xa9\xe0G^\x82 \x93\xbc}\xb0\xd61\xae\xb0\xb4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
624 626 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
625 627 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
626 628
627 629 $ hg unbundle2 ../rev-replay.hg2 < ../rev.hg2
628 630 adding changesets
629 631 adding manifests
630 632 adding file changes
631 633 added 0 changesets with 0 changes to 3 files
632 634 0 unread bytes
633 635 addchangegroup return: 1
634 636
635 637 $ cat ../rev-replay.hg2
636 638 HG20\x00\x00\x00/\x11reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to0return1\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
637 639
638 640 Real world exchange
639 641 =====================
640 642
641 643
642 644 clone --pull
643 645
644 646 $ cd ..
645 647 $ hg clone main other --pull --rev 9520eea781bc
646 648 adding changesets
647 649 adding manifests
648 650 adding file changes
649 651 added 2 changesets with 2 changes to 2 files
650 652 updating to branch default
651 653 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
652 654 $ hg -R other log -G
653 655 @ changeset: 1:9520eea781bc
654 656 | tag: tip
655 657 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
656 658 | date: Sat Apr 30 15:24:48 2011 +0200
657 659 | summary: E
658 660 |
659 661 o changeset: 0:cd010b8cd998
660 662 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
661 663 date: Sat Apr 30 15:24:48 2011 +0200
662 664 summary: A
663 665
664 666
665 667 pull
666 668
667 669 $ hg -R other pull -r 24b6387c8c8c
668 670 pulling from $TESTTMP/main (glob)
669 671 searching for changes
670 672 adding changesets
671 673 adding manifests
672 674 adding file changes
673 675 added 1 changesets with 1 changes to 1 files (+1 heads)
674 676 (run 'hg heads' to see heads, 'hg merge' to merge)
675 677
676 678 push
677 679
678 680 $ hg -R main push other --rev eea13746799a
679 681 pushing to other
680 682 searching for changes
681 683 adding changesets
682 684 adding manifests
683 685 adding file changes
684 686 added 1 changesets with 0 changes to 0 files (-1 heads)
687
688 pull over ssh
689
690 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
691 pulling from ssh://user@dummy/main
692 searching for changes
693 adding changesets
694 adding manifests
695 adding file changes
696 added 1 changesets with 1 changes to 1 files (+1 heads)
697 (run 'hg heads' to see heads, 'hg merge' to merge)
698
699 pull over http
700
701 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
702 $ cat main.pid >> $DAEMON_PIDS
703
704 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
705 pulling from http://localhost:$HGPORT/
706 searching for changes
707 adding changesets
708 adding manifests
709 adding file changes
710 added 1 changesets with 1 changes to 1 files (+1 heads)
711 (run 'hg heads .' to see heads, 'hg merge' to merge)
712 $ cat main-error.log
713
714
General Comments 0
You need to be logged in to leave comments. Login now