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