diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py --- a/mercurial/hgweb/protocol.py +++ b/mercurial/hgweb/protocol.py @@ -52,6 +52,7 @@ self.response = '' self.ui = ui self.name = 'http' + self._protocaps = None def getargs(self, args): knownargs = self._args() @@ -88,6 +89,11 @@ length -= int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0)) for s in util.filechunkiter(self.req, limit=length): fp.write(s) + def getprotocaps(self): + if self._protocaps is None: + value = decodevaluefromheaders(self.req, r'X-HgProto') + self._protocaps = value.split(' ') + return self._protocaps def redirect(self): self.oldio = self.ui.fout, self.ui.ferr self.ui.ferr = self.ui.fout = stringio() @@ -109,7 +115,7 @@ """ # Determine the response media type and compression engine based # on the request parameters. - protocaps = decodevaluefromheaders(self.req, r'X-HgProto').split(' ') + protocaps = self.getprotocaps() if '0.2' in protocaps: # All clients are expected to support uncompressed data. @@ -117,11 +123,7 @@ return HGTYPE2, util._noopengine(), {} # Default as defined by wire protocol spec. - compformats = ['zlib', 'none'] - for cap in protocaps: - if cap.startswith('comp='): - compformats = cap[5:].split(',') - break + compformats = self.getcompressionsupport() # Now find an agreed upon compression format. for engine in wireproto.supportedcompengines(self.ui, self, diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py --- a/mercurial/sshpeer.py +++ b/mercurial/sshpeer.py @@ -13,6 +13,7 @@ from . import ( error, pycompat, + sshserver, util, wireproto, ) @@ -182,6 +183,13 @@ # End of _basewirecommands interface. + def _clientcapabilities(self): + protoparams = [] + comps = [e.wireprotosupport().name for e in + util.compengines.supportedwireengines(util.CLIENTROLE)] + protoparams.append('comp=%s' % ','.join(comps)) + return protoparams + def _validaterepo(self, sshcmd, args, remotecmd, sshenv=None): # cleanup up previous run self._cleanup() @@ -240,6 +248,12 @@ self._caps.update(l[:-1].split(":")[1].split()) break + if 'protocaps' in self._caps: + try: + self._call("protocaps", + caps=' '.join(self._clientcapabilities())) + except IOError: + badresponse() def _readerr(self): _forwardoutput(self.ui, self._pipee) @@ -296,7 +310,12 @@ dbg(line % ' %s-%s: %d' % (key, dk, len(dv))) self.ui.debug("sending %s command\n" % cmd) self._pipeo.write("%s\n" % cmd) - _func, names = wireproto.commands[cmd] + if cmd in wireproto.commands: + _func, names = wireproto.commands[cmd] + elif getattr(sshserver.sshserver, 'do_' + cmd, None): + names = getattr(sshserver.sshserver, 'do_%s_arguments' % cmd, None) + else: + raise KeyError(cmd) keys = names.split() wireargs = {} for k in keys: diff --git a/mercurial/sshserver.py b/mercurial/sshserver.py --- a/mercurial/sshserver.py +++ b/mercurial/sshserver.py @@ -27,6 +27,7 @@ self.fin = ui.fin self.fout = ui.fout self.name = 'ssh' + self._protocaps = [] hook.redirect(True) ui.fout = repo.ui.fout = ui.ferr @@ -66,6 +67,20 @@ fpout.write(self.fin.read(count)) count = int(self.fin.readline()) + def getprotocaps(self): + """return the wireprotocol capabilities of the current request""" + return self._protocaps + + def do_protocaps(self): + """ssh-specific command for sending the client capabilities + + The ssh protocol is stateful and doesn't retransmit the wireprotocol + capabilities on every request. + """ + self._protocaps = self.getargs(self.do_protocaps_arguments)[0] + self.sendresponse('OK') + do_protocaps_arguments = 'caps' + def redirect(self): pass diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -64,6 +64,17 @@ """ raise NotImplementedError() + def getprotocaps(self): + """return the wireprotocol capabilities of the current request""" + raise NotImplementedError() + + def getcompressionsupport(self): + """return a list of compression methods supported by the client""" + for cap in self.getprotocaps(): + if cap.startswith('comp='): + return cap[5:].split(',') + return ['zlib', 'none'] + def redirect(self): """may setup interception for stdout and stderr @@ -799,6 +810,9 @@ comptypes = ','.join(urlreq.quote(e.wireprotosupport().name) for e in compengines) caps.append('compression=%s' % comptypes) + elif proto.name == 'ssh': + # Advertise support for the ssh-only protocaps command + caps.append('protocaps') return caps diff --git a/tests/test-ssh-bundle1.t b/tests/test-ssh-bundle1.t --- a/tests/test-ssh-bundle1.t +++ b/tests/test-ssh-bundle1.t @@ -467,9 +467,10 @@ running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re) sending hello command sending between command - remote: 384 - remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + remote: 394 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN protocaps remote: 1 + sending protocaps command preparing listkeys for "bookmarks" sending listkeys command received listkey for "bookmarks": 45 bytes diff --git a/tests/test-ssh.t b/tests/test-ssh.t --- a/tests/test-ssh.t +++ b/tests/test-ssh.t @@ -486,9 +486,12 @@ devel-peer-request: between devel-peer-request: pairs: 81 bytes sending between command - remote: 384 - remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + remote: 394 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN protocaps remote: 1 + devel-peer-request: protocaps + devel-peer-request: caps: 25 bytes + sending protocaps command query 1; heads devel-peer-request: batch devel-peer-request: cmds: 141 bytes