Mercurial > hg
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()) |