##// END OF EJS Templates
pullbundle: fix handling of gzip bundlespecs...
Joerg Sonnenberger -
r38700:7e4a856a default
parent child Browse files
Show More
@@ -1,666 +1,668
1 1 # wireprotov1server.py - Wire protocol version 1 server functionality
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 os
11 11
12 12 from .i18n import _
13 13 from .node import (
14 14 hex,
15 15 nullid,
16 16 )
17 17
18 18 from . import (
19 19 bundle2,
20 20 changegroup as changegroupmod,
21 21 discovery,
22 22 encoding,
23 23 error,
24 24 exchange,
25 25 pushkey as pushkeymod,
26 26 pycompat,
27 27 streamclone,
28 28 util,
29 29 wireprototypes,
30 30 )
31 31
32 32 from .utils import (
33 33 procutil,
34 34 stringutil,
35 35 )
36 36
37 37 urlerr = util.urlerr
38 38 urlreq = util.urlreq
39 39
40 40 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
41 41 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
42 42 'IncompatibleClient')
43 43 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
44 44
45 45 def clientcompressionsupport(proto):
46 46 """Returns a list of compression methods supported by the client.
47 47
48 48 Returns a list of the compression methods supported by the client
49 49 according to the protocol capabilities. If no such capability has
50 50 been announced, fallback to the default of zlib and uncompressed.
51 51 """
52 52 for cap in proto.getprotocaps():
53 53 if cap.startswith('comp='):
54 54 return cap[5:].split(',')
55 55 return ['zlib', 'none']
56 56
57 57 # wire protocol command can either return a string or one of these classes.
58 58
59 59 def getdispatchrepo(repo, proto, command):
60 60 """Obtain the repo used for processing wire protocol commands.
61 61
62 62 The intent of this function is to serve as a monkeypatch point for
63 63 extensions that need commands to operate on different repo views under
64 64 specialized circumstances.
65 65 """
66 66 return repo.filtered('served')
67 67
68 68 def dispatch(repo, proto, command):
69 69 repo = getdispatchrepo(repo, proto, command)
70 70
71 71 func, spec = commands[command]
72 72 args = proto.getargs(spec)
73 73
74 74 return func(repo, proto, *args)
75 75
76 76 def options(cmd, keys, others):
77 77 opts = {}
78 78 for k in keys:
79 79 if k in others:
80 80 opts[k] = others[k]
81 81 del others[k]
82 82 if others:
83 83 procutil.stderr.write("warning: %s ignored unexpected arguments %s\n"
84 84 % (cmd, ",".join(others)))
85 85 return opts
86 86
87 87 def bundle1allowed(repo, action):
88 88 """Whether a bundle1 operation is allowed from the server.
89 89
90 90 Priority is:
91 91
92 92 1. server.bundle1gd.<action> (if generaldelta active)
93 93 2. server.bundle1.<action>
94 94 3. server.bundle1gd (if generaldelta active)
95 95 4. server.bundle1
96 96 """
97 97 ui = repo.ui
98 98 gd = 'generaldelta' in repo.requirements
99 99
100 100 if gd:
101 101 v = ui.configbool('server', 'bundle1gd.%s' % action)
102 102 if v is not None:
103 103 return v
104 104
105 105 v = ui.configbool('server', 'bundle1.%s' % action)
106 106 if v is not None:
107 107 return v
108 108
109 109 if gd:
110 110 v = ui.configbool('server', 'bundle1gd')
111 111 if v is not None:
112 112 return v
113 113
114 114 return ui.configbool('server', 'bundle1')
115 115
116 116 commands = wireprototypes.commanddict()
117 117
118 118 def wireprotocommand(name, args=None, permission='push'):
119 119 """Decorator to declare a wire protocol command.
120 120
121 121 ``name`` is the name of the wire protocol command being provided.
122 122
123 123 ``args`` defines the named arguments accepted by the command. It is
124 124 a space-delimited list of argument names. ``*`` denotes a special value
125 125 that says to accept all named arguments.
126 126
127 127 ``permission`` defines the permission type needed to run this command.
128 128 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
129 129 respectively. Default is to assume command requires ``push`` permissions
130 130 because otherwise commands not declaring their permissions could modify
131 131 a repository that is supposed to be read-only.
132 132 """
133 133 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
134 134 if v['version'] == 1}
135 135
136 136 # Because SSHv2 is a mirror of SSHv1, we allow "batch" commands through to
137 137 # SSHv2.
138 138 # TODO undo this hack when SSH is using the unified frame protocol.
139 139 if name == b'batch':
140 140 transports.add(wireprototypes.SSHV2)
141 141
142 142 if permission not in ('push', 'pull'):
143 143 raise error.ProgrammingError('invalid wire protocol permission; '
144 144 'got %s; expected "push" or "pull"' %
145 145 permission)
146 146
147 147 if args is None:
148 148 args = ''
149 149
150 150 if not isinstance(args, bytes):
151 151 raise error.ProgrammingError('arguments for version 1 commands '
152 152 'must be declared as bytes')
153 153
154 154 def register(func):
155 155 if name in commands:
156 156 raise error.ProgrammingError('%s command already registered '
157 157 'for version 1' % name)
158 158 commands[name] = wireprototypes.commandentry(
159 159 func, args=args, transports=transports, permission=permission)
160 160
161 161 return func
162 162 return register
163 163
164 164 # TODO define a more appropriate permissions type to use for this.
165 165 @wireprotocommand('batch', 'cmds *', permission='pull')
166 166 def batch(repo, proto, cmds, others):
167 167 unescapearg = wireprototypes.unescapebatcharg
168 168 repo = repo.filtered("served")
169 169 res = []
170 170 for pair in cmds.split(';'):
171 171 op, args = pair.split(' ', 1)
172 172 vals = {}
173 173 for a in args.split(','):
174 174 if a:
175 175 n, v = a.split('=')
176 176 vals[unescapearg(n)] = unescapearg(v)
177 177 func, spec = commands[op]
178 178
179 179 # Validate that client has permissions to perform this command.
180 180 perm = commands[op].permission
181 181 assert perm in ('push', 'pull')
182 182 proto.checkperm(perm)
183 183
184 184 if spec:
185 185 keys = spec.split()
186 186 data = {}
187 187 for k in keys:
188 188 if k == '*':
189 189 star = {}
190 190 for key in vals.keys():
191 191 if key not in keys:
192 192 star[key] = vals[key]
193 193 data['*'] = star
194 194 else:
195 195 data[k] = vals[k]
196 196 result = func(repo, proto, *[data[k] for k in keys])
197 197 else:
198 198 result = func(repo, proto)
199 199 if isinstance(result, wireprototypes.ooberror):
200 200 return result
201 201
202 202 # For now, all batchable commands must return bytesresponse or
203 203 # raw bytes (for backwards compatibility).
204 204 assert isinstance(result, (wireprototypes.bytesresponse, bytes))
205 205 if isinstance(result, wireprototypes.bytesresponse):
206 206 result = result.data
207 207 res.append(wireprototypes.escapebatcharg(result))
208 208
209 209 return wireprototypes.bytesresponse(';'.join(res))
210 210
211 211 @wireprotocommand('between', 'pairs', permission='pull')
212 212 def between(repo, proto, pairs):
213 213 pairs = [wireprototypes.decodelist(p, '-') for p in pairs.split(" ")]
214 214 r = []
215 215 for b in repo.between(pairs):
216 216 r.append(wireprototypes.encodelist(b) + "\n")
217 217
218 218 return wireprototypes.bytesresponse(''.join(r))
219 219
220 220 @wireprotocommand('branchmap', permission='pull')
221 221 def branchmap(repo, proto):
222 222 branchmap = repo.branchmap()
223 223 heads = []
224 224 for branch, nodes in branchmap.iteritems():
225 225 branchname = urlreq.quote(encoding.fromlocal(branch))
226 226 branchnodes = wireprototypes.encodelist(nodes)
227 227 heads.append('%s %s' % (branchname, branchnodes))
228 228
229 229 return wireprototypes.bytesresponse('\n'.join(heads))
230 230
231 231 @wireprotocommand('branches', 'nodes', permission='pull')
232 232 def branches(repo, proto, nodes):
233 233 nodes = wireprototypes.decodelist(nodes)
234 234 r = []
235 235 for b in repo.branches(nodes):
236 236 r.append(wireprototypes.encodelist(b) + "\n")
237 237
238 238 return wireprototypes.bytesresponse(''.join(r))
239 239
240 240 @wireprotocommand('clonebundles', '', permission='pull')
241 241 def clonebundles(repo, proto):
242 242 """Server command for returning info for available bundles to seed clones.
243 243
244 244 Clients will parse this response and determine what bundle to fetch.
245 245
246 246 Extensions may wrap this command to filter or dynamically emit data
247 247 depending on the request. e.g. you could advertise URLs for the closest
248 248 data center given the client's IP address.
249 249 """
250 250 return wireprototypes.bytesresponse(
251 251 repo.vfs.tryread('clonebundles.manifest'))
252 252
253 253 wireprotocaps = ['lookup', 'branchmap', 'pushkey',
254 254 'known', 'getbundle', 'unbundlehash']
255 255
256 256 def _capabilities(repo, proto):
257 257 """return a list of capabilities for a repo
258 258
259 259 This function exists to allow extensions to easily wrap capabilities
260 260 computation
261 261
262 262 - returns a lists: easy to alter
263 263 - change done here will be propagated to both `capabilities` and `hello`
264 264 command without any other action needed.
265 265 """
266 266 # copy to prevent modification of the global list
267 267 caps = list(wireprotocaps)
268 268
269 269 # Command of same name as capability isn't exposed to version 1 of
270 270 # transports. So conditionally add it.
271 271 if commands.commandavailable('changegroupsubset', proto):
272 272 caps.append('changegroupsubset')
273 273
274 274 if streamclone.allowservergeneration(repo):
275 275 if repo.ui.configbool('server', 'preferuncompressed'):
276 276 caps.append('stream-preferred')
277 277 requiredformats = repo.requirements & repo.supportedformats
278 278 # if our local revlogs are just revlogv1, add 'stream' cap
279 279 if not requiredformats - {'revlogv1'}:
280 280 caps.append('stream')
281 281 # otherwise, add 'streamreqs' detailing our local revlog format
282 282 else:
283 283 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
284 284 if repo.ui.configbool('experimental', 'bundle2-advertise'):
285 285 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role='server'))
286 286 caps.append('bundle2=' + urlreq.quote(capsblob))
287 287 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
288 288
289 289 return proto.addcapabilities(repo, caps)
290 290
291 291 # If you are writing an extension and consider wrapping this function. Wrap
292 292 # `_capabilities` instead.
293 293 @wireprotocommand('capabilities', permission='pull')
294 294 def capabilities(repo, proto):
295 295 caps = _capabilities(repo, proto)
296 296 return wireprototypes.bytesresponse(' '.join(sorted(caps)))
297 297
298 298 @wireprotocommand('changegroup', 'roots', permission='pull')
299 299 def changegroup(repo, proto, roots):
300 300 nodes = wireprototypes.decodelist(roots)
301 301 outgoing = discovery.outgoing(repo, missingroots=nodes,
302 302 missingheads=repo.heads())
303 303 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
304 304 gen = iter(lambda: cg.read(32768), '')
305 305 return wireprototypes.streamres(gen=gen)
306 306
307 307 @wireprotocommand('changegroupsubset', 'bases heads',
308 308 permission='pull')
309 309 def changegroupsubset(repo, proto, bases, heads):
310 310 bases = wireprototypes.decodelist(bases)
311 311 heads = wireprototypes.decodelist(heads)
312 312 outgoing = discovery.outgoing(repo, missingroots=bases,
313 313 missingheads=heads)
314 314 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
315 315 gen = iter(lambda: cg.read(32768), '')
316 316 return wireprototypes.streamres(gen=gen)
317 317
318 318 @wireprotocommand('debugwireargs', 'one two *',
319 319 permission='pull')
320 320 def debugwireargs(repo, proto, one, two, others):
321 321 # only accept optional args from the known set
322 322 opts = options('debugwireargs', ['three', 'four'], others)
323 323 return wireprototypes.bytesresponse(repo.debugwireargs(
324 324 one, two, **pycompat.strkwargs(opts)))
325 325
326 326 def find_pullbundle(repo, proto, opts, clheads, heads, common):
327 327 """Return a file object for the first matching pullbundle.
328 328
329 329 Pullbundles are specified in .hg/pullbundles.manifest similar to
330 330 clonebundles.
331 331 For each entry, the bundle specification is checked for compatibility:
332 332 - Client features vs the BUNDLESPEC.
333 333 - Revisions shared with the clients vs base revisions of the bundle.
334 334 A bundle can be applied only if all its base revisions are known by
335 335 the client.
336 336 - At least one leaf of the bundle's DAG is missing on the client.
337 337 - Every leaf of the bundle's DAG is part of node set the client wants.
338 338 E.g. do not send a bundle of all changes if the client wants only
339 339 one specific branch of many.
340 340 """
341 341 def decodehexstring(s):
342 342 return set([h.decode('hex') for h in s.split(';')])
343 343
344 344 manifest = repo.vfs.tryread('pullbundles.manifest')
345 345 if not manifest:
346 346 return None
347 347 res = exchange.parseclonebundlesmanifest(repo, manifest)
348 348 res = exchange.filterclonebundleentries(repo, res)
349 349 if not res:
350 350 return None
351 351 cl = repo.changelog
352 352 heads_anc = cl.ancestors([cl.rev(rev) for rev in heads], inclusive=True)
353 353 common_anc = cl.ancestors([cl.rev(rev) for rev in common], inclusive=True)
354 354 compformats = clientcompressionsupport(proto)
355 355 for entry in res:
356 if 'COMPRESSION' in entry and entry['COMPRESSION'] not in compformats:
356 comp = entry.get('COMPRESSION')
357 altcomp = util.compengines._bundlenames.get(comp)
358 if comp and comp not in compformats and altcomp not in compformats:
357 359 continue
358 360 # No test yet for VERSION, since V2 is supported by any client
359 361 # that advertises partial pulls
360 362 if 'heads' in entry:
361 363 try:
362 364 bundle_heads = decodehexstring(entry['heads'])
363 365 except TypeError:
364 366 # Bad heads entry
365 367 continue
366 368 if bundle_heads.issubset(common):
367 369 continue # Nothing new
368 370 if all(cl.rev(rev) in common_anc for rev in bundle_heads):
369 371 continue # Still nothing new
370 372 if any(cl.rev(rev) not in heads_anc and
371 373 cl.rev(rev) not in common_anc for rev in bundle_heads):
372 374 continue
373 375 if 'bases' in entry:
374 376 try:
375 377 bundle_bases = decodehexstring(entry['bases'])
376 378 except TypeError:
377 379 # Bad bases entry
378 380 continue
379 381 if not all(cl.rev(rev) in common_anc for rev in bundle_bases):
380 382 continue
381 383 path = entry['URL']
382 384 repo.ui.debug('sending pullbundle "%s"\n' % path)
383 385 try:
384 386 return repo.vfs.open(path)
385 387 except IOError:
386 388 repo.ui.debug('pullbundle "%s" not accessible\n' % path)
387 389 continue
388 390 return None
389 391
390 392 @wireprotocommand('getbundle', '*', permission='pull')
391 393 def getbundle(repo, proto, others):
392 394 opts = options('getbundle', wireprototypes.GETBUNDLE_ARGUMENTS.keys(),
393 395 others)
394 396 for k, v in opts.iteritems():
395 397 keytype = wireprototypes.GETBUNDLE_ARGUMENTS[k]
396 398 if keytype == 'nodes':
397 399 opts[k] = wireprototypes.decodelist(v)
398 400 elif keytype == 'csv':
399 401 opts[k] = list(v.split(','))
400 402 elif keytype == 'scsv':
401 403 opts[k] = set(v.split(','))
402 404 elif keytype == 'boolean':
403 405 # Client should serialize False as '0', which is a non-empty string
404 406 # so it evaluates as a True bool.
405 407 if v == '0':
406 408 opts[k] = False
407 409 else:
408 410 opts[k] = bool(v)
409 411 elif keytype != 'plain':
410 412 raise KeyError('unknown getbundle option type %s'
411 413 % keytype)
412 414
413 415 if not bundle1allowed(repo, 'pull'):
414 416 if not exchange.bundle2requested(opts.get('bundlecaps')):
415 417 if proto.name == 'http-v1':
416 418 return wireprototypes.ooberror(bundle2required)
417 419 raise error.Abort(bundle2requiredmain,
418 420 hint=bundle2requiredhint)
419 421
420 422 prefercompressed = True
421 423
422 424 try:
423 425 clheads = set(repo.changelog.heads())
424 426 heads = set(opts.get('heads', set()))
425 427 common = set(opts.get('common', set()))
426 428 common.discard(nullid)
427 429 if (repo.ui.configbool('server', 'pullbundle') and
428 430 'partial-pull' in proto.getprotocaps()):
429 431 # Check if a pre-built bundle covers this request.
430 432 bundle = find_pullbundle(repo, proto, opts, clheads, heads, common)
431 433 if bundle:
432 434 return wireprototypes.streamres(gen=util.filechunkiter(bundle),
433 435 prefer_uncompressed=True)
434 436
435 437 if repo.ui.configbool('server', 'disablefullbundle'):
436 438 # Check to see if this is a full clone.
437 439 changegroup = opts.get('cg', True)
438 440 if changegroup and not common and clheads == heads:
439 441 raise error.Abort(
440 442 _('server has pull-based clones disabled'),
441 443 hint=_('remove --pull if specified or upgrade Mercurial'))
442 444
443 445 info, chunks = exchange.getbundlechunks(repo, 'serve',
444 446 **pycompat.strkwargs(opts))
445 447 prefercompressed = info.get('prefercompressed', True)
446 448 except error.Abort as exc:
447 449 # cleanly forward Abort error to the client
448 450 if not exchange.bundle2requested(opts.get('bundlecaps')):
449 451 if proto.name == 'http-v1':
450 452 return wireprototypes.ooberror(pycompat.bytestr(exc) + '\n')
451 453 raise # cannot do better for bundle1 + ssh
452 454 # bundle2 request expect a bundle2 reply
453 455 bundler = bundle2.bundle20(repo.ui)
454 456 manargs = [('message', pycompat.bytestr(exc))]
455 457 advargs = []
456 458 if exc.hint is not None:
457 459 advargs.append(('hint', exc.hint))
458 460 bundler.addpart(bundle2.bundlepart('error:abort',
459 461 manargs, advargs))
460 462 chunks = bundler.getchunks()
461 463 prefercompressed = False
462 464
463 465 return wireprototypes.streamres(
464 466 gen=chunks, prefer_uncompressed=not prefercompressed)
465 467
466 468 @wireprotocommand('heads', permission='pull')
467 469 def heads(repo, proto):
468 470 h = repo.heads()
469 471 return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + '\n')
470 472
471 473 @wireprotocommand('hello', permission='pull')
472 474 def hello(repo, proto):
473 475 """Called as part of SSH handshake to obtain server info.
474 476
475 477 Returns a list of lines describing interesting things about the
476 478 server, in an RFC822-like format.
477 479
478 480 Currently, the only one defined is ``capabilities``, which consists of a
479 481 line of space separated tokens describing server abilities:
480 482
481 483 capabilities: <token0> <token1> <token2>
482 484 """
483 485 caps = capabilities(repo, proto).data
484 486 return wireprototypes.bytesresponse('capabilities: %s\n' % caps)
485 487
486 488 @wireprotocommand('listkeys', 'namespace', permission='pull')
487 489 def listkeys(repo, proto, namespace):
488 490 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
489 491 return wireprototypes.bytesresponse(pushkeymod.encodekeys(d))
490 492
491 493 @wireprotocommand('lookup', 'key', permission='pull')
492 494 def lookup(repo, proto, key):
493 495 try:
494 496 k = encoding.tolocal(key)
495 497 n = repo.lookup(k)
496 498 r = hex(n)
497 499 success = 1
498 500 except Exception as inst:
499 501 r = stringutil.forcebytestr(inst)
500 502 success = 0
501 503 return wireprototypes.bytesresponse('%d %s\n' % (success, r))
502 504
503 505 @wireprotocommand('known', 'nodes *', permission='pull')
504 506 def known(repo, proto, nodes, others):
505 507 v = ''.join(b and '1' or '0'
506 508 for b in repo.known(wireprototypes.decodelist(nodes)))
507 509 return wireprototypes.bytesresponse(v)
508 510
509 511 @wireprotocommand('protocaps', 'caps', permission='pull')
510 512 def protocaps(repo, proto, caps):
511 513 if proto.name == wireprototypes.SSHV1:
512 514 proto._protocaps = set(caps.split(' '))
513 515 return wireprototypes.bytesresponse('OK')
514 516
515 517 @wireprotocommand('pushkey', 'namespace key old new', permission='push')
516 518 def pushkey(repo, proto, namespace, key, old, new):
517 519 # compatibility with pre-1.8 clients which were accidentally
518 520 # sending raw binary nodes rather than utf-8-encoded hex
519 521 if len(new) == 20 and stringutil.escapestr(new) != new:
520 522 # looks like it could be a binary node
521 523 try:
522 524 new.decode('utf-8')
523 525 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
524 526 except UnicodeDecodeError:
525 527 pass # binary, leave unmodified
526 528 else:
527 529 new = encoding.tolocal(new) # normal path
528 530
529 531 with proto.mayberedirectstdio() as output:
530 532 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
531 533 encoding.tolocal(old), new) or False
532 534
533 535 output = output.getvalue() if output else ''
534 536 return wireprototypes.bytesresponse('%d\n%s' % (int(r), output))
535 537
536 538 @wireprotocommand('stream_out', permission='pull')
537 539 def stream(repo, proto):
538 540 '''If the server supports streaming clone, it advertises the "stream"
539 541 capability with a value representing the version and flags of the repo
540 542 it is serving. Client checks to see if it understands the format.
541 543 '''
542 544 return wireprototypes.streamreslegacy(
543 545 streamclone.generatev1wireproto(repo))
544 546
545 547 @wireprotocommand('unbundle', 'heads', permission='push')
546 548 def unbundle(repo, proto, heads):
547 549 their_heads = wireprototypes.decodelist(heads)
548 550
549 551 with proto.mayberedirectstdio() as output:
550 552 try:
551 553 exchange.check_heads(repo, their_heads, 'preparing changes')
552 554 cleanup = lambda: None
553 555 try:
554 556 payload = proto.getpayload()
555 557 if repo.ui.configbool('server', 'streamunbundle'):
556 558 def cleanup():
557 559 # Ensure that the full payload is consumed, so
558 560 # that the connection doesn't contain trailing garbage.
559 561 for p in payload:
560 562 pass
561 563 fp = util.chunkbuffer(payload)
562 564 else:
563 565 # write bundle data to temporary file as it can be big
564 566 fp, tempname = None, None
565 567 def cleanup():
566 568 if fp:
567 569 fp.close()
568 570 if tempname:
569 571 os.unlink(tempname)
570 572 fd, tempname = pycompat.mkstemp(prefix='hg-unbundle-')
571 573 repo.ui.debug('redirecting incoming bundle to %s\n' %
572 574 tempname)
573 575 fp = os.fdopen(fd, pycompat.sysstr('wb+'))
574 576 r = 0
575 577 for p in payload:
576 578 fp.write(p)
577 579 fp.seek(0)
578 580
579 581 gen = exchange.readbundle(repo.ui, fp, None)
580 582 if (isinstance(gen, changegroupmod.cg1unpacker)
581 583 and not bundle1allowed(repo, 'push')):
582 584 if proto.name == 'http-v1':
583 585 # need to special case http because stderr do not get to
584 586 # the http client on failed push so we need to abuse
585 587 # some other error type to make sure the message get to
586 588 # the user.
587 589 return wireprototypes.ooberror(bundle2required)
588 590 raise error.Abort(bundle2requiredmain,
589 591 hint=bundle2requiredhint)
590 592
591 593 r = exchange.unbundle(repo, gen, their_heads, 'serve',
592 594 proto.client())
593 595 if util.safehasattr(r, 'addpart'):
594 596 # The return looks streamable, we are in the bundle2 case
595 597 # and should return a stream.
596 598 return wireprototypes.streamreslegacy(gen=r.getchunks())
597 599 return wireprototypes.pushres(
598 600 r, output.getvalue() if output else '')
599 601
600 602 finally:
601 603 cleanup()
602 604
603 605 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
604 606 # handle non-bundle2 case first
605 607 if not getattr(exc, 'duringunbundle2', False):
606 608 try:
607 609 raise
608 610 except error.Abort:
609 611 # The old code we moved used procutil.stderr directly.
610 612 # We did not change it to minimise code change.
611 613 # This need to be moved to something proper.
612 614 # Feel free to do it.
613 615 procutil.stderr.write("abort: %s\n" % exc)
614 616 if exc.hint is not None:
615 617 procutil.stderr.write("(%s)\n" % exc.hint)
616 618 procutil.stderr.flush()
617 619 return wireprototypes.pushres(
618 620 0, output.getvalue() if output else '')
619 621 except error.PushRaced:
620 622 return wireprototypes.pusherr(
621 623 pycompat.bytestr(exc),
622 624 output.getvalue() if output else '')
623 625
624 626 bundler = bundle2.bundle20(repo.ui)
625 627 for out in getattr(exc, '_bundle2salvagedoutput', ()):
626 628 bundler.addpart(out)
627 629 try:
628 630 try:
629 631 raise
630 632 except error.PushkeyFailed as exc:
631 633 # check client caps
632 634 remotecaps = getattr(exc, '_replycaps', None)
633 635 if (remotecaps is not None
634 636 and 'pushkey' not in remotecaps.get('error', ())):
635 637 # no support remote side, fallback to Abort handler.
636 638 raise
637 639 part = bundler.newpart('error:pushkey')
638 640 part.addparam('in-reply-to', exc.partid)
639 641 if exc.namespace is not None:
640 642 part.addparam('namespace', exc.namespace,
641 643 mandatory=False)
642 644 if exc.key is not None:
643 645 part.addparam('key', exc.key, mandatory=False)
644 646 if exc.new is not None:
645 647 part.addparam('new', exc.new, mandatory=False)
646 648 if exc.old is not None:
647 649 part.addparam('old', exc.old, mandatory=False)
648 650 if exc.ret is not None:
649 651 part.addparam('ret', exc.ret, mandatory=False)
650 652 except error.BundleValueError as exc:
651 653 errpart = bundler.newpart('error:unsupportedcontent')
652 654 if exc.parttype is not None:
653 655 errpart.addparam('parttype', exc.parttype)
654 656 if exc.params:
655 657 errpart.addparam('params', '\0'.join(exc.params))
656 658 except error.Abort as exc:
657 659 manargs = [('message', stringutil.forcebytestr(exc))]
658 660 advargs = []
659 661 if exc.hint is not None:
660 662 advargs.append(('hint', exc.hint))
661 663 bundler.addpart(bundle2.bundlepart('error:abort',
662 664 manargs, advargs))
663 665 except error.PushRaced as exc:
664 666 bundler.newpart('error:pushraced',
665 667 [('message', stringutil.forcebytestr(exc))])
666 668 return wireprototypes.streamreslegacy(gen=bundler.getchunks())
@@ -1,157 +1,157
1 1 #require no-chg
2 2
3 3 $ hg init repo
4 4 $ cd repo
5 5 $ echo foo > foo
6 6 $ hg ci -qAm 'add foo'
7 7 $ echo >> foo
8 8 $ hg ci -m 'change foo'
9 9 $ hg up -qC 0
10 10 $ echo bar > bar
11 11 $ hg ci -qAm 'add bar'
12 12
13 13 $ hg log
14 14 changeset: 2:effea6de0384
15 15 tag: tip
16 16 parent: 0:bbd179dfa0a7
17 17 user: test
18 18 date: Thu Jan 01 00:00:00 1970 +0000
19 19 summary: add bar
20 20
21 21 changeset: 1:ed1b79f46b9a
22 22 user: test
23 23 date: Thu Jan 01 00:00:00 1970 +0000
24 24 summary: change foo
25 25
26 26 changeset: 0:bbd179dfa0a7
27 27 user: test
28 28 date: Thu Jan 01 00:00:00 1970 +0000
29 29 summary: add foo
30 30
31 31 $ cd ..
32 32
33 33 Test pullbundle functionality
34 34
35 35 $ cd repo
36 36 $ cat <<EOF > .hg/hgrc
37 37 > [server]
38 38 > pullbundle = True
39 39 > [extensions]
40 40 > blackbox =
41 41 > EOF
42 42 $ hg bundle --base null -r 0 .hg/0.hg
43 43 1 changesets found
44 44 $ hg bundle --base 0 -r 1 .hg/1.hg
45 45 1 changesets found
46 46 $ hg bundle --base 1 -r 2 .hg/2.hg
47 47 1 changesets found
48 48 $ cat <<EOF > .hg/pullbundles.manifest
49 > 2.hg heads=effea6de0384e684f44435651cb7bd70b8735bd4 bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
50 > 1.hg heads=ed1b79f46b9a29f5a6efa59cf12fcfca43bead5a bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
51 > 0.hg heads=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
49 > 2.hg BUNDLESPEC=none-v2 heads=effea6de0384e684f44435651cb7bd70b8735bd4 bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
50 > 1.hg BUNDLESPEC=bzip2-v2 heads=ed1b79f46b9a29f5a6efa59cf12fcfca43bead5a bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
51 > 0.hg BUNDLESPEC=gzip-v2 heads=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
52 52 > EOF
53 53 $ hg --config blackbox.track=debug --debug serve -p $HGPORT2 -d --pid-file=../repo.pid
54 54 listening at http://*:$HGPORT2/ (bound to $LOCALIP:$HGPORT2) (glob) (?)
55 55 $ cat ../repo.pid >> $DAEMON_PIDS
56 56 $ cd ..
57 57 $ hg clone -r 0 http://localhost:$HGPORT2/ repo.pullbundle
58 58 adding changesets
59 59 adding manifests
60 60 adding file changes
61 61 added 1 changesets with 1 changes to 1 files
62 62 new changesets bbd179dfa0a7
63 63 updating to branch default
64 64 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 65 $ cd repo.pullbundle
66 66 $ hg pull -r 1
67 67 pulling from http://localhost:$HGPORT2/
68 68 searching for changes
69 69 adding changesets
70 70 adding manifests
71 71 adding file changes
72 72 added 1 changesets with 1 changes to 1 files
73 73 new changesets ed1b79f46b9a
74 74 (run 'hg update' to get a working copy)
75 75 $ hg pull -r 2
76 76 pulling from http://localhost:$HGPORT2/
77 77 searching for changes
78 78 adding changesets
79 79 adding manifests
80 80 adding file changes
81 81 added 1 changesets with 1 changes to 1 files (+1 heads)
82 82 new changesets effea6de0384
83 83 (run 'hg heads' to see heads, 'hg merge' to merge)
84 84 $ cd ..
85 85 $ killdaemons.py
86 86 $ grep 'sending pullbundle ' repo/.hg/blackbox.log
87 87 * sending pullbundle "0.hg" (glob)
88 88 * sending pullbundle "1.hg" (glob)
89 89 * sending pullbundle "2.hg" (glob)
90 90 $ rm repo/.hg/blackbox.log
91 91
92 92 Test pullbundle functionality for incremental pulls
93 93
94 94 $ cd repo
95 95 $ hg --config blackbox.track=debug --debug serve -p $HGPORT2 -d --pid-file=../repo.pid
96 96 listening at http://*:$HGPORT2/ (bound to $LOCALIP:$HGPORT2) (glob) (?)
97 97 $ cat ../repo.pid >> $DAEMON_PIDS
98 98 $ cd ..
99 99 $ hg clone http://localhost:$HGPORT2/ repo.pullbundle2
100 100 requesting all changes
101 101 adding changesets
102 102 adding manifests
103 103 adding file changes
104 104 added 1 changesets with 1 changes to 1 files
105 105 adding changesets
106 106 adding manifests
107 107 adding file changes
108 108 added 1 changesets with 1 changes to 1 files
109 109 adding changesets
110 110 adding manifests
111 111 adding file changes
112 112 added 1 changesets with 1 changes to 1 files (+1 heads)
113 113 new changesets bbd179dfa0a7:ed1b79f46b9a
114 114 updating to branch default
115 115 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 116 $ killdaemons.py
117 117 $ grep 'sending pullbundle ' repo/.hg/blackbox.log
118 118 * sending pullbundle "0.hg" (glob)
119 119 * sending pullbundle "2.hg" (glob)
120 120 * sending pullbundle "1.hg" (glob)
121 121 $ rm repo/.hg/blackbox.log
122 122
123 123 Test recovery from misconfigured server sending no new data
124 124
125 125 $ cd repo
126 126 $ cat <<EOF > .hg/pullbundles.manifest
127 127 > 0.hg heads=ed1b79f46b9a29f5a6efa59cf12fcfca43bead5a bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
128 128 > 0.hg heads=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
129 129 > EOF
130 130 $ hg --config blackbox.track=debug --debug serve -p $HGPORT2 -d --pid-file=../repo.pid
131 131 listening at http://*:$HGPORT2/ (bound to $LOCALIP:$HGPORT2) (glob) (?)
132 132 $ cat ../repo.pid >> $DAEMON_PIDS
133 133 $ cd ..
134 134 $ hg clone -r 0 http://localhost:$HGPORT2/ repo.pullbundle3
135 135 adding changesets
136 136 adding manifests
137 137 adding file changes
138 138 added 1 changesets with 1 changes to 1 files
139 139 new changesets bbd179dfa0a7
140 140 updating to branch default
141 141 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 142 $ cd repo.pullbundle3
143 143 $ hg pull -r 1
144 144 pulling from http://localhost:$HGPORT2/
145 145 searching for changes
146 146 adding changesets
147 147 adding manifests
148 148 adding file changes
149 149 added 0 changesets with 0 changes to 1 files
150 150 abort: 00changelog.i@ed1b79f46b9a: no node!
151 151 [255]
152 152 $ cd ..
153 153 $ killdaemons.py
154 154 $ grep 'sending pullbundle ' repo/.hg/blackbox.log
155 155 * sending pullbundle "0.hg" (glob)
156 156 * sending pullbundle "0.hg" (glob)
157 157 $ rm repo/.hg/blackbox.log
General Comments 0
You need to be logged in to leave comments. Login now