comparison mercurial/wireprotov1server.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents 566daffc607d
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
37 37
38 urlerr = util.urlerr 38 urlerr = util.urlerr
39 urlreq = util.urlreq 39 urlreq = util.urlreq
40 40
41 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required') 41 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
42 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/' 42 bundle2requiredhint = _(
43 'IncompatibleClient') 43 'see https://www.mercurial-scm.org/wiki/' 'IncompatibleClient'
44 )
44 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint) 45 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
46
45 47
46 def clientcompressionsupport(proto): 48 def clientcompressionsupport(proto):
47 """Returns a list of compression methods supported by the client. 49 """Returns a list of compression methods supported by the client.
48 50
49 Returns a list of the compression methods supported by the client 51 Returns a list of the compression methods supported by the client
53 for cap in proto.getprotocaps(): 55 for cap in proto.getprotocaps():
54 if cap.startswith('comp='): 56 if cap.startswith('comp='):
55 return cap[5:].split(',') 57 return cap[5:].split(',')
56 return ['zlib', 'none'] 58 return ['zlib', 'none']
57 59
60
58 # wire protocol command can either return a string or one of these classes. 61 # wire protocol command can either return a string or one of these classes.
62
59 63
60 def getdispatchrepo(repo, proto, command): 64 def getdispatchrepo(repo, proto, command):
61 """Obtain the repo used for processing wire protocol commands. 65 """Obtain the repo used for processing wire protocol commands.
62 66
63 The intent of this function is to serve as a monkeypatch point for 67 The intent of this function is to serve as a monkeypatch point for
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:
113 if v is not None: 122 if v is not None:
114 return v 123 return v
115 124
116 return ui.configbool('server', 'bundle1') 125 return ui.configbool('server', 'bundle1')
117 126
127
118 commands = wireprototypes.commanddict() 128 commands = wireprototypes.commanddict()
129
119 130
120 def wireprotocommand(name, args=None, permission='push'): 131 def wireprotocommand(name, args=None, permission='push'):
121 """Decorator to declare a wire protocol command. 132 """Decorator to declare a wire protocol command.
122 133
123 ``name`` is the name of the wire protocol command being provided. 134 ``name`` is the name of the wire protocol command being provided.
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.
247 Extensions may wrap this command to filter or dynamically emit data 269 Extensions may wrap this command to filter or dynamically emit data
248 depending on the request. e.g. you could advertise URLs for the closest 270 depending on the request. e.g. you could advertise URLs for the closest
249 data center given the client's IP address. 271 data center given the client's IP address.
250 """ 272 """
251 return wireprototypes.bytesresponse( 273 return wireprototypes.bytesresponse(
252 repo.vfs.tryread('clonebundles.manifest')) 274 repo.vfs.tryread('clonebundles.manifest')
253 275 )
254 wireprotocaps = ['lookup', 'branchmap', 'pushkey', 276
255 'known', 'getbundle', 'unbundlehash'] 277
278 wireprotocaps = [
279 'lookup',
280 'branchmap',
281 'pushkey',
282 'known',
283 'getbundle',
284 'unbundlehash',
285 ]
286
256 287
257 def _capabilities(repo, proto): 288 def _capabilities(repo, proto):
258 """return a list of capabilities for a repo 289 """return a list of capabilities for a repo
259 290
260 This function exists to allow extensions to easily wrap capabilities 291 This function exists to allow extensions to easily wrap capabilities
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
342 - At least one leaf of the bundle's DAG is missing on the client. 377 - At least one leaf of the bundle's DAG is missing on the client.
343 - Every leaf of the bundle's DAG is part of node set the client wants. 378 - Every leaf of the bundle's DAG is part of node set the client wants.
344 E.g. do not send a bundle of all changes if the client wants only 379 E.g. do not send a bundle of all changes if the client wants only
345 one specific branch of many. 380 one specific branch of many.
346 """ 381 """
382
347 def decodehexstring(s): 383 def decodehexstring(s):
348 return {binascii.unhexlify(h) for h in s.split(';')} 384 return {binascii.unhexlify(h) for h in s.split(';')}
349 385
350 manifest = repo.vfs.tryread('pullbundles.manifest') 386 manifest = repo.vfs.tryread('pullbundles.manifest')
351 if not manifest: 387 if not manifest:
370 bundle_heads = decodehexstring(entry['heads']) 406 bundle_heads = decodehexstring(entry['heads'])
371 except TypeError: 407 except TypeError:
372 # Bad heads entry 408 # Bad heads entry
373 continue 409 continue
374 if bundle_heads.issubset(common): 410 if bundle_heads.issubset(common):
375 continue # Nothing new 411 continue # Nothing new
376 if all(cl.rev(rev) in common_anc for rev in bundle_heads): 412 if all(cl.rev(rev) in common_anc for rev in bundle_heads):
377 continue # Still nothing new 413 continue # Still nothing new
378 if any(cl.rev(rev) not in heads_anc and 414 if any(
379 cl.rev(rev) not in common_anc for rev in bundle_heads): 415 cl.rev(rev) not in heads_anc and cl.rev(rev) not in common_anc
416 for rev in bundle_heads
417 ):
380 continue 418 continue
381 if 'bases' in entry: 419 if 'bases' in entry:
382 try: 420 try:
383 bundle_bases = decodehexstring(entry['bases']) 421 bundle_bases = decodehexstring(entry['bases'])
384 except TypeError: 422 except TypeError:
393 except IOError: 431 except IOError:
394 repo.ui.debug('pullbundle "%s" not accessible\n' % path) 432 repo.ui.debug('pullbundle "%s" not accessible\n' % path)
395 continue 433 continue
396 return None 434 return None
397 435
436
398 @wireprotocommand('getbundle', '*', permission='pull') 437 @wireprotocommand('getbundle', '*', permission='pull')
399 def getbundle(repo, proto, others): 438 def getbundle(repo, proto, others):
400 opts = options('getbundle', wireprototypes.GETBUNDLE_ARGUMENTS.keys(), 439 opts = options(
401 others) 440 'getbundle', wireprototypes.GETBUNDLE_ARGUMENTS.keys(), others
441 )
402 for k, v in opts.iteritems(): 442 for k, v in opts.iteritems():
403 keytype = wireprototypes.GETBUNDLE_ARGUMENTS[k] 443 keytype = wireprototypes.GETBUNDLE_ARGUMENTS[k]
404 if keytype == 'nodes': 444 if keytype == 'nodes':
405 opts[k] = wireprototypes.decodelist(v) 445 opts[k] = wireprototypes.decodelist(v)
406 elif keytype == 'csv': 446 elif keytype == 'csv':
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:
661 except error.Abort as exc: 734 except error.Abort as exc:
662 manargs = [('message', stringutil.forcebytestr(exc))] 735 manargs = [('message', stringutil.forcebytestr(exc))]
663 advargs = [] 736 advargs = []
664 if exc.hint is not None: 737 if exc.hint is not None:
665 advargs.append(('hint', exc.hint)) 738 advargs.append(('hint', exc.hint))
666 bundler.addpart(bundle2.bundlepart('error:abort', 739 bundler.addpart(
667 manargs, advargs)) 740 bundle2.bundlepart('error:abort', manargs, advargs)
741 )
668 except error.PushRaced as exc: 742 except error.PushRaced as exc:
669 bundler.newpart('error:pushraced', 743 bundler.newpart(
670 [('message', stringutil.forcebytestr(exc))]) 744 'error:pushraced',
745 [('message', stringutil.forcebytestr(exc))],
746 )
671 return wireprototypes.streamreslegacy(gen=bundler.getchunks()) 747 return wireprototypes.streamreslegacy(gen=bundler.getchunks())