142 respectively. Default is to assume command requires ``push`` permissions |
142 respectively. Default is to assume command requires ``push`` permissions |
143 because otherwise commands not declaring their permissions could modify |
143 because otherwise commands not declaring their permissions could modify |
144 a repository that is supposed to be read-only. |
144 a repository that is supposed to be read-only. |
145 """ |
145 """ |
146 transports = { |
146 transports = { |
147 k for k, v in wireprototypes.TRANSPORTS.items() if v['version'] == 1 |
147 k for k, v in wireprototypes.TRANSPORTS.items() if v[b'version'] == 1 |
148 } |
148 } |
149 |
149 |
150 # 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 |
151 # SSHv2. |
151 # SSHv2. |
152 # 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. |
153 if name == b'batch': |
153 if name == b'batch': |
154 transports.add(wireprototypes.SSHV2) |
154 transports.add(wireprototypes.SSHV2) |
155 |
155 |
156 if permission not in ('push', 'pull'): |
156 if permission not in (b'push', b'pull'): |
157 raise error.ProgrammingError( |
157 raise error.ProgrammingError( |
158 'invalid wire protocol permission; ' |
158 b'invalid wire protocol permission; ' |
159 'got %s; expected "push" or "pull"' % permission |
159 b'got %s; expected "push" or "pull"' % permission |
160 ) |
160 ) |
161 |
161 |
162 if args is None: |
162 if args is None: |
163 args = '' |
163 args = b'' |
164 |
164 |
165 if not isinstance(args, bytes): |
165 if not isinstance(args, bytes): |
166 raise error.ProgrammingError( |
166 raise error.ProgrammingError( |
167 'arguments for version 1 commands ' 'must be declared as bytes' |
167 b'arguments for version 1 commands ' b'must be declared as bytes' |
168 ) |
168 ) |
169 |
169 |
170 def register(func): |
170 def register(func): |
171 if name in commands: |
171 if name in commands: |
172 raise error.ProgrammingError( |
172 raise error.ProgrammingError( |
173 '%s command already registered ' 'for version 1' % name |
173 b'%s command already registered ' b'for version 1' % name |
174 ) |
174 ) |
175 commands[name] = wireprototypes.commandentry( |
175 commands[name] = wireprototypes.commandentry( |
176 func, args=args, transports=transports, permission=permission |
176 func, args=args, transports=transports, permission=permission |
177 ) |
177 ) |
178 |
178 |
180 |
180 |
181 return register |
181 return register |
182 |
182 |
183 |
183 |
184 # TODO define a more appropriate permissions type to use for this. |
184 # TODO define a more appropriate permissions type to use for this. |
185 @wireprotocommand('batch', 'cmds *', permission='pull') |
185 @wireprotocommand(b'batch', b'cmds *', permission=b'pull') |
186 def batch(repo, proto, cmds, others): |
186 def batch(repo, proto, cmds, others): |
187 unescapearg = wireprototypes.unescapebatcharg |
187 unescapearg = wireprototypes.unescapebatcharg |
188 res = [] |
188 res = [] |
189 for pair in cmds.split(';'): |
189 for pair in cmds.split(b';'): |
190 op, args = pair.split(' ', 1) |
190 op, args = pair.split(b' ', 1) |
191 vals = {} |
191 vals = {} |
192 for a in args.split(','): |
192 for a in args.split(b','): |
193 if a: |
193 if a: |
194 n, v = a.split('=') |
194 n, v = a.split(b'=') |
195 vals[unescapearg(n)] = unescapearg(v) |
195 vals[unescapearg(n)] = unescapearg(v) |
196 func, spec = commands[op] |
196 func, spec = commands[op] |
197 |
197 |
198 # Validate that client has permissions to perform this command. |
198 # Validate that client has permissions to perform this command. |
199 perm = commands[op].permission |
199 perm = commands[op].permission |
200 assert perm in ('push', 'pull') |
200 assert perm in (b'push', b'pull') |
201 proto.checkperm(perm) |
201 proto.checkperm(perm) |
202 |
202 |
203 if spec: |
203 if spec: |
204 keys = spec.split() |
204 keys = spec.split() |
205 data = {} |
205 data = {} |
206 for k in keys: |
206 for k in keys: |
207 if k == '*': |
207 if k == b'*': |
208 star = {} |
208 star = {} |
209 for key in vals.keys(): |
209 for key in vals.keys(): |
210 if key not in keys: |
210 if key not in keys: |
211 star[key] = vals[key] |
211 star[key] = vals[key] |
212 data['*'] = star |
212 data[b'*'] = star |
213 else: |
213 else: |
214 data[k] = vals[k] |
214 data[k] = vals[k] |
215 result = func(repo, proto, *[data[k] for k in keys]) |
215 result = func(repo, proto, *[data[k] for k in keys]) |
216 else: |
216 else: |
217 result = func(repo, proto) |
217 result = func(repo, proto) |
223 assert isinstance(result, (wireprototypes.bytesresponse, bytes)) |
223 assert isinstance(result, (wireprototypes.bytesresponse, bytes)) |
224 if isinstance(result, wireprototypes.bytesresponse): |
224 if isinstance(result, wireprototypes.bytesresponse): |
225 result = result.data |
225 result = result.data |
226 res.append(wireprototypes.escapebatcharg(result)) |
226 res.append(wireprototypes.escapebatcharg(result)) |
227 |
227 |
228 return wireprototypes.bytesresponse(';'.join(res)) |
228 return wireprototypes.bytesresponse(b';'.join(res)) |
229 |
229 |
230 |
230 |
231 @wireprotocommand('between', 'pairs', permission='pull') |
231 @wireprotocommand(b'between', b'pairs', permission=b'pull') |
232 def between(repo, proto, pairs): |
232 def between(repo, proto, pairs): |
233 pairs = [wireprototypes.decodelist(p, '-') for p in pairs.split(" ")] |
233 pairs = [wireprototypes.decodelist(p, b'-') for p in pairs.split(b" ")] |
234 r = [] |
234 r = [] |
235 for b in repo.between(pairs): |
235 for b in repo.between(pairs): |
236 r.append(wireprototypes.encodelist(b) + "\n") |
236 r.append(wireprototypes.encodelist(b) + b"\n") |
237 |
237 |
238 return wireprototypes.bytesresponse(''.join(r)) |
238 return wireprototypes.bytesresponse(b''.join(r)) |
239 |
239 |
240 |
240 |
241 @wireprotocommand('branchmap', permission='pull') |
241 @wireprotocommand(b'branchmap', permission=b'pull') |
242 def branchmap(repo, proto): |
242 def branchmap(repo, proto): |
243 branchmap = repo.branchmap() |
243 branchmap = repo.branchmap() |
244 heads = [] |
244 heads = [] |
245 for branch, nodes in branchmap.iteritems(): |
245 for branch, nodes in branchmap.iteritems(): |
246 branchname = urlreq.quote(encoding.fromlocal(branch)) |
246 branchname = urlreq.quote(encoding.fromlocal(branch)) |
247 branchnodes = wireprototypes.encodelist(nodes) |
247 branchnodes = wireprototypes.encodelist(nodes) |
248 heads.append('%s %s' % (branchname, branchnodes)) |
248 heads.append(b'%s %s' % (branchname, branchnodes)) |
249 |
249 |
250 return wireprototypes.bytesresponse('\n'.join(heads)) |
250 return wireprototypes.bytesresponse(b'\n'.join(heads)) |
251 |
251 |
252 |
252 |
253 @wireprotocommand('branches', 'nodes', permission='pull') |
253 @wireprotocommand(b'branches', b'nodes', permission=b'pull') |
254 def branches(repo, proto, nodes): |
254 def branches(repo, proto, nodes): |
255 nodes = wireprototypes.decodelist(nodes) |
255 nodes = wireprototypes.decodelist(nodes) |
256 r = [] |
256 r = [] |
257 for b in repo.branches(nodes): |
257 for b in repo.branches(nodes): |
258 r.append(wireprototypes.encodelist(b) + "\n") |
258 r.append(wireprototypes.encodelist(b) + b"\n") |
259 |
259 |
260 return wireprototypes.bytesresponse(''.join(r)) |
260 return wireprototypes.bytesresponse(b''.join(r)) |
261 |
261 |
262 |
262 |
263 @wireprotocommand('clonebundles', '', permission='pull') |
263 @wireprotocommand(b'clonebundles', b'', permission=b'pull') |
264 def clonebundles(repo, proto): |
264 def clonebundles(repo, proto): |
265 """Server command for returning info for available bundles to seed clones. |
265 """Server command for returning info for available bundles to seed clones. |
266 |
266 |
267 Clients will parse this response and determine what bundle to fetch. |
267 Clients will parse this response and determine what bundle to fetch. |
268 |
268 |
269 Extensions may wrap this command to filter or dynamically emit data |
269 Extensions may wrap this command to filter or dynamically emit data |
270 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 |
271 data center given the client's IP address. |
271 data center given the client's IP address. |
272 """ |
272 """ |
273 return wireprototypes.bytesresponse( |
273 return wireprototypes.bytesresponse( |
274 repo.vfs.tryread('clonebundles.manifest') |
274 repo.vfs.tryread(b'clonebundles.manifest') |
275 ) |
275 ) |
276 |
276 |
277 |
277 |
278 wireprotocaps = [ |
278 wireprotocaps = [ |
279 'lookup', |
279 b'lookup', |
280 'branchmap', |
280 b'branchmap', |
281 'pushkey', |
281 b'pushkey', |
282 'known', |
282 b'known', |
283 'getbundle', |
283 b'getbundle', |
284 'unbundlehash', |
284 b'unbundlehash', |
285 ] |
285 ] |
286 |
286 |
287 |
287 |
288 def _capabilities(repo, proto): |
288 def _capabilities(repo, proto): |
289 """return a list of capabilities for a repo |
289 """return a list of capabilities for a repo |
298 # copy to prevent modification of the global list |
298 # copy to prevent modification of the global list |
299 caps = list(wireprotocaps) |
299 caps = list(wireprotocaps) |
300 |
300 |
301 # Command of same name as capability isn't exposed to version 1 of |
301 # Command of same name as capability isn't exposed to version 1 of |
302 # transports. So conditionally add it. |
302 # transports. So conditionally add it. |
303 if commands.commandavailable('changegroupsubset', proto): |
303 if commands.commandavailable(b'changegroupsubset', proto): |
304 caps.append('changegroupsubset') |
304 caps.append(b'changegroupsubset') |
305 |
305 |
306 if streamclone.allowservergeneration(repo): |
306 if streamclone.allowservergeneration(repo): |
307 if repo.ui.configbool('server', 'preferuncompressed'): |
307 if repo.ui.configbool(b'server', b'preferuncompressed'): |
308 caps.append('stream-preferred') |
308 caps.append(b'stream-preferred') |
309 requiredformats = repo.requirements & repo.supportedformats |
309 requiredformats = repo.requirements & repo.supportedformats |
310 # if our local revlogs are just revlogv1, add 'stream' cap |
310 # if our local revlogs are just revlogv1, add 'stream' cap |
311 if not requiredformats - {'revlogv1'}: |
311 if not requiredformats - {b'revlogv1'}: |
312 caps.append('stream') |
312 caps.append(b'stream') |
313 # otherwise, add 'streamreqs' detailing our local revlog format |
313 # otherwise, add 'streamreqs' detailing our local revlog format |
314 else: |
314 else: |
315 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats))) |
315 caps.append(b'streamreqs=%s' % b','.join(sorted(requiredformats))) |
316 if repo.ui.configbool('experimental', 'bundle2-advertise'): |
316 if repo.ui.configbool(b'experimental', b'bundle2-advertise'): |
317 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role='server')) |
317 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role=b'server')) |
318 caps.append('bundle2=' + urlreq.quote(capsblob)) |
318 caps.append(b'bundle2=' + urlreq.quote(capsblob)) |
319 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority)) |
319 caps.append(b'unbundle=%s' % b','.join(bundle2.bundlepriority)) |
320 |
320 |
321 if repo.ui.configbool('experimental', 'narrow'): |
321 if repo.ui.configbool(b'experimental', b'narrow'): |
322 caps.append(wireprototypes.NARROWCAP) |
322 caps.append(wireprototypes.NARROWCAP) |
323 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'): |
323 if repo.ui.configbool(b'experimental', b'narrowservebrokenellipses'): |
324 caps.append(wireprototypes.ELLIPSESCAP) |
324 caps.append(wireprototypes.ELLIPSESCAP) |
325 |
325 |
326 return proto.addcapabilities(repo, caps) |
326 return proto.addcapabilities(repo, caps) |
327 |
327 |
328 |
328 |
329 # 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 |
330 # `_capabilities` instead. |
330 # `_capabilities` instead. |
331 @wireprotocommand('capabilities', permission='pull') |
331 @wireprotocommand(b'capabilities', permission=b'pull') |
332 def capabilities(repo, proto): |
332 def capabilities(repo, proto): |
333 caps = _capabilities(repo, proto) |
333 caps = _capabilities(repo, proto) |
334 return wireprototypes.bytesresponse(' '.join(sorted(caps))) |
334 return wireprototypes.bytesresponse(b' '.join(sorted(caps))) |
335 |
335 |
336 |
336 |
337 @wireprotocommand('changegroup', 'roots', permission='pull') |
337 @wireprotocommand(b'changegroup', b'roots', permission=b'pull') |
338 def changegroup(repo, proto, roots): |
338 def changegroup(repo, proto, roots): |
339 nodes = wireprototypes.decodelist(roots) |
339 nodes = wireprototypes.decodelist(roots) |
340 outgoing = discovery.outgoing( |
340 outgoing = discovery.outgoing( |
341 repo, missingroots=nodes, missingheads=repo.heads() |
341 repo, missingroots=nodes, missingheads=repo.heads() |
342 ) |
342 ) |
343 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve') |
343 cg = changegroupmod.makechangegroup(repo, outgoing, b'01', b'serve') |
344 gen = iter(lambda: cg.read(32768), '') |
344 gen = iter(lambda: cg.read(32768), b'') |
345 return wireprototypes.streamres(gen=gen) |
345 return wireprototypes.streamres(gen=gen) |
346 |
346 |
347 |
347 |
348 @wireprotocommand('changegroupsubset', 'bases heads', permission='pull') |
348 @wireprotocommand(b'changegroupsubset', b'bases heads', permission=b'pull') |
349 def changegroupsubset(repo, proto, bases, heads): |
349 def changegroupsubset(repo, proto, bases, heads): |
350 bases = wireprototypes.decodelist(bases) |
350 bases = wireprototypes.decodelist(bases) |
351 heads = wireprototypes.decodelist(heads) |
351 heads = wireprototypes.decodelist(heads) |
352 outgoing = discovery.outgoing(repo, missingroots=bases, missingheads=heads) |
352 outgoing = discovery.outgoing(repo, missingroots=bases, missingheads=heads) |
353 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve') |
353 cg = changegroupmod.makechangegroup(repo, outgoing, b'01', b'serve') |
354 gen = iter(lambda: cg.read(32768), '') |
354 gen = iter(lambda: cg.read(32768), b'') |
355 return wireprototypes.streamres(gen=gen) |
355 return wireprototypes.streamres(gen=gen) |
356 |
356 |
357 |
357 |
358 @wireprotocommand('debugwireargs', 'one two *', permission='pull') |
358 @wireprotocommand(b'debugwireargs', b'one two *', permission=b'pull') |
359 def debugwireargs(repo, proto, one, two, others): |
359 def debugwireargs(repo, proto, one, two, others): |
360 # only accept optional args from the known set |
360 # only accept optional args from the known set |
361 opts = options('debugwireargs', ['three', 'four'], others) |
361 opts = options(b'debugwireargs', [b'three', b'four'], others) |
362 return wireprototypes.bytesresponse( |
362 return wireprototypes.bytesresponse( |
363 repo.debugwireargs(one, two, **pycompat.strkwargs(opts)) |
363 repo.debugwireargs(one, two, **pycompat.strkwargs(opts)) |
364 ) |
364 ) |
365 |
365 |
366 |
366 |
414 if any( |
414 if any( |
415 cl.rev(rev) not in heads_anc and cl.rev(rev) not in common_anc |
415 cl.rev(rev) not in heads_anc and cl.rev(rev) not in common_anc |
416 for rev in bundle_heads |
416 for rev in bundle_heads |
417 ): |
417 ): |
418 continue |
418 continue |
419 if 'bases' in entry: |
419 if b'bases' in entry: |
420 try: |
420 try: |
421 bundle_bases = decodehexstring(entry['bases']) |
421 bundle_bases = decodehexstring(entry[b'bases']) |
422 except TypeError: |
422 except TypeError: |
423 # Bad bases entry |
423 # Bad bases entry |
424 continue |
424 continue |
425 if not all(cl.rev(rev) in common_anc for rev in bundle_bases): |
425 if not all(cl.rev(rev) in common_anc for rev in bundle_bases): |
426 continue |
426 continue |
427 path = entry['URL'] |
427 path = entry[b'URL'] |
428 repo.ui.debug('sending pullbundle "%s"\n' % path) |
428 repo.ui.debug(b'sending pullbundle "%s"\n' % path) |
429 try: |
429 try: |
430 return repo.vfs.open(path) |
430 return repo.vfs.open(path) |
431 except IOError: |
431 except IOError: |
432 repo.ui.debug('pullbundle "%s" not accessible\n' % path) |
432 repo.ui.debug(b'pullbundle "%s" not accessible\n' % path) |
433 continue |
433 continue |
434 return None |
434 return None |
435 |
435 |
436 |
436 |
437 @wireprotocommand('getbundle', '*', permission='pull') |
437 @wireprotocommand(b'getbundle', b'*', permission=b'pull') |
438 def getbundle(repo, proto, others): |
438 def getbundle(repo, proto, others): |
439 opts = options( |
439 opts = options( |
440 'getbundle', wireprototypes.GETBUNDLE_ARGUMENTS.keys(), others |
440 b'getbundle', wireprototypes.GETBUNDLE_ARGUMENTS.keys(), others |
441 ) |
441 ) |
442 for k, v in opts.iteritems(): |
442 for k, v in opts.iteritems(): |
443 keytype = wireprototypes.GETBUNDLE_ARGUMENTS[k] |
443 keytype = wireprototypes.GETBUNDLE_ARGUMENTS[k] |
444 if keytype == 'nodes': |
444 if keytype == b'nodes': |
445 opts[k] = wireprototypes.decodelist(v) |
445 opts[k] = wireprototypes.decodelist(v) |
446 elif keytype == 'csv': |
446 elif keytype == b'csv': |
447 opts[k] = list(v.split(',')) |
447 opts[k] = list(v.split(b',')) |
448 elif keytype == 'scsv': |
448 elif keytype == b'scsv': |
449 opts[k] = set(v.split(',')) |
449 opts[k] = set(v.split(b',')) |
450 elif keytype == 'boolean': |
450 elif keytype == b'boolean': |
451 # Client should serialize False as '0', which is a non-empty string |
451 # Client should serialize False as '0', which is a non-empty string |
452 # so it evaluates as a True bool. |
452 # so it evaluates as a True bool. |
453 if v == '0': |
453 if v == b'0': |
454 opts[k] = False |
454 opts[k] = False |
455 else: |
455 else: |
456 opts[k] = bool(v) |
456 opts[k] = bool(v) |
457 elif keytype != 'plain': |
457 elif keytype != b'plain': |
458 raise KeyError('unknown getbundle option type %s' % keytype) |
458 raise KeyError(b'unknown getbundle option type %s' % keytype) |
459 |
459 |
460 if not bundle1allowed(repo, 'pull'): |
460 if not bundle1allowed(repo, b'pull'): |
461 if not exchange.bundle2requested(opts.get('bundlecaps')): |
461 if not exchange.bundle2requested(opts.get(b'bundlecaps')): |
462 if proto.name == 'http-v1': |
462 if proto.name == b'http-v1': |
463 return wireprototypes.ooberror(bundle2required) |
463 return wireprototypes.ooberror(bundle2required) |
464 raise error.Abort(bundle2requiredmain, hint=bundle2requiredhint) |
464 raise error.Abort(bundle2requiredmain, hint=bundle2requiredhint) |
465 |
465 |
466 try: |
466 try: |
467 clheads = set(repo.changelog.heads()) |
467 clheads = set(repo.changelog.heads()) |
468 heads = set(opts.get('heads', set())) |
468 heads = set(opts.get(b'heads', set())) |
469 common = set(opts.get('common', set())) |
469 common = set(opts.get(b'common', set())) |
470 common.discard(nullid) |
470 common.discard(nullid) |
471 if ( |
471 if ( |
472 repo.ui.configbool('server', 'pullbundle') |
472 repo.ui.configbool(b'server', b'pullbundle') |
473 and 'partial-pull' in proto.getprotocaps() |
473 and b'partial-pull' in proto.getprotocaps() |
474 ): |
474 ): |
475 # Check if a pre-built bundle covers this request. |
475 # Check if a pre-built bundle covers this request. |
476 bundle = find_pullbundle(repo, proto, opts, clheads, heads, common) |
476 bundle = find_pullbundle(repo, proto, opts, clheads, heads, common) |
477 if bundle: |
477 if bundle: |
478 return wireprototypes.streamres( |
478 return wireprototypes.streamres( |
479 gen=util.filechunkiter(bundle), prefer_uncompressed=True |
479 gen=util.filechunkiter(bundle), prefer_uncompressed=True |
480 ) |
480 ) |
481 |
481 |
482 if repo.ui.configbool('server', 'disablefullbundle'): |
482 if repo.ui.configbool(b'server', b'disablefullbundle'): |
483 # Check to see if this is a full clone. |
483 # Check to see if this is a full clone. |
484 changegroup = opts.get('cg', True) |
484 changegroup = opts.get(b'cg', True) |
485 if changegroup and not common and clheads == heads: |
485 if changegroup and not common and clheads == heads: |
486 raise error.Abort( |
486 raise error.Abort( |
487 _('server has pull-based clones disabled'), |
487 _(b'server has pull-based clones disabled'), |
488 hint=_('remove --pull if specified or upgrade Mercurial'), |
488 hint=_(b'remove --pull if specified or upgrade Mercurial'), |
489 ) |
489 ) |
490 |
490 |
491 info, chunks = exchange.getbundlechunks( |
491 info, chunks = exchange.getbundlechunks( |
492 repo, 'serve', **pycompat.strkwargs(opts) |
492 repo, b'serve', **pycompat.strkwargs(opts) |
493 ) |
493 ) |
494 prefercompressed = info.get('prefercompressed', True) |
494 prefercompressed = info.get(b'prefercompressed', True) |
495 except error.Abort as exc: |
495 except error.Abort as exc: |
496 # cleanly forward Abort error to the client |
496 # cleanly forward Abort error to the client |
497 if not exchange.bundle2requested(opts.get('bundlecaps')): |
497 if not exchange.bundle2requested(opts.get(b'bundlecaps')): |
498 if proto.name == 'http-v1': |
498 if proto.name == b'http-v1': |
499 return wireprototypes.ooberror(pycompat.bytestr(exc) + '\n') |
499 return wireprototypes.ooberror(pycompat.bytestr(exc) + b'\n') |
500 raise # cannot do better for bundle1 + ssh |
500 raise # cannot do better for bundle1 + ssh |
501 # bundle2 request expect a bundle2 reply |
501 # bundle2 request expect a bundle2 reply |
502 bundler = bundle2.bundle20(repo.ui) |
502 bundler = bundle2.bundle20(repo.ui) |
503 manargs = [('message', pycompat.bytestr(exc))] |
503 manargs = [(b'message', pycompat.bytestr(exc))] |
504 advargs = [] |
504 advargs = [] |
505 if exc.hint is not None: |
505 if exc.hint is not None: |
506 advargs.append(('hint', exc.hint)) |
506 advargs.append((b'hint', exc.hint)) |
507 bundler.addpart(bundle2.bundlepart('error:abort', manargs, advargs)) |
507 bundler.addpart(bundle2.bundlepart(b'error:abort', manargs, advargs)) |
508 chunks = bundler.getchunks() |
508 chunks = bundler.getchunks() |
509 prefercompressed = False |
509 prefercompressed = False |
510 |
510 |
511 return wireprototypes.streamres( |
511 return wireprototypes.streamres( |
512 gen=chunks, prefer_uncompressed=not prefercompressed |
512 gen=chunks, prefer_uncompressed=not prefercompressed |
513 ) |
513 ) |
514 |
514 |
515 |
515 |
516 @wireprotocommand('heads', permission='pull') |
516 @wireprotocommand(b'heads', permission=b'pull') |
517 def heads(repo, proto): |
517 def heads(repo, proto): |
518 h = repo.heads() |
518 h = repo.heads() |
519 return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + '\n') |
519 return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + b'\n') |
520 |
520 |
521 |
521 |
522 @wireprotocommand('hello', permission='pull') |
522 @wireprotocommand(b'hello', permission=b'pull') |
523 def hello(repo, proto): |
523 def hello(repo, proto): |
524 """Called as part of SSH handshake to obtain server info. |
524 """Called as part of SSH handshake to obtain server info. |
525 |
525 |
526 Returns a list of lines describing interesting things about the |
526 Returns a list of lines describing interesting things about the |
527 server, in an RFC822-like format. |
527 server, in an RFC822-like format. |
530 line of space separated tokens describing server abilities: |
530 line of space separated tokens describing server abilities: |
531 |
531 |
532 capabilities: <token0> <token1> <token2> |
532 capabilities: <token0> <token1> <token2> |
533 """ |
533 """ |
534 caps = capabilities(repo, proto).data |
534 caps = capabilities(repo, proto).data |
535 return wireprototypes.bytesresponse('capabilities: %s\n' % caps) |
535 return wireprototypes.bytesresponse(b'capabilities: %s\n' % caps) |
536 |
536 |
537 |
537 |
538 @wireprotocommand('listkeys', 'namespace', permission='pull') |
538 @wireprotocommand(b'listkeys', b'namespace', permission=b'pull') |
539 def listkeys(repo, proto, namespace): |
539 def listkeys(repo, proto, namespace): |
540 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items()) |
540 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items()) |
541 return wireprototypes.bytesresponse(pushkeymod.encodekeys(d)) |
541 return wireprototypes.bytesresponse(pushkeymod.encodekeys(d)) |
542 |
542 |
543 |
543 |
544 @wireprotocommand('lookup', 'key', permission='pull') |
544 @wireprotocommand(b'lookup', b'key', permission=b'pull') |
545 def lookup(repo, proto, key): |
545 def lookup(repo, proto, key): |
546 try: |
546 try: |
547 k = encoding.tolocal(key) |
547 k = encoding.tolocal(key) |
548 n = repo.lookup(k) |
548 n = repo.lookup(k) |
549 r = hex(n) |
549 r = hex(n) |
550 success = 1 |
550 success = 1 |
551 except Exception as inst: |
551 except Exception as inst: |
552 r = stringutil.forcebytestr(inst) |
552 r = stringutil.forcebytestr(inst) |
553 success = 0 |
553 success = 0 |
554 return wireprototypes.bytesresponse('%d %s\n' % (success, r)) |
554 return wireprototypes.bytesresponse(b'%d %s\n' % (success, r)) |
555 |
555 |
556 |
556 |
557 @wireprotocommand('known', 'nodes *', permission='pull') |
557 @wireprotocommand(b'known', b'nodes *', permission=b'pull') |
558 def known(repo, proto, nodes, others): |
558 def known(repo, proto, nodes, others): |
559 v = ''.join( |
559 v = b''.join( |
560 b and '1' or '0' for b in repo.known(wireprototypes.decodelist(nodes)) |
560 b and b'1' or b'0' for b in repo.known(wireprototypes.decodelist(nodes)) |
561 ) |
561 ) |
562 return wireprototypes.bytesresponse(v) |
562 return wireprototypes.bytesresponse(v) |
563 |
563 |
564 |
564 |
565 @wireprotocommand('protocaps', 'caps', permission='pull') |
565 @wireprotocommand(b'protocaps', b'caps', permission=b'pull') |
566 def protocaps(repo, proto, caps): |
566 def protocaps(repo, proto, caps): |
567 if proto.name == wireprototypes.SSHV1: |
567 if proto.name == wireprototypes.SSHV1: |
568 proto._protocaps = set(caps.split(' ')) |
568 proto._protocaps = set(caps.split(b' ')) |
569 return wireprototypes.bytesresponse('OK') |
569 return wireprototypes.bytesresponse(b'OK') |
570 |
570 |
571 |
571 |
572 @wireprotocommand('pushkey', 'namespace key old new', permission='push') |
572 @wireprotocommand(b'pushkey', b'namespace key old new', permission=b'push') |
573 def pushkey(repo, proto, namespace, key, old, new): |
573 def pushkey(repo, proto, namespace, key, old, new): |
574 # compatibility with pre-1.8 clients which were accidentally |
574 # compatibility with pre-1.8 clients which were accidentally |
575 # sending raw binary nodes rather than utf-8-encoded hex |
575 # sending raw binary nodes rather than utf-8-encoded hex |
576 if len(new) == 20 and stringutil.escapestr(new) != new: |
576 if len(new) == 20 and stringutil.escapestr(new) != new: |
577 # looks like it could be a binary node |
577 # looks like it could be a binary node |
592 new, |
592 new, |
593 ) |
593 ) |
594 or False |
594 or False |
595 ) |
595 ) |
596 |
596 |
597 output = output.getvalue() if output else '' |
597 output = output.getvalue() if output else b'' |
598 return wireprototypes.bytesresponse('%d\n%s' % (int(r), output)) |
598 return wireprototypes.bytesresponse(b'%d\n%s' % (int(r), output)) |
599 |
599 |
600 |
600 |
601 @wireprotocommand('stream_out', permission='pull') |
601 @wireprotocommand(b'stream_out', permission=b'pull') |
602 def stream(repo, proto): |
602 def stream(repo, proto): |
603 '''If the server supports streaming clone, it advertises the "stream" |
603 '''If the server supports streaming clone, it advertises the "stream" |
604 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 |
605 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. |
606 ''' |
606 ''' |
607 return wireprototypes.streamreslegacy(streamclone.generatev1wireproto(repo)) |
607 return wireprototypes.streamreslegacy(streamclone.generatev1wireproto(repo)) |
608 |
608 |
609 |
609 |
610 @wireprotocommand('unbundle', 'heads', permission='push') |
610 @wireprotocommand(b'unbundle', b'heads', permission=b'push') |
611 def unbundle(repo, proto, heads): |
611 def unbundle(repo, proto, heads): |
612 their_heads = wireprototypes.decodelist(heads) |
612 their_heads = wireprototypes.decodelist(heads) |
613 |
613 |
614 with proto.mayberedirectstdio() as output: |
614 with proto.mayberedirectstdio() as output: |
615 try: |
615 try: |
616 exchange.check_heads(repo, their_heads, 'preparing changes') |
616 exchange.check_heads(repo, their_heads, b'preparing changes') |
617 cleanup = lambda: None |
617 cleanup = lambda: None |
618 try: |
618 try: |
619 payload = proto.getpayload() |
619 payload = proto.getpayload() |
620 if repo.ui.configbool('server', 'streamunbundle'): |
620 if repo.ui.configbool(b'server', b'streamunbundle'): |
621 |
621 |
622 def cleanup(): |
622 def cleanup(): |
623 # Ensure that the full payload is consumed, so |
623 # Ensure that the full payload is consumed, so |
624 # that the connection doesn't contain trailing garbage. |
624 # that the connection doesn't contain trailing garbage. |
625 for p in payload: |
625 for p in payload: |
634 if fp: |
634 if fp: |
635 fp.close() |
635 fp.close() |
636 if tempname: |
636 if tempname: |
637 os.unlink(tempname) |
637 os.unlink(tempname) |
638 |
638 |
639 fd, tempname = pycompat.mkstemp(prefix='hg-unbundle-') |
639 fd, tempname = pycompat.mkstemp(prefix=b'hg-unbundle-') |
640 repo.ui.debug( |
640 repo.ui.debug( |
641 'redirecting incoming bundle to %s\n' % tempname |
641 b'redirecting incoming bundle to %s\n' % tempname |
642 ) |
642 ) |
643 fp = os.fdopen(fd, pycompat.sysstr('wb+')) |
643 fp = os.fdopen(fd, pycompat.sysstr(b'wb+')) |
644 for p in payload: |
644 for p in payload: |
645 fp.write(p) |
645 fp.write(p) |
646 fp.seek(0) |
646 fp.seek(0) |
647 |
647 |
648 gen = exchange.readbundle(repo.ui, fp, None) |
648 gen = exchange.readbundle(repo.ui, fp, None) |
649 if isinstance( |
649 if isinstance( |
650 gen, changegroupmod.cg1unpacker |
650 gen, changegroupmod.cg1unpacker |
651 ) and not bundle1allowed(repo, 'push'): |
651 ) and not bundle1allowed(repo, b'push'): |
652 if proto.name == 'http-v1': |
652 if proto.name == b'http-v1': |
653 # need to special case http because stderr do not get to |
653 # need to special case http because stderr do not get to |
654 # the http client on failed push so we need to abuse |
654 # the http client on failed push so we need to abuse |
655 # some other error type to make sure the message get to |
655 # some other error type to make sure the message get to |
656 # the user. |
656 # the user. |
657 return wireprototypes.ooberror(bundle2required) |
657 return wireprototypes.ooberror(bundle2required) |
658 raise error.Abort( |
658 raise error.Abort( |
659 bundle2requiredmain, hint=bundle2requiredhint |
659 bundle2requiredmain, hint=bundle2requiredhint |
660 ) |
660 ) |
661 |
661 |
662 r = exchange.unbundle( |
662 r = exchange.unbundle( |
663 repo, gen, their_heads, 'serve', proto.client() |
663 repo, gen, their_heads, b'serve', proto.client() |
664 ) |
664 ) |
665 if util.safehasattr(r, 'addpart'): |
665 if util.safehasattr(r, b'addpart'): |
666 # The return looks streamable, we are in the bundle2 case |
666 # The return looks streamable, we are in the bundle2 case |
667 # and should return a stream. |
667 # and should return a stream. |
668 return wireprototypes.streamreslegacy(gen=r.getchunks()) |
668 return wireprototypes.streamreslegacy(gen=r.getchunks()) |
669 return wireprototypes.pushres( |
669 return wireprototypes.pushres( |
670 r, output.getvalue() if output else '' |
670 r, output.getvalue() if output else b'' |
671 ) |
671 ) |
672 |
672 |
673 finally: |
673 finally: |
674 cleanup() |
674 cleanup() |
675 |
675 |
681 except error.Abort: |
681 except error.Abort: |
682 # The old code we moved used procutil.stderr directly. |
682 # The old code we moved used procutil.stderr directly. |
683 # We did not change it to minimise code change. |
683 # We did not change it to minimise code change. |
684 # This need to be moved to something proper. |
684 # This need to be moved to something proper. |
685 # Feel free to do it. |
685 # Feel free to do it. |
686 procutil.stderr.write("abort: %s\n" % exc) |
686 procutil.stderr.write(b"abort: %s\n" % exc) |
687 if exc.hint is not None: |
687 if exc.hint is not None: |
688 procutil.stderr.write("(%s)\n" % exc.hint) |
688 procutil.stderr.write(b"(%s)\n" % exc.hint) |
689 procutil.stderr.flush() |
689 procutil.stderr.flush() |
690 return wireprototypes.pushres( |
690 return wireprototypes.pushres( |
691 0, output.getvalue() if output else '' |
691 0, output.getvalue() if output else b'' |
692 ) |
692 ) |
693 except error.PushRaced: |
693 except error.PushRaced: |
694 return wireprototypes.pusherr( |
694 return wireprototypes.pusherr( |
695 pycompat.bytestr(exc), |
695 pycompat.bytestr(exc), |
696 output.getvalue() if output else '', |
696 output.getvalue() if output else b'', |
697 ) |
697 ) |
698 |
698 |
699 bundler = bundle2.bundle20(repo.ui) |
699 bundler = bundle2.bundle20(repo.ui) |
700 for out in getattr(exc, '_bundle2salvagedoutput', ()): |
700 for out in getattr(exc, '_bundle2salvagedoutput', ()): |
701 bundler.addpart(out) |
701 bundler.addpart(out) |
705 except error.PushkeyFailed as exc: |
705 except error.PushkeyFailed as exc: |
706 # check client caps |
706 # check client caps |
707 remotecaps = getattr(exc, '_replycaps', None) |
707 remotecaps = getattr(exc, '_replycaps', None) |
708 if ( |
708 if ( |
709 remotecaps is not None |
709 remotecaps is not None |
710 and 'pushkey' not in remotecaps.get('error', ()) |
710 and b'pushkey' not in remotecaps.get(b'error', ()) |
711 ): |
711 ): |
712 # no support remote side, fallback to Abort handler. |
712 # no support remote side, fallback to Abort handler. |
713 raise |
713 raise |
714 part = bundler.newpart('error:pushkey') |
714 part = bundler.newpart(b'error:pushkey') |
715 part.addparam('in-reply-to', exc.partid) |
715 part.addparam(b'in-reply-to', exc.partid) |
716 if exc.namespace is not None: |
716 if exc.namespace is not None: |
717 part.addparam( |
717 part.addparam( |
718 'namespace', exc.namespace, mandatory=False |
718 b'namespace', exc.namespace, mandatory=False |
719 ) |
719 ) |
720 if exc.key is not None: |
720 if exc.key is not None: |
721 part.addparam('key', exc.key, mandatory=False) |
721 part.addparam(b'key', exc.key, mandatory=False) |
722 if exc.new is not None: |
722 if exc.new is not None: |
723 part.addparam('new', exc.new, mandatory=False) |
723 part.addparam(b'new', exc.new, mandatory=False) |
724 if exc.old is not None: |
724 if exc.old is not None: |
725 part.addparam('old', exc.old, mandatory=False) |
725 part.addparam(b'old', exc.old, mandatory=False) |
726 if exc.ret is not None: |
726 if exc.ret is not None: |
727 part.addparam('ret', exc.ret, mandatory=False) |
727 part.addparam(b'ret', exc.ret, mandatory=False) |
728 except error.BundleValueError as exc: |
728 except error.BundleValueError as exc: |
729 errpart = bundler.newpart('error:unsupportedcontent') |
729 errpart = bundler.newpart(b'error:unsupportedcontent') |
730 if exc.parttype is not None: |
730 if exc.parttype is not None: |
731 errpart.addparam('parttype', exc.parttype) |
731 errpart.addparam(b'parttype', exc.parttype) |
732 if exc.params: |
732 if exc.params: |
733 errpart.addparam('params', '\0'.join(exc.params)) |
733 errpart.addparam(b'params', b'\0'.join(exc.params)) |
734 except error.Abort as exc: |
734 except error.Abort as exc: |
735 manargs = [('message', stringutil.forcebytestr(exc))] |
735 manargs = [(b'message', stringutil.forcebytestr(exc))] |
736 advargs = [] |
736 advargs = [] |
737 if exc.hint is not None: |
737 if exc.hint is not None: |
738 advargs.append(('hint', exc.hint)) |
738 advargs.append((b'hint', exc.hint)) |
739 bundler.addpart( |
739 bundler.addpart( |
740 bundle2.bundlepart('error:abort', manargs, advargs) |
740 bundle2.bundlepart(b'error:abort', manargs, advargs) |
741 ) |
741 ) |
742 except error.PushRaced as exc: |
742 except error.PushRaced as exc: |
743 bundler.newpart( |
743 bundler.newpart( |
744 'error:pushraced', |
744 b'error:pushraced', |
745 [('message', stringutil.forcebytestr(exc))], |
745 [(b'message', stringutil.forcebytestr(exc))], |
746 ) |
746 ) |
747 return wireprototypes.streamreslegacy(gen=bundler.getchunks()) |
747 return wireprototypes.streamreslegacy(gen=bundler.getchunks()) |