65 specialized circumstances. |
69 specialized circumstances. |
66 """ |
70 """ |
67 viewconfig = repo.ui.config('server', 'view') |
71 viewconfig = repo.ui.config('server', 'view') |
68 return repo.filtered(viewconfig) |
72 return repo.filtered(viewconfig) |
69 |
73 |
|
74 |
70 def dispatch(repo, proto, command): |
75 def dispatch(repo, proto, command): |
71 repo = getdispatchrepo(repo, proto, command) |
76 repo = getdispatchrepo(repo, proto, command) |
72 |
77 |
73 func, spec = commands[command] |
78 func, spec = commands[command] |
74 args = proto.getargs(spec) |
79 args = proto.getargs(spec) |
75 |
80 |
76 return func(repo, proto, *args) |
81 return func(repo, proto, *args) |
|
82 |
77 |
83 |
78 def options(cmd, keys, others): |
84 def options(cmd, keys, others): |
79 opts = {} |
85 opts = {} |
80 for k in keys: |
86 for k in keys: |
81 if k in others: |
87 if k in others: |
82 opts[k] = others[k] |
88 opts[k] = others[k] |
83 del others[k] |
89 del others[k] |
84 if others: |
90 if others: |
85 procutil.stderr.write("warning: %s ignored unexpected arguments %s\n" |
91 procutil.stderr.write( |
86 % (cmd, ",".join(others))) |
92 "warning: %s ignored unexpected arguments %s\n" |
|
93 % (cmd, ",".join(others)) |
|
94 ) |
87 return opts |
95 return opts |
|
96 |
88 |
97 |
89 def bundle1allowed(repo, action): |
98 def bundle1allowed(repo, action): |
90 """Whether a bundle1 operation is allowed from the server. |
99 """Whether a bundle1 operation is allowed from the server. |
91 |
100 |
92 Priority is: |
101 Priority is: |
130 Can be ``push`` or ``pull``. These roughly map to read-write and read-only, |
141 Can be ``push`` or ``pull``. These roughly map to read-write and read-only, |
131 respectively. Default is to assume command requires ``push`` permissions |
142 respectively. Default is to assume command requires ``push`` permissions |
132 because otherwise commands not declaring their permissions could modify |
143 because otherwise commands not declaring their permissions could modify |
133 a repository that is supposed to be read-only. |
144 a repository that is supposed to be read-only. |
134 """ |
145 """ |
135 transports = {k for k, v in wireprototypes.TRANSPORTS.items() |
146 transports = { |
136 if v['version'] == 1} |
147 k for k, v in wireprototypes.TRANSPORTS.items() if v['version'] == 1 |
|
148 } |
137 |
149 |
138 # Because SSHv2 is a mirror of SSHv1, we allow "batch" commands through to |
150 # Because SSHv2 is a mirror of SSHv1, we allow "batch" commands through to |
139 # SSHv2. |
151 # SSHv2. |
140 # TODO undo this hack when SSH is using the unified frame protocol. |
152 # TODO undo this hack when SSH is using the unified frame protocol. |
141 if name == b'batch': |
153 if name == b'batch': |
142 transports.add(wireprototypes.SSHV2) |
154 transports.add(wireprototypes.SSHV2) |
143 |
155 |
144 if permission not in ('push', 'pull'): |
156 if permission not in ('push', 'pull'): |
145 raise error.ProgrammingError('invalid wire protocol permission; ' |
157 raise error.ProgrammingError( |
146 'got %s; expected "push" or "pull"' % |
158 'invalid wire protocol permission; ' |
147 permission) |
159 'got %s; expected "push" or "pull"' % permission |
|
160 ) |
148 |
161 |
149 if args is None: |
162 if args is None: |
150 args = '' |
163 args = '' |
151 |
164 |
152 if not isinstance(args, bytes): |
165 if not isinstance(args, bytes): |
153 raise error.ProgrammingError('arguments for version 1 commands ' |
166 raise error.ProgrammingError( |
154 'must be declared as bytes') |
167 'arguments for version 1 commands ' 'must be declared as bytes' |
|
168 ) |
155 |
169 |
156 def register(func): |
170 def register(func): |
157 if name in commands: |
171 if name in commands: |
158 raise error.ProgrammingError('%s command already registered ' |
172 raise error.ProgrammingError( |
159 'for version 1' % name) |
173 '%s command already registered ' 'for version 1' % name |
|
174 ) |
160 commands[name] = wireprototypes.commandentry( |
175 commands[name] = wireprototypes.commandentry( |
161 func, args=args, transports=transports, permission=permission) |
176 func, args=args, transports=transports, permission=permission |
|
177 ) |
162 |
178 |
163 return func |
179 return func |
|
180 |
164 return register |
181 return register |
|
182 |
165 |
183 |
166 # TODO define a more appropriate permissions type to use for this. |
184 # TODO define a more appropriate permissions type to use for this. |
167 @wireprotocommand('batch', 'cmds *', permission='pull') |
185 @wireprotocommand('batch', 'cmds *', permission='pull') |
168 def batch(repo, proto, cmds, others): |
186 def batch(repo, proto, cmds, others): |
169 unescapearg = wireprototypes.unescapebatcharg |
187 unescapearg = wireprototypes.unescapebatcharg |
207 result = result.data |
225 result = result.data |
208 res.append(wireprototypes.escapebatcharg(result)) |
226 res.append(wireprototypes.escapebatcharg(result)) |
209 |
227 |
210 return wireprototypes.bytesresponse(';'.join(res)) |
228 return wireprototypes.bytesresponse(';'.join(res)) |
211 |
229 |
|
230 |
212 @wireprotocommand('between', 'pairs', permission='pull') |
231 @wireprotocommand('between', 'pairs', permission='pull') |
213 def between(repo, proto, pairs): |
232 def between(repo, proto, pairs): |
214 pairs = [wireprototypes.decodelist(p, '-') for p in pairs.split(" ")] |
233 pairs = [wireprototypes.decodelist(p, '-') for p in pairs.split(" ")] |
215 r = [] |
234 r = [] |
216 for b in repo.between(pairs): |
235 for b in repo.between(pairs): |
217 r.append(wireprototypes.encodelist(b) + "\n") |
236 r.append(wireprototypes.encodelist(b) + "\n") |
218 |
237 |
219 return wireprototypes.bytesresponse(''.join(r)) |
238 return wireprototypes.bytesresponse(''.join(r)) |
|
239 |
220 |
240 |
221 @wireprotocommand('branchmap', permission='pull') |
241 @wireprotocommand('branchmap', permission='pull') |
222 def branchmap(repo, proto): |
242 def branchmap(repo, proto): |
223 branchmap = repo.branchmap() |
243 branchmap = repo.branchmap() |
224 heads = [] |
244 heads = [] |
227 branchnodes = wireprototypes.encodelist(nodes) |
247 branchnodes = wireprototypes.encodelist(nodes) |
228 heads.append('%s %s' % (branchname, branchnodes)) |
248 heads.append('%s %s' % (branchname, branchnodes)) |
229 |
249 |
230 return wireprototypes.bytesresponse('\n'.join(heads)) |
250 return wireprototypes.bytesresponse('\n'.join(heads)) |
231 |
251 |
|
252 |
232 @wireprotocommand('branches', 'nodes', permission='pull') |
253 @wireprotocommand('branches', 'nodes', permission='pull') |
233 def branches(repo, proto, nodes): |
254 def branches(repo, proto, nodes): |
234 nodes = wireprototypes.decodelist(nodes) |
255 nodes = wireprototypes.decodelist(nodes) |
235 r = [] |
256 r = [] |
236 for b in repo.branches(nodes): |
257 for b in repo.branches(nodes): |
237 r.append(wireprototypes.encodelist(b) + "\n") |
258 r.append(wireprototypes.encodelist(b) + "\n") |
238 |
259 |
239 return wireprototypes.bytesresponse(''.join(r)) |
260 return wireprototypes.bytesresponse(''.join(r)) |
240 |
261 |
|
262 |
241 @wireprotocommand('clonebundles', '', permission='pull') |
263 @wireprotocommand('clonebundles', '', permission='pull') |
242 def clonebundles(repo, proto): |
264 def clonebundles(repo, proto): |
243 """Server command for returning info for available bundles to seed clones. |
265 """Server command for returning info for available bundles to seed clones. |
244 |
266 |
245 Clients will parse this response and determine what bundle to fetch. |
267 Clients will parse this response and determine what bundle to fetch. |
292 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'): |
323 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'): |
293 caps.append(wireprototypes.ELLIPSESCAP) |
324 caps.append(wireprototypes.ELLIPSESCAP) |
294 |
325 |
295 return proto.addcapabilities(repo, caps) |
326 return proto.addcapabilities(repo, caps) |
296 |
327 |
|
328 |
297 # If you are writing an extension and consider wrapping this function. Wrap |
329 # If you are writing an extension and consider wrapping this function. Wrap |
298 # `_capabilities` instead. |
330 # `_capabilities` instead. |
299 @wireprotocommand('capabilities', permission='pull') |
331 @wireprotocommand('capabilities', permission='pull') |
300 def capabilities(repo, proto): |
332 def capabilities(repo, proto): |
301 caps = _capabilities(repo, proto) |
333 caps = _capabilities(repo, proto) |
302 return wireprototypes.bytesresponse(' '.join(sorted(caps))) |
334 return wireprototypes.bytesresponse(' '.join(sorted(caps))) |
303 |
335 |
|
336 |
304 @wireprotocommand('changegroup', 'roots', permission='pull') |
337 @wireprotocommand('changegroup', 'roots', permission='pull') |
305 def changegroup(repo, proto, roots): |
338 def changegroup(repo, proto, roots): |
306 nodes = wireprototypes.decodelist(roots) |
339 nodes = wireprototypes.decodelist(roots) |
307 outgoing = discovery.outgoing(repo, missingroots=nodes, |
340 outgoing = discovery.outgoing( |
308 missingheads=repo.heads()) |
341 repo, missingroots=nodes, missingheads=repo.heads() |
|
342 ) |
309 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve') |
343 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve') |
310 gen = iter(lambda: cg.read(32768), '') |
344 gen = iter(lambda: cg.read(32768), '') |
311 return wireprototypes.streamres(gen=gen) |
345 return wireprototypes.streamres(gen=gen) |
312 |
346 |
313 @wireprotocommand('changegroupsubset', 'bases heads', |
347 |
314 permission='pull') |
348 @wireprotocommand('changegroupsubset', 'bases heads', permission='pull') |
315 def changegroupsubset(repo, proto, bases, heads): |
349 def changegroupsubset(repo, proto, bases, heads): |
316 bases = wireprototypes.decodelist(bases) |
350 bases = wireprototypes.decodelist(bases) |
317 heads = wireprototypes.decodelist(heads) |
351 heads = wireprototypes.decodelist(heads) |
318 outgoing = discovery.outgoing(repo, missingroots=bases, |
352 outgoing = discovery.outgoing(repo, missingroots=bases, missingheads=heads) |
319 missingheads=heads) |
|
320 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve') |
353 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve') |
321 gen = iter(lambda: cg.read(32768), '') |
354 gen = iter(lambda: cg.read(32768), '') |
322 return wireprototypes.streamres(gen=gen) |
355 return wireprototypes.streamres(gen=gen) |
323 |
356 |
324 @wireprotocommand('debugwireargs', 'one two *', |
357 |
325 permission='pull') |
358 @wireprotocommand('debugwireargs', 'one two *', permission='pull') |
326 def debugwireargs(repo, proto, one, two, others): |
359 def debugwireargs(repo, proto, one, two, others): |
327 # only accept optional args from the known set |
360 # only accept optional args from the known set |
328 opts = options('debugwireargs', ['three', 'four'], others) |
361 opts = options('debugwireargs', ['three', 'four'], others) |
329 return wireprototypes.bytesresponse(repo.debugwireargs( |
362 return wireprototypes.bytesresponse( |
330 one, two, **pycompat.strkwargs(opts))) |
363 repo.debugwireargs(one, two, **pycompat.strkwargs(opts)) |
|
364 ) |
|
365 |
331 |
366 |
332 def find_pullbundle(repo, proto, opts, clheads, heads, common): |
367 def find_pullbundle(repo, proto, opts, clheads, heads, common): |
333 """Return a file object for the first matching pullbundle. |
368 """Return a file object for the first matching pullbundle. |
334 |
369 |
335 Pullbundles are specified in .hg/pullbundles.manifest similar to |
370 Pullbundles are specified in .hg/pullbundles.manifest similar to |
413 if v == '0': |
453 if v == '0': |
414 opts[k] = False |
454 opts[k] = False |
415 else: |
455 else: |
416 opts[k] = bool(v) |
456 opts[k] = bool(v) |
417 elif keytype != 'plain': |
457 elif keytype != 'plain': |
418 raise KeyError('unknown getbundle option type %s' |
458 raise KeyError('unknown getbundle option type %s' % keytype) |
419 % keytype) |
|
420 |
459 |
421 if not bundle1allowed(repo, 'pull'): |
460 if not bundle1allowed(repo, 'pull'): |
422 if not exchange.bundle2requested(opts.get('bundlecaps')): |
461 if not exchange.bundle2requested(opts.get('bundlecaps')): |
423 if proto.name == 'http-v1': |
462 if proto.name == 'http-v1': |
424 return wireprototypes.ooberror(bundle2required) |
463 return wireprototypes.ooberror(bundle2required) |
425 raise error.Abort(bundle2requiredmain, |
464 raise error.Abort(bundle2requiredmain, hint=bundle2requiredhint) |
426 hint=bundle2requiredhint) |
|
427 |
465 |
428 try: |
466 try: |
429 clheads = set(repo.changelog.heads()) |
467 clheads = set(repo.changelog.heads()) |
430 heads = set(opts.get('heads', set())) |
468 heads = set(opts.get('heads', set())) |
431 common = set(opts.get('common', set())) |
469 common = set(opts.get('common', set())) |
432 common.discard(nullid) |
470 common.discard(nullid) |
433 if (repo.ui.configbool('server', 'pullbundle') and |
471 if ( |
434 'partial-pull' in proto.getprotocaps()): |
472 repo.ui.configbool('server', 'pullbundle') |
|
473 and 'partial-pull' in proto.getprotocaps() |
|
474 ): |
435 # Check if a pre-built bundle covers this request. |
475 # Check if a pre-built bundle covers this request. |
436 bundle = find_pullbundle(repo, proto, opts, clheads, heads, common) |
476 bundle = find_pullbundle(repo, proto, opts, clheads, heads, common) |
437 if bundle: |
477 if bundle: |
438 return wireprototypes.streamres(gen=util.filechunkiter(bundle), |
478 return wireprototypes.streamres( |
439 prefer_uncompressed=True) |
479 gen=util.filechunkiter(bundle), prefer_uncompressed=True |
|
480 ) |
440 |
481 |
441 if repo.ui.configbool('server', 'disablefullbundle'): |
482 if repo.ui.configbool('server', 'disablefullbundle'): |
442 # Check to see if this is a full clone. |
483 # Check to see if this is a full clone. |
443 changegroup = opts.get('cg', True) |
484 changegroup = opts.get('cg', True) |
444 if changegroup and not common and clheads == heads: |
485 if changegroup and not common and clheads == heads: |
445 raise error.Abort( |
486 raise error.Abort( |
446 _('server has pull-based clones disabled'), |
487 _('server has pull-based clones disabled'), |
447 hint=_('remove --pull if specified or upgrade Mercurial')) |
488 hint=_('remove --pull if specified or upgrade Mercurial'), |
448 |
489 ) |
449 info, chunks = exchange.getbundlechunks(repo, 'serve', |
490 |
450 **pycompat.strkwargs(opts)) |
491 info, chunks = exchange.getbundlechunks( |
|
492 repo, 'serve', **pycompat.strkwargs(opts) |
|
493 ) |
451 prefercompressed = info.get('prefercompressed', True) |
494 prefercompressed = info.get('prefercompressed', True) |
452 except error.Abort as exc: |
495 except error.Abort as exc: |
453 # cleanly forward Abort error to the client |
496 # cleanly forward Abort error to the client |
454 if not exchange.bundle2requested(opts.get('bundlecaps')): |
497 if not exchange.bundle2requested(opts.get('bundlecaps')): |
455 if proto.name == 'http-v1': |
498 if proto.name == 'http-v1': |
456 return wireprototypes.ooberror(pycompat.bytestr(exc) + '\n') |
499 return wireprototypes.ooberror(pycompat.bytestr(exc) + '\n') |
457 raise # cannot do better for bundle1 + ssh |
500 raise # cannot do better for bundle1 + ssh |
458 # bundle2 request expect a bundle2 reply |
501 # bundle2 request expect a bundle2 reply |
459 bundler = bundle2.bundle20(repo.ui) |
502 bundler = bundle2.bundle20(repo.ui) |
460 manargs = [('message', pycompat.bytestr(exc))] |
503 manargs = [('message', pycompat.bytestr(exc))] |
461 advargs = [] |
504 advargs = [] |
462 if exc.hint is not None: |
505 if exc.hint is not None: |
463 advargs.append(('hint', exc.hint)) |
506 advargs.append(('hint', exc.hint)) |
464 bundler.addpart(bundle2.bundlepart('error:abort', |
507 bundler.addpart(bundle2.bundlepart('error:abort', manargs, advargs)) |
465 manargs, advargs)) |
|
466 chunks = bundler.getchunks() |
508 chunks = bundler.getchunks() |
467 prefercompressed = False |
509 prefercompressed = False |
468 |
510 |
469 return wireprototypes.streamres( |
511 return wireprototypes.streamres( |
470 gen=chunks, prefer_uncompressed=not prefercompressed) |
512 gen=chunks, prefer_uncompressed=not prefercompressed |
|
513 ) |
|
514 |
471 |
515 |
472 @wireprotocommand('heads', permission='pull') |
516 @wireprotocommand('heads', permission='pull') |
473 def heads(repo, proto): |
517 def heads(repo, proto): |
474 h = repo.heads() |
518 h = repo.heads() |
475 return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + '\n') |
519 return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + '\n') |
476 |
520 |
|
521 |
477 @wireprotocommand('hello', permission='pull') |
522 @wireprotocommand('hello', permission='pull') |
478 def hello(repo, proto): |
523 def hello(repo, proto): |
479 """Called as part of SSH handshake to obtain server info. |
524 """Called as part of SSH handshake to obtain server info. |
480 |
525 |
481 Returns a list of lines describing interesting things about the |
526 Returns a list of lines describing interesting things about the |
487 capabilities: <token0> <token1> <token2> |
532 capabilities: <token0> <token1> <token2> |
488 """ |
533 """ |
489 caps = capabilities(repo, proto).data |
534 caps = capabilities(repo, proto).data |
490 return wireprototypes.bytesresponse('capabilities: %s\n' % caps) |
535 return wireprototypes.bytesresponse('capabilities: %s\n' % caps) |
491 |
536 |
|
537 |
492 @wireprotocommand('listkeys', 'namespace', permission='pull') |
538 @wireprotocommand('listkeys', 'namespace', permission='pull') |
493 def listkeys(repo, proto, namespace): |
539 def listkeys(repo, proto, namespace): |
494 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items()) |
540 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items()) |
495 return wireprototypes.bytesresponse(pushkeymod.encodekeys(d)) |
541 return wireprototypes.bytesresponse(pushkeymod.encodekeys(d)) |
|
542 |
496 |
543 |
497 @wireprotocommand('lookup', 'key', permission='pull') |
544 @wireprotocommand('lookup', 'key', permission='pull') |
498 def lookup(repo, proto, key): |
545 def lookup(repo, proto, key): |
499 try: |
546 try: |
500 k = encoding.tolocal(key) |
547 k = encoding.tolocal(key) |
504 except Exception as inst: |
551 except Exception as inst: |
505 r = stringutil.forcebytestr(inst) |
552 r = stringutil.forcebytestr(inst) |
506 success = 0 |
553 success = 0 |
507 return wireprototypes.bytesresponse('%d %s\n' % (success, r)) |
554 return wireprototypes.bytesresponse('%d %s\n' % (success, r)) |
508 |
555 |
|
556 |
509 @wireprotocommand('known', 'nodes *', permission='pull') |
557 @wireprotocommand('known', 'nodes *', permission='pull') |
510 def known(repo, proto, nodes, others): |
558 def known(repo, proto, nodes, others): |
511 v = ''.join(b and '1' or '0' |
559 v = ''.join( |
512 for b in repo.known(wireprototypes.decodelist(nodes))) |
560 b and '1' or '0' for b in repo.known(wireprototypes.decodelist(nodes)) |
|
561 ) |
513 return wireprototypes.bytesresponse(v) |
562 return wireprototypes.bytesresponse(v) |
|
563 |
514 |
564 |
515 @wireprotocommand('protocaps', 'caps', permission='pull') |
565 @wireprotocommand('protocaps', 'caps', permission='pull') |
516 def protocaps(repo, proto, caps): |
566 def protocaps(repo, proto, caps): |
517 if proto.name == wireprototypes.SSHV1: |
567 if proto.name == wireprototypes.SSHV1: |
518 proto._protocaps = set(caps.split(' ')) |
568 proto._protocaps = set(caps.split(' ')) |
519 return wireprototypes.bytesresponse('OK') |
569 return wireprototypes.bytesresponse('OK') |
|
570 |
520 |
571 |
521 @wireprotocommand('pushkey', 'namespace key old new', permission='push') |
572 @wireprotocommand('pushkey', 'namespace key old new', permission='push') |
522 def pushkey(repo, proto, namespace, key, old, new): |
573 def pushkey(repo, proto, namespace, key, old, new): |
523 # compatibility with pre-1.8 clients which were accidentally |
574 # compatibility with pre-1.8 clients which were accidentally |
524 # sending raw binary nodes rather than utf-8-encoded hex |
575 # sending raw binary nodes rather than utf-8-encoded hex |
525 if len(new) == 20 and stringutil.escapestr(new) != new: |
576 if len(new) == 20 and stringutil.escapestr(new) != new: |
526 # looks like it could be a binary node |
577 # looks like it could be a binary node |
527 try: |
578 try: |
528 new.decode('utf-8') |
579 new.decode('utf-8') |
529 new = encoding.tolocal(new) # but cleanly decodes as UTF-8 |
580 new = encoding.tolocal(new) # but cleanly decodes as UTF-8 |
530 except UnicodeDecodeError: |
581 except UnicodeDecodeError: |
531 pass # binary, leave unmodified |
582 pass # binary, leave unmodified |
532 else: |
583 else: |
533 new = encoding.tolocal(new) # normal path |
584 new = encoding.tolocal(new) # normal path |
534 |
585 |
535 with proto.mayberedirectstdio() as output: |
586 with proto.mayberedirectstdio() as output: |
536 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), |
587 r = ( |
537 encoding.tolocal(old), new) or False |
588 repo.pushkey( |
|
589 encoding.tolocal(namespace), |
|
590 encoding.tolocal(key), |
|
591 encoding.tolocal(old), |
|
592 new, |
|
593 ) |
|
594 or False |
|
595 ) |
538 |
596 |
539 output = output.getvalue() if output else '' |
597 output = output.getvalue() if output else '' |
540 return wireprototypes.bytesresponse('%d\n%s' % (int(r), output)) |
598 return wireprototypes.bytesresponse('%d\n%s' % (int(r), output)) |
|
599 |
541 |
600 |
542 @wireprotocommand('stream_out', permission='pull') |
601 @wireprotocommand('stream_out', permission='pull') |
543 def stream(repo, proto): |
602 def stream(repo, proto): |
544 '''If the server supports streaming clone, it advertises the "stream" |
603 '''If the server supports streaming clone, it advertises the "stream" |
545 capability with a value representing the version and flags of the repo |
604 capability with a value representing the version and flags of the repo |
546 it is serving. Client checks to see if it understands the format. |
605 it is serving. Client checks to see if it understands the format. |
547 ''' |
606 ''' |
548 return wireprototypes.streamreslegacy( |
607 return wireprototypes.streamreslegacy(streamclone.generatev1wireproto(repo)) |
549 streamclone.generatev1wireproto(repo)) |
608 |
550 |
609 |
551 @wireprotocommand('unbundle', 'heads', permission='push') |
610 @wireprotocommand('unbundle', 'heads', permission='push') |
552 def unbundle(repo, proto, heads): |
611 def unbundle(repo, proto, heads): |
553 their_heads = wireprototypes.decodelist(heads) |
612 their_heads = wireprototypes.decodelist(heads) |
554 |
613 |
557 exchange.check_heads(repo, their_heads, 'preparing changes') |
616 exchange.check_heads(repo, their_heads, 'preparing changes') |
558 cleanup = lambda: None |
617 cleanup = lambda: None |
559 try: |
618 try: |
560 payload = proto.getpayload() |
619 payload = proto.getpayload() |
561 if repo.ui.configbool('server', 'streamunbundle'): |
620 if repo.ui.configbool('server', 'streamunbundle'): |
|
621 |
562 def cleanup(): |
622 def cleanup(): |
563 # Ensure that the full payload is consumed, so |
623 # Ensure that the full payload is consumed, so |
564 # that the connection doesn't contain trailing garbage. |
624 # that the connection doesn't contain trailing garbage. |
565 for p in payload: |
625 for p in payload: |
566 pass |
626 pass |
|
627 |
567 fp = util.chunkbuffer(payload) |
628 fp = util.chunkbuffer(payload) |
568 else: |
629 else: |
569 # write bundle data to temporary file as it can be big |
630 # write bundle data to temporary file as it can be big |
570 fp, tempname = None, None |
631 fp, tempname = None, None |
|
632 |
571 def cleanup(): |
633 def cleanup(): |
572 if fp: |
634 if fp: |
573 fp.close() |
635 fp.close() |
574 if tempname: |
636 if tempname: |
575 os.unlink(tempname) |
637 os.unlink(tempname) |
|
638 |
576 fd, tempname = pycompat.mkstemp(prefix='hg-unbundle-') |
639 fd, tempname = pycompat.mkstemp(prefix='hg-unbundle-') |
577 repo.ui.debug('redirecting incoming bundle to %s\n' % |
640 repo.ui.debug( |
578 tempname) |
641 'redirecting incoming bundle to %s\n' % tempname |
|
642 ) |
579 fp = os.fdopen(fd, pycompat.sysstr('wb+')) |
643 fp = os.fdopen(fd, pycompat.sysstr('wb+')) |
580 for p in payload: |
644 for p in payload: |
581 fp.write(p) |
645 fp.write(p) |
582 fp.seek(0) |
646 fp.seek(0) |
583 |
647 |
584 gen = exchange.readbundle(repo.ui, fp, None) |
648 gen = exchange.readbundle(repo.ui, fp, None) |
585 if (isinstance(gen, changegroupmod.cg1unpacker) |
649 if isinstance( |
586 and not bundle1allowed(repo, 'push')): |
650 gen, changegroupmod.cg1unpacker |
|
651 ) and not bundle1allowed(repo, 'push'): |
587 if proto.name == 'http-v1': |
652 if proto.name == 'http-v1': |
588 # need to special case http because stderr do not get to |
653 # need to special case http because stderr do not get to |
589 # the http client on failed push so we need to abuse |
654 # the http client on failed push so we need to abuse |
590 # some other error type to make sure the message get to |
655 # some other error type to make sure the message get to |
591 # the user. |
656 # the user. |
592 return wireprototypes.ooberror(bundle2required) |
657 return wireprototypes.ooberror(bundle2required) |
593 raise error.Abort(bundle2requiredmain, |
658 raise error.Abort( |
594 hint=bundle2requiredhint) |
659 bundle2requiredmain, hint=bundle2requiredhint |
595 |
660 ) |
596 r = exchange.unbundle(repo, gen, their_heads, 'serve', |
661 |
597 proto.client()) |
662 r = exchange.unbundle( |
|
663 repo, gen, their_heads, 'serve', proto.client() |
|
664 ) |
598 if util.safehasattr(r, 'addpart'): |
665 if util.safehasattr(r, 'addpart'): |
599 # The return looks streamable, we are in the bundle2 case |
666 # The return looks streamable, we are in the bundle2 case |
600 # and should return a stream. |
667 # and should return a stream. |
601 return wireprototypes.streamreslegacy(gen=r.getchunks()) |
668 return wireprototypes.streamreslegacy(gen=r.getchunks()) |
602 return wireprototypes.pushres( |
669 return wireprototypes.pushres( |
603 r, output.getvalue() if output else '') |
670 r, output.getvalue() if output else '' |
|
671 ) |
604 |
672 |
605 finally: |
673 finally: |
606 cleanup() |
674 cleanup() |
607 |
675 |
608 except (error.BundleValueError, error.Abort, error.PushRaced) as exc: |
676 except (error.BundleValueError, error.Abort, error.PushRaced) as exc: |
618 procutil.stderr.write("abort: %s\n" % exc) |
686 procutil.stderr.write("abort: %s\n" % exc) |
619 if exc.hint is not None: |
687 if exc.hint is not None: |
620 procutil.stderr.write("(%s)\n" % exc.hint) |
688 procutil.stderr.write("(%s)\n" % exc.hint) |
621 procutil.stderr.flush() |
689 procutil.stderr.flush() |
622 return wireprototypes.pushres( |
690 return wireprototypes.pushres( |
623 0, output.getvalue() if output else '') |
691 0, output.getvalue() if output else '' |
|
692 ) |
624 except error.PushRaced: |
693 except error.PushRaced: |
625 return wireprototypes.pusherr( |
694 return wireprototypes.pusherr( |
626 pycompat.bytestr(exc), |
695 pycompat.bytestr(exc), |
627 output.getvalue() if output else '') |
696 output.getvalue() if output else '', |
|
697 ) |
628 |
698 |
629 bundler = bundle2.bundle20(repo.ui) |
699 bundler = bundle2.bundle20(repo.ui) |
630 for out in getattr(exc, '_bundle2salvagedoutput', ()): |
700 for out in getattr(exc, '_bundle2salvagedoutput', ()): |
631 bundler.addpart(out) |
701 bundler.addpart(out) |
632 try: |
702 try: |
633 try: |
703 try: |
634 raise |
704 raise |
635 except error.PushkeyFailed as exc: |
705 except error.PushkeyFailed as exc: |
636 # check client caps |
706 # check client caps |
637 remotecaps = getattr(exc, '_replycaps', None) |
707 remotecaps = getattr(exc, '_replycaps', None) |
638 if (remotecaps is not None |
708 if ( |
639 and 'pushkey' not in remotecaps.get('error', ())): |
709 remotecaps is not None |
|
710 and 'pushkey' not in remotecaps.get('error', ()) |
|
711 ): |
640 # no support remote side, fallback to Abort handler. |
712 # no support remote side, fallback to Abort handler. |
641 raise |
713 raise |
642 part = bundler.newpart('error:pushkey') |
714 part = bundler.newpart('error:pushkey') |
643 part.addparam('in-reply-to', exc.partid) |
715 part.addparam('in-reply-to', exc.partid) |
644 if exc.namespace is not None: |
716 if exc.namespace is not None: |
645 part.addparam('namespace', exc.namespace, |
717 part.addparam( |
646 mandatory=False) |
718 'namespace', exc.namespace, mandatory=False |
|
719 ) |
647 if exc.key is not None: |
720 if exc.key is not None: |
648 part.addparam('key', exc.key, mandatory=False) |
721 part.addparam('key', exc.key, mandatory=False) |
649 if exc.new is not None: |
722 if exc.new is not None: |
650 part.addparam('new', exc.new, mandatory=False) |
723 part.addparam('new', exc.new, mandatory=False) |
651 if exc.old is not None: |
724 if exc.old is not None: |