mercurial/wireprotov1server.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43089 c59eb1560c44
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    36 )
    36 )
    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 = _(b'incompatible Mercurial client; bundle2 required')
    42 bundle2requiredhint = _(
    42 bundle2requiredhint = _(
    43     'see https://www.mercurial-scm.org/wiki/' 'IncompatibleClient'
    43     b'see https://www.mercurial-scm.org/wiki/' b'IncompatibleClient'
    44 )
    44 )
    45 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
    45 bundle2required = b'%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
    46 
    46 
    47 
    47 
    48 def clientcompressionsupport(proto):
    48 def clientcompressionsupport(proto):
    49     """Returns a list of compression methods supported by the client.
    49     """Returns a list of compression methods supported by the client.
    50 
    50 
    51     Returns a list of the compression methods supported by the client
    51     Returns a list of the compression methods supported by the client
    52     according to the protocol capabilities. If no such capability has
    52     according to the protocol capabilities. If no such capability has
    53     been announced, fallback to the default of zlib and uncompressed.
    53     been announced, fallback to the default of zlib and uncompressed.
    54     """
    54     """
    55     for cap in proto.getprotocaps():
    55     for cap in proto.getprotocaps():
    56         if cap.startswith('comp='):
    56         if cap.startswith(b'comp='):
    57             return cap[5:].split(',')
    57             return cap[5:].split(b',')
    58     return ['zlib', 'none']
    58     return [b'zlib', b'none']
    59 
    59 
    60 
    60 
    61 # 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 
    62 
    63 
    63 
    66 
    66 
    67     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
    68     extensions that need commands to operate on different repo views under
    68     extensions that need commands to operate on different repo views under
    69     specialized circumstances.
    69     specialized circumstances.
    70     """
    70     """
    71     viewconfig = repo.ui.config('server', 'view')
    71     viewconfig = repo.ui.config(b'server', b'view')
    72     return repo.filtered(viewconfig)
    72     return repo.filtered(viewconfig)
    73 
    73 
    74 
    74 
    75 def dispatch(repo, proto, command):
    75 def dispatch(repo, proto, command):
    76     repo = getdispatchrepo(repo, proto, command)
    76     repo = getdispatchrepo(repo, proto, command)
    87         if k in others:
    87         if k in others:
    88             opts[k] = others[k]
    88             opts[k] = others[k]
    89             del others[k]
    89             del others[k]
    90     if others:
    90     if others:
    91         procutil.stderr.write(
    91         procutil.stderr.write(
    92             "warning: %s ignored unexpected arguments %s\n"
    92             b"warning: %s ignored unexpected arguments %s\n"
    93             % (cmd, ",".join(others))
    93             % (cmd, b",".join(others))
    94         )
    94         )
    95     return opts
    95     return opts
    96 
    96 
    97 
    97 
    98 def bundle1allowed(repo, action):
    98 def bundle1allowed(repo, action):
   104     2. server.bundle1.<action>
   104     2. server.bundle1.<action>
   105     3. server.bundle1gd (if generaldelta active)
   105     3. server.bundle1gd (if generaldelta active)
   106     4. server.bundle1
   106     4. server.bundle1
   107     """
   107     """
   108     ui = repo.ui
   108     ui = repo.ui
   109     gd = 'generaldelta' in repo.requirements
   109     gd = b'generaldelta' in repo.requirements
   110 
   110 
   111     if gd:
   111     if gd:
   112         v = ui.configbool('server', 'bundle1gd.%s' % action)
   112         v = ui.configbool(b'server', b'bundle1gd.%s' % action)
   113         if v is not None:
   113         if v is not None:
   114             return v
   114             return v
   115 
   115 
   116     v = ui.configbool('server', 'bundle1.%s' % action)
   116     v = ui.configbool(b'server', b'bundle1.%s' % action)
   117     if v is not None:
   117     if v is not None:
   118         return v
   118         return v
   119 
   119 
   120     if gd:
   120     if gd:
   121         v = ui.configbool('server', 'bundle1gd')
   121         v = ui.configbool(b'server', b'bundle1gd')
   122         if v is not None:
   122         if v is not None:
   123             return v
   123             return v
   124 
   124 
   125     return ui.configbool('server', 'bundle1')
   125     return ui.configbool(b'server', b'bundle1')
   126 
   126 
   127 
   127 
   128 commands = wireprototypes.commanddict()
   128 commands = wireprototypes.commanddict()
   129 
   129 
   130 
   130 
   131 def wireprotocommand(name, args=None, permission='push'):
   131 def wireprotocommand(name, args=None, permission=b'push'):
   132     """Decorator to declare a wire protocol command.
   132     """Decorator to declare a wire protocol command.
   133 
   133 
   134     ``name`` is the name of the wire protocol command being provided.
   134     ``name`` is the name of the wire protocol command being provided.
   135 
   135 
   136     ``args`` defines the named arguments accepted by the command. It is
   136     ``args`` defines the named arguments accepted by the command. It is
   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 
   379       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
   380       one specific branch of many.
   380       one specific branch of many.
   381     """
   381     """
   382 
   382 
   383     def decodehexstring(s):
   383     def decodehexstring(s):
   384         return {binascii.unhexlify(h) for h in s.split(';')}
   384         return {binascii.unhexlify(h) for h in s.split(b';')}
   385 
   385 
   386     manifest = repo.vfs.tryread('pullbundles.manifest')
   386     manifest = repo.vfs.tryread(b'pullbundles.manifest')
   387     if not manifest:
   387     if not manifest:
   388         return None
   388         return None
   389     res = exchange.parseclonebundlesmanifest(repo, manifest)
   389     res = exchange.parseclonebundlesmanifest(repo, manifest)
   390     res = exchange.filterclonebundleentries(repo, res)
   390     res = exchange.filterclonebundleentries(repo, res)
   391     if not res:
   391     if not res:
   393     cl = repo.changelog
   393     cl = repo.changelog
   394     heads_anc = cl.ancestors([cl.rev(rev) for rev in heads], inclusive=True)
   394     heads_anc = cl.ancestors([cl.rev(rev) for rev in heads], inclusive=True)
   395     common_anc = cl.ancestors([cl.rev(rev) for rev in common], inclusive=True)
   395     common_anc = cl.ancestors([cl.rev(rev) for rev in common], inclusive=True)
   396     compformats = clientcompressionsupport(proto)
   396     compformats = clientcompressionsupport(proto)
   397     for entry in res:
   397     for entry in res:
   398         comp = entry.get('COMPRESSION')
   398         comp = entry.get(b'COMPRESSION')
   399         altcomp = util.compengines._bundlenames.get(comp)
   399         altcomp = util.compengines._bundlenames.get(comp)
   400         if comp and comp not in compformats and altcomp not in compformats:
   400         if comp and comp not in compformats and altcomp not in compformats:
   401             continue
   401             continue
   402         # No test yet for VERSION, since V2 is supported by any client
   402         # No test yet for VERSION, since V2 is supported by any client
   403         # that advertises partial pulls
   403         # that advertises partial pulls
   404         if 'heads' in entry:
   404         if b'heads' in entry:
   405             try:
   405             try:
   406                 bundle_heads = decodehexstring(entry['heads'])
   406                 bundle_heads = decodehexstring(entry[b'heads'])
   407             except TypeError:
   407             except TypeError:
   408                 # Bad heads entry
   408                 # Bad heads entry
   409                 continue
   409                 continue
   410             if bundle_heads.issubset(common):
   410             if bundle_heads.issubset(common):
   411                 continue  # Nothing new
   411                 continue  # Nothing new
   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())