D2057: translate base85.c into rust code
Ivzhh added a comment. Thank you @indygreg! The OxidationPlan is my best reference when I started to make a move, and this thread is even more helpful. I am really interested in exploring this ;-) In 2014 I was trying to change the hg backend storage to Postgres, a silly and failed experiment. Anyway, I will save everyone's time and stop talking. I will come back later with a more meaningful implementation. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2057 To: Ivzhh, #hg-reviewers Cc: krbullock, indygreg, durin42, kevincox, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2057: translate base85.c into rust code
indygreg added a comment. To be honest, we're not yet sure what we'll decide for the Python -> Rust bridge. The problem is summarized in the `Rust <=> Python Interop` section on https://www.mercurial-scm.org/wiki/OxidationPlan. I suspect at some level we'll need a CPython extension for CPython for performance reasons (especially for high volume function calls). PyPy obviously uses CFFI. I think the ideal outcome is we can write Rust that exposes a C API and use CFFI natively on PyPy and something like `cbindgen` + `Milksnake` to auto-generate a CPython extension that acts as a wrapper around the C API exposed by Rust. I'm not sure if anyone has invented this exact wheel yet. If not, it's probably faster to use `rust-cpython`. Maybe several months from now we have enough Rust and maintaining `rust-cpython` is painful enough that we pursue the auto-generated CPython extension route. What I'm trying to say is you have a green field to explore! But at this juncture, perfect is the enemy of done. We'll be happy with any forward progress, even failed experiments. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2057 To: Ivzhh, #hg-reviewers Cc: krbullock, indygreg, durin42, kevincox, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2057: translate base85.c into rust code
Ivzhh added a comment. As the author of this patch, actually I have the same concern. I started to translate base85 as baby steps to find a way of integrating rust and cpython, on my side, Today I modify setup.py, policy.py and makefile to run hg's test suit with the new base85. For myself, it is only proof of concept. Maybe I should take another way: translate more python modules into CFFI-style, and let CFFI call rust implementation. And gradually change more implementations of python modules with corresponding cffi-style, while keep the python interface the same. My own hope is the rust routines will be able to call each other and eventually run some __basic__ tasks without calling python part. And the rust still lazily provides info to python interface for extensions etc. I am exploring this way now, and hope the findings will be useful for community to make decision. Thank you all for the comments! REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2057 To: Ivzhh, #hg-reviewers Cc: krbullock, indygreg, durin42, kevincox, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2094: wireprotoserver: define and use parse_qs from urllib
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY The cgi module is deprecated since Python 2.6. Let's replace uses of it in wireprotoserver with a similar function from urllib. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2094 AFFECTED FILES mercurial/urllibcompat.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -7,7 +7,6 @@ from __future__ import absolute_import import abc -import cgi import contextlib import struct import sys @@ -134,12 +133,12 @@ args = util.rapply(pycompat.bytesurl, self._req.form.copy()) postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0)) if postlen: -args.update(cgi.parse_qs( +args.update(urlreq.parseqs( self._req.read(postlen), keep_blank_values=True)) return args argvalue = decodevaluefromheaders(self._req, r'X-HgArg') -args.update(cgi.parse_qs(argvalue, keep_blank_values=True)) +args.update(urlreq.parseqs(argvalue, keep_blank_values=True)) return args def forwardpayload(self, fp): diff --git a/mercurial/urllibcompat.py b/mercurial/urllibcompat.py --- a/mercurial/urllibcompat.py +++ b/mercurial/urllibcompat.py @@ -47,6 +47,7 @@ "urlparse", "urlunparse", )) +urlreq._registeralias(urllib.parse, "parse_qs", "parseqs") urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote") import urllib.request urlreq._registeraliases(urllib.request, ( @@ -157,6 +158,7 @@ "urlparse", "urlunparse", )) +urlreq._registeralias(urlparse, "parse_qs", "parseqs") urlerr._registeraliases(urllib2, ( "HTTPError", "URLError", To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2085: wireprotoserver: rename getfile() to forwardpayload() (API)
indygreg updated this revision to Diff 5348. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2085?vs=5337=5348 REVISION DETAIL https://phab.mercurial-scm.org/D2085 AFFECTED FILES hgext/largefiles/proto.py mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -64,14 +64,10 @@ returns a list of values (same order as )""" @abc.abstractmethod -def getfile(self, fp): -"""write the whole content of a file into a file like object +def forwardpayload(self, fp): +"""Read the raw payload and forward to a file. -The file is in the form:: - -(\n)+0\n - -chunk size is the ascii version of the int. +The payload is read in full before the function returns. """ @abc.abstractmethod @@ -145,7 +141,7 @@ args.update(cgi.parse_qs(argvalue, keep_blank_values=True)) return args -def getfile(self, fp): +def forwardpayload(self, fp): length = int(self._req.env[r'CONTENT_LENGTH']) # If httppostargs is used, we need to read Content-Length # minus the amount that was consumed by args. @@ -392,7 +388,12 @@ data[arg] = val return [data[k] for k in keys] -def getfile(self, fpout): +def forwardpayload(self, fpout): +# The file is in the form: +# +# \n +# ... +# 0\n _sshv1respondbytes(self._fout, b'') count = int(self._fin.readline()) while count: diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -1008,7 +1008,7 @@ fp = os.fdopen(fd, pycompat.sysstr('wb+')) r = 0 try: -proto.getfile(fp) +proto.forwardpayload(fp) fp.seek(0) gen = exchange.readbundle(repo.ui, fp, None) if (isinstance(gen, changegroupmod.cg1unpacker) diff --git a/hgext/largefiles/proto.py b/hgext/largefiles/proto.py --- a/hgext/largefiles/proto.py +++ b/hgext/largefiles/proto.py @@ -40,7 +40,7 @@ tmpfp = util.atomictempfile(path, createmode=repo.store.createmode) try: -proto.getfile(tmpfp) +proto.forwardpayload(tmpfp) tmpfp._fp.seek(0) if sha != lfutil.hexsha1(tmpfp._fp): raise IOError(0, _('largefile contents do not match hash')) To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2087: wireprotoserver: move responsetype() out of http handler
indygreg updated this revision to Diff 5350. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2087?vs=5339=5350 REVISION DETAIL https://phab.mercurial-scm.org/D2087 AFFECTED FILES mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -170,48 +170,6 @@ urlreq.quote(self._req.env.get('REMOTE_HOST', '')), urlreq.quote(self._req.env.get('REMOTE_USER', ''))) -def responsetype(self, prefer_uncompressed): -"""Determine the appropriate response type and compression settings. - -Returns a tuple of (mediatype, compengine, engineopts). -""" -# Determine the response media type and compression engine based -# on the request parameters. -protocaps = decodevaluefromheaders(self._req, r'X-HgProto').split(' ') - -if '0.2' in protocaps: -# All clients are expected to support uncompressed data. -if prefer_uncompressed: -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 - -# Now find an agreed upon compression format. -for engine in wireproto.supportedcompengines(self._ui, - util.SERVERROLE): -if engine.wireprotosupport().name in compformats: -opts = {} -level = self._ui.configint('server', - '%slevel' % engine.name()) -if level is not None: -opts['level'] = level - -return HGTYPE2, engine, opts - -# No mutually supported compression format. Fall back to the -# legacy protocol. - -# Don't allow untrusted settings because disabling compression or -# setting a very high compression level could lead to flooding -# the server's network or CPU. -opts = {'level': self._ui.configint('server', 'zliblevel')} -return HGTYPE, util.compengines['zlib'], opts - def iscmd(cmd): return cmd in wireproto.commands @@ -252,6 +210,46 @@ 'handleerror': lambda ex: _handlehttperror(ex, req, cmd), } +def _httpresponsetype(ui, req, prefer_uncompressed): +"""Determine the appropriate response type and compression settings. + +Returns a tuple of (mediatype, compengine, engineopts). +""" +# Determine the response media type and compression engine based +# on the request parameters. +protocaps = decodevaluefromheaders(req, r'X-HgProto').split(' ') + +if '0.2' in protocaps: +# All clients are expected to support uncompressed data. +if prefer_uncompressed: +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 + +# Now find an agreed upon compression format. +for engine in wireproto.supportedcompengines(ui, util.SERVERROLE): +if engine.wireprotosupport().name in compformats: +opts = {} +level = ui.configint('server', '%slevel' % engine.name()) +if level is not None: +opts['level'] = level + +return HGTYPE2, engine, opts + +# No mutually supported compression format. Fall back to the +# legacy protocol. + +# Don't allow untrusted settings because disabling compression or +# setting a very high compression level could lead to flooding +# the server's network or CPU. +opts = {'level': ui.configint('server', 'zliblevel')} +return HGTYPE, util.compengines['zlib'], opts + def _callhttp(repo, req, proto, cmd): def genversion2(gen, engine, engineopts): # application/mercurial-0.2 always sends a payload header @@ -284,8 +282,8 @@ # This code for compression should not be streamres specific. It # is here because we only compress streamres at the moment. -mediatype, engine, engineopts = proto.responsetype( -rsp.prefer_uncompressed) +mediatype, engine, engineopts = _httpresponsetype( +repo.ui, req, rsp.prefer_uncompressed) gen = engine.compressstream(gen, engineopts) if mediatype == HGTYPE2: To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org
D2092: wireprotoserver: add version to SSH protocol names (API)
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY We recently introduced version 2 of the SSH protocol. Like we did for the peer, we will need to differentiate version 1 and 2 of the server. So, we add version components to the advertised name of the protocol handler. Nothing in core looks for the old value. But an extension may. .. api:: SSH protocol handler now advertises its name internally as "ssh-v1" instead of "ssh." REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2092 AFFECTED FILES mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -367,7 +367,7 @@ @property def name(self): -return 'ssh' +return SSHV1 def getargs(self, args): data = {} To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2093: tests: add tests for sending recognized command before handshake
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY Rounding out our test coverage for the SSH server. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2093 AFFECTED FILES tests/test-ssh-proto.t CHANGE DETAILS diff --git a/tests/test-ssh-proto.t b/tests/test-ssh-proto.t --- a/tests/test-ssh-proto.t +++ b/tests/test-ssh-proto.t @@ -389,6 +389,33 @@ 0 0 +Send a valid command before the handshake + + $ hg -R server serve --stdio << EOF + > heads + > hello + > between + > pairs 81 + > - + > EOF + 41 + 68986213bd4485ea51533535e3fc9e78007a711f + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + +And a variation that doesn't send the between command + + $ hg -R server serve --stdio << EOF + > heads + > hello + > EOF + 41 + 68986213bd4485ea51533535e3fc9e78007a711f + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + Send an upgrade request to a server that doesn't support that command $ hg -R server serve --stdio << EOF To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2088: wireprototypes: move wire protocol response types to new module
indygreg updated this revision to Diff 5351. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2088?vs=5340=5351 REVISION DETAIL https://phab.mercurial-scm.org/D2088 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py mercurial/wireprototypes.py CHANGE DETAILS diff --git a/mercurial/wireprototypes.py b/mercurial/wireprototypes.py new file mode 100644 --- /dev/null +++ b/mercurial/wireprototypes.py @@ -0,0 +1,61 @@ +# Copyright 2018 Gregory Szorc+# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +class ooberror(object): +"""wireproto reply: failure of a batch of operation + +Something failed during a batch call. The error message is stored in +`self.message`. +""" +def __init__(self, message): +self.message = message + +class pushres(object): +"""wireproto reply: success with simple integer return + +The call was successful and returned an integer contained in `self.res`. +""" +def __init__(self, res, output): +self.res = res +self.output = output + +class pusherr(object): +"""wireproto reply: failure + +The call failed. The `self.res` attribute contains the error message. +""" +def __init__(self, res, output): +self.res = res +self.output = output + +class streamres(object): +"""wireproto reply: binary stream + +The call was successful and the result is a stream. + +Accepts a generator containing chunks of data to be sent to the client. + +``prefer_uncompressed`` indicates that the data is expected to be +uncompressable and that the stream should therefore use the ``none`` +engine. +""" +def __init__(self, gen=None, prefer_uncompressed=False): +self.gen = gen +self.prefer_uncompressed = prefer_uncompressed + +class streamreslegacy(object): +"""wireproto reply: uncompressed binary stream + +The call was successful and the result is a stream. + +Accepts a generator containing chunks of data to be sent to the client. + +Like ``streamres``, but sends an uncompressed data for "version 1" clients +using the application/mercurial-0.1 media type. +""" +def __init__(self, gen=None): +self.gen = gen diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -20,6 +20,7 @@ pycompat, util, wireproto, +wireprototypes, ) stringio = util.stringio @@ -273,11 +274,11 @@ if isinstance(rsp, bytes): req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.streamres_legacy): +elif isinstance(rsp, wireprototypes.streamreslegacy): gen = rsp.gen req.respond(HTTP_OK, HGTYPE) return gen -elif isinstance(rsp, wireproto.streamres): +elif isinstance(rsp, wireprototypes.streamres): gen = rsp.gen # This code for compression should not be streamres specific. It @@ -291,18 +292,18 @@ req.respond(HTTP_OK, mediatype) return gen -elif isinstance(rsp, wireproto.pushres): +elif isinstance(rsp, wireprototypes.pushres): rsp = '%d\n%s' % (rsp.res, rsp.output) req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.pusherr): +elif isinstance(rsp, wireprototypes.pusherr): # This is the httplib workaround documented in _handlehttperror(). req.drain() rsp = '0\n%s\n' % rsp.res req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.ooberror): +elif isinstance(rsp, wireprototypes.ooberror): rsp = rsp.message req.respond(HTTP_OK, HGERRTYPE, body=rsp) return [] @@ -434,16 +435,16 @@ if isinstance(rsp, bytes): _sshv1respondbytes(self._fout, rsp) -elif isinstance(rsp, wireproto.streamres): +elif isinstance(rsp, wireprototypes.streamres): _sshv1respondstream(self._fout, rsp) -elif isinstance(rsp, wireproto.streamres_legacy): +elif isinstance(rsp, wireprototypes.streamreslegacy): _sshv1respondstream(self._fout, rsp) -elif isinstance(rsp, wireproto.pushres): +elif isinstance(rsp, wireprototypes.pushres): _sshv1respondbytes(self._fout, b'') _sshv1respondbytes(self._fout, bytes(rsp.res)) -elif isinstance(rsp, wireproto.pusherr): +elif isinstance(rsp, wireprototypes.pusherr): _sshv1respondbytes(self._fout, rsp.res) -elif isinstance(rsp, wireproto.ooberror): +elif isinstance(rsp, wireprototypes.ooberror):
D2083: wireprotoserver: remove redirect() and restore() (API)
indygreg updated this revision to Diff 5346. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2083?vs=5335=5346 REVISION DETAIL https://phab.mercurial-scm.org/D2083 AFFECTED FILES mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -88,23 +88,6 @@ won't be captured. """ -@abc.abstractmethod -def redirect(self): -"""may setup interception for stdout and stderr - -See also the `restore` method.""" - -# If the `redirect` function does install interception, the `restore` -# function MUST be defined. If interception is not used, this function -# MUST NOT be defined. -# -# left commented here on purpose -# -#def restore(self): -#"""reinstall previous stdout and stderr and return intercepted stdout -#""" -#raise NotImplementedError() - def decodevaluefromheaders(req, headerprefix): """Decode a long value from multiple HTTP request headers. @@ -181,15 +164,6 @@ self._ui.fout = oldout self._ui.ferr = olderr -def redirect(self): -self._oldio = self._ui.fout, self._ui.ferr -self._ui.ferr = self._ui.fout = stringio() - -def restore(self): -val = self._ui.fout.getvalue() -self._ui.ferr, self._ui.fout = self._oldio -return val - def _client(self): return 'remote:%s:%s:%s' % ( self._req.env.get('wsgi.url_scheme') or 'http', @@ -425,9 +399,6 @@ def mayberedirectstdio(self): yield None -def redirect(self): -pass - def _client(self): client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] return 'remote:ssh:' + client To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2086: wireproto: remove unused proto argument from supportedcompengines (API)
indygreg updated this revision to Diff 5349. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2086?vs=5338=5349 REVISION DETAIL https://phab.mercurial-scm.org/D2086 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -192,7 +192,7 @@ break # Now find an agreed upon compression format. -for engine in wireproto.supportedcompengines(self._ui, self, +for engine in wireproto.supportedcompengines(self._ui, util.SERVERROLE): if engine.wireprotosupport().name in compformats: opts = {} diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -587,7 +587,7 @@ return ui.configbool('server', 'bundle1') -def supportedcompengines(ui, proto, role): +def supportedcompengines(ui, role): """Obtain the list of supported compression engines for a request.""" assert role in (util.CLIENTROLE, util.SERVERROLE) @@ -824,7 +824,7 @@ # FUTURE advertise minrx and mintx after consulting config option caps.append('httpmediatype=0.1rx,0.1tx,0.2tx') -compengines = supportedcompengines(repo.ui, proto, util.SERVERROLE) +compengines = supportedcompengines(repo.ui, util.SERVERROLE) if compengines: comptypes = ','.join(urlreq.quote(e.wireprotosupport().name) for e in compengines) To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2089: wireproto: introduce type for raw byte responses (API)
indygreg updated this revision to Diff 5352. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2089?vs=5341=5352 REVISION DETAIL https://phab.mercurial-scm.org/D2089 AFFECTED FILES hgext/largefiles/proto.py mercurial/wireproto.py mercurial/wireprotoserver.py mercurial/wireprototypes.py tests/sshprotoext.py tests/test-wireproto.py CHANGE DETAILS diff --git a/tests/test-wireproto.py b/tests/test-wireproto.py --- a/tests/test-wireproto.py +++ b/tests/test-wireproto.py @@ -1,8 +1,10 @@ from __future__ import absolute_import, print_function from mercurial import ( +error, util, wireproto, +wireprototypes, ) stringio = util.stringio @@ -42,7 +44,13 @@ return ['batch'] def _call(self, cmd, **args): -return wireproto.dispatch(self.serverrepo, proto(args), cmd) +res = wireproto.dispatch(self.serverrepo, proto(args), cmd) +if isinstance(res, wireprototypes.bytesresponse): +return res.data +elif isinstance(res, bytes): +return res +else: +raise error.Abort('dummy client does not support response type') def _callstream(self, cmd, **args): return stringio(self._call(cmd, **args)) diff --git a/tests/sshprotoext.py b/tests/sshprotoext.py --- a/tests/sshprotoext.py +++ b/tests/sshprotoext.py @@ -49,7 +49,7 @@ l = self._fin.readline() assert l == b'between\n' rsp = wireproto.dispatch(self._repo, self._proto, b'between') -wireprotoserver._sshv1respondbytes(self._fout, rsp) +wireprotoserver._sshv1respondbytes(self._fout, rsp.data) super(prehelloserver, self).serve_forever() @@ -74,7 +74,7 @@ # Send the upgrade response. self._fout.write(b'upgraded %s %s\n' % (token, name)) servercaps = wireproto.capabilities(self._repo, self._proto) -rsp = b'capabilities: %s' % servercaps +rsp = b'capabilities: %s' % servercaps.data self._fout.write(b'%d\n' % len(rsp)) self._fout.write(rsp) self._fout.write(b'\n') diff --git a/mercurial/wireprototypes.py b/mercurial/wireprototypes.py --- a/mercurial/wireprototypes.py +++ b/mercurial/wireprototypes.py @@ -5,6 +5,11 @@ from __future__ import absolute_import +class bytesresponse(object): +"""A wire protocol response consisting of raw bytes.""" +def __init__(self, data): +self.data = data + class ooberror(object): """wireproto reply: failure of a batch of operation diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -274,6 +274,9 @@ if isinstance(rsp, bytes): req.respond(HTTP_OK, HGTYPE, body=rsp) return [] +elif isinstance(rsp, wireprototypes.bytesresponse): +req.respond(HTTP_OK, HGTYPE, body=rsp.data) +return [] elif isinstance(rsp, wireprototypes.streamreslegacy): gen = rsp.gen req.respond(HTTP_OK, HGTYPE) @@ -435,6 +438,8 @@ if isinstance(rsp, bytes): _sshv1respondbytes(self._fout, rsp) +elif isinstance(rsp, wireprototypes.bytesresponse): +_sshv1respondbytes(self._fout, rsp.data) elif isinstance(rsp, wireprototypes.streamres): _sshv1respondstream(self._fout, rsp) elif isinstance(rsp, wireprototypes.streamreslegacy): diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -37,6 +37,7 @@ urlerr = util.urlerr urlreq = util.urlreq +bytesresponse = wireprototypes.bytesresponse ooberror = wireprototypes.ooberror pushres = wireprototypes.pushres pusherr = wireprototypes.pusherr @@ -696,16 +697,24 @@ result = func(repo, proto) if isinstance(result, ooberror): return result + +# For now, all batchable commands must return bytesresponse or +# raw bytes (for backwards compatibility). +assert isinstance(result, (bytesresponse, bytes)) +if isinstance(result, bytesresponse): +result = result.data res.append(escapearg(result)) -return ';'.join(res) + +return bytesresponse(';'.join(res)) @wireprotocommand('between', 'pairs') def between(repo, proto, pairs): pairs = [decodelist(p, '-') for p in pairs.split(" ")] r = [] for b in repo.between(pairs): r.append(encodelist(b) + "\n") -return "".join(r) + +return bytesresponse(''.join(r)) @wireprotocommand('branchmap') def branchmap(repo, proto): @@ -715,15 +724,17 @@ branchname = urlreq.quote(encoding.fromlocal(branch)) branchnodes = encodelist(nodes) heads.append('%s %s' % (branchname, branchnodes)) -return '\n'.join(heads) + +return bytesresponse('\n'.join(heads)) @wireprotocommand('branches', 'nodes') def
D2084: wireprotoserver: rename _client to client (API)
indygreg updated this revision to Diff 5347. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2084?vs=5336=5347 REVISION DETAIL https://phab.mercurial-scm.org/D2084 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -88,6 +88,10 @@ won't be captured. """ +@abc.abstractmethod +def client(self): +"""Returns a string representation of this client (as bytes).""" + def decodevaluefromheaders(req, headerprefix): """Decode a long value from multiple HTTP request headers. @@ -164,7 +168,7 @@ self._ui.fout = oldout self._ui.ferr = olderr -def _client(self): +def client(self): return 'remote:%s:%s:%s' % ( self._req.env.get('wsgi.url_scheme') or 'http', urlreq.quote(self._req.env.get('REMOTE_HOST', '')), @@ -399,7 +403,7 @@ def mayberedirectstdio(self): yield None -def _client(self): +def client(self): client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] return 'remote:ssh:' + client diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -1023,7 +1023,7 @@ hint=bundle2requiredhint) r = exchange.unbundle(repo, gen, their_heads, 'serve', - proto._client()) + proto.client()) if util.safehasattr(r, 'addpart'): # The return looks streamable, we are in the bundle2 case # and should return a stream. To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2082: wireproto: use maybecapturestdio() for push responses (API)
indygreg updated this revision to Diff 5345. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2082?vs=5334=5345 REVISION DETAIL https://phab.mercurial-scm.org/D2082 AFFECTED FILES hgext/largefiles/proto.py mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -320,15 +320,13 @@ req.respond(HTTP_OK, mediatype) return gen elif isinstance(rsp, wireproto.pushres): -val = proto.restore() -rsp = '%d\n%s' % (rsp.res, val) +rsp = '%d\n%s' % (rsp.res, rsp.output) req.respond(HTTP_OK, HGTYPE, body=rsp) return [] elif isinstance(rsp, wireproto.pusherr): # This is the httplib workaround documented in _handlehttperror(). req.drain() -proto.restore() rsp = '0\n%s\n' % rsp.res req.respond(HTTP_OK, HGTYPE, body=rsp) return [] diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -510,16 +510,18 @@ The call was successful and returned an integer contained in `self.res`. """ -def __init__(self, res): +def __init__(self, res, output): self.res = res +self.output = output class pusherr(object): """wireproto reply: failure The call failed. The `self.res` attribute contains the error message. """ -def __init__(self, res): +def __init__(self, res, output): self.res = res +self.output = output class ooberror(object): """wireproto reply: failure of a batch of operation @@ -997,97 +999,98 @@ def unbundle(repo, proto, heads): their_heads = decodelist(heads) -try: -proto.redirect() - -exchange.check_heads(repo, their_heads, 'preparing changes') - -# write bundle data to temporary file because it can be big -fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') -fp = os.fdopen(fd, pycompat.sysstr('wb+')) -r = 0 +with proto.mayberedirectstdio() as output: try: -proto.getfile(fp) -fp.seek(0) -gen = exchange.readbundle(repo.ui, fp, None) -if (isinstance(gen, changegroupmod.cg1unpacker) -and not bundle1allowed(repo, 'push')): -if proto.name == 'http': -# need to special case http because stderr do not get to -# the http client on failed push so we need to abuse some -# other error type to make sure the message get to the -# user. -return ooberror(bundle2required) -raise error.Abort(bundle2requiredmain, - hint=bundle2requiredhint) +exchange.check_heads(repo, their_heads, 'preparing changes') -r = exchange.unbundle(repo, gen, their_heads, 'serve', - proto._client()) -if util.safehasattr(r, 'addpart'): -# The return looks streamable, we are in the bundle2 case and -# should return a stream. -return streamres_legacy(gen=r.getchunks()) -return pushres(r) - -finally: -fp.close() -os.unlink(tempname) - -except (error.BundleValueError, error.Abort, error.PushRaced) as exc: -# handle non-bundle2 case first -if not getattr(exc, 'duringunbundle2', False): +# write bundle data to temporary file because it can be big +fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') +fp = os.fdopen(fd, pycompat.sysstr('wb+')) +r = 0 try: -raise -except error.Abort: -# The old code we moved used util.stderr directly. -# We did not change it to minimise code change. -# This need to be moved to something proper. -# Feel free to do it. -util.stderr.write("abort: %s\n" % exc) -if exc.hint is not None: -util.stderr.write("(%s)\n" % exc.hint) -return pushres(0) -except error.PushRaced: -return pusherr(str(exc)) +proto.getfile(fp) +fp.seek(0) +gen = exchange.readbundle(repo.ui, fp, None) +if (isinstance(gen, changegroupmod.cg1unpacker) +and not bundle1allowed(repo, 'push')): +if proto.name == 'http': +# need to special case http because stderr do not get to +# the http client on failed push so we need to abuse +# some other error type to make sure the message get to +
D2081: wireprotoserver: add context manager mechanism for redirecting stdio
indygreg updated this revision to Diff 5344. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2081?vs=5333=5344 REVISION DETAIL https://phab.mercurial-scm.org/D2081 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -8,6 +8,7 @@ import abc import cgi +import contextlib import struct import sys @@ -74,6 +75,20 @@ """ @abc.abstractmethod +def mayberedirectstdio(self): +"""Context manager to possibly redirect stdio. + +The context manager yields a file-object like object that receives +stdout and stderr output when the context manager is active. Or it +yields ``None`` if no I/O redirection occurs. + +The intent of this context manager is to capture stdio output +so it may be sent in the response. Some transports support streaming +stdio to the client in real time. For these transports, stdio output +won't be captured. +""" + +@abc.abstractmethod def redirect(self): """may setup interception for stdout and stderr @@ -151,6 +166,21 @@ for s in util.filechunkiter(self._req, limit=length): fp.write(s) +@contextlib.contextmanager +def mayberedirectstdio(self): +oldout = self._ui.fout +olderr = self._ui.ferr + +out = util.stringio() + +try: +self._ui.fout = out +self._ui.ferr = out +yield out +finally: +self._ui.fout = oldout +self._ui.ferr = olderr + def redirect(self): self._oldio = self._ui.fout, self._ui.ferr self._ui.ferr = self._ui.fout = stringio() @@ -393,6 +423,10 @@ fpout.write(self._fin.read(count)) count = int(self._fin.readline()) +@contextlib.contextmanager +def mayberedirectstdio(self): +yield None + def redirect(self): pass diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -978,20 +978,12 @@ else: new = encoding.tolocal(new) # normal path -if util.safehasattr(proto, 'restore'): - -proto.redirect() - +with proto.mayberedirectstdio() as output: r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), encoding.tolocal(old), new) or False -output = proto.restore() - -return '%s\n%s' % (int(r), output) - -r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), - encoding.tolocal(old), new) -return '%s\n' % int(r) +output = output.getvalue() if output else '' +return '%s\n%s' % (int(r), output) @wireprotocommand('stream_out') def stream(repo, proto): To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2091: wireprotoserver: extract SSH response handling functions
indygreg updated this revision to Diff 5342. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2091?vs=5331=5342 REVISION DETAIL https://phab.mercurial-scm.org/D2091 AFFECTED FILES mercurial/wireprotoserver.py tests/sshprotoext.py CHANGE DETAILS diff --git a/tests/sshprotoext.py b/tests/sshprotoext.py --- a/tests/sshprotoext.py +++ b/tests/sshprotoext.py @@ -45,11 +45,11 @@ l = self._fin.readline() assert l == b'hello\n' # Respond to unknown commands with an empty reply. -self._sendresponse(b'') +wireprotoserver._sshv1respondbytes(self._fout, b'') l = self._fin.readline() assert l == b'between\n' rsp = wireproto.dispatch(self._repo, self, b'between') -self._handlers[rsp.__class__](self, rsp) +wireprotoserver._sshv1respondbytes(self._fout, rsp) super(prehelloserver, self).serve_forever() diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -336,6 +336,24 @@ return '' +def _sshv1respondbytes(fout, value): +"""Send a bytes response for protocol version 1.""" +fout.write('%d\n' % len(value)) +fout.write(value) +fout.flush() + +def _sshv1respondstream(fout, source): +write = fout.write +for chunk in source.gen: +write(chunk) +fout.flush() + +def _sshv1respondooberror(fout, ferr, rsp): +ferr.write(b'%s\n-\n' % rsp) +ferr.flush() +fout.write(b'\n') +fout.flush() + class sshserver(baseprotocolhandler): def __init__(self, ui, repo): self._ui = ui @@ -376,60 +394,43 @@ return [data[k] for k in keys] def getfile(self, fpout): -self._sendresponse('') +_sshv1respondbytes(self._fout, b'') count = int(self._fin.readline()) while count: fpout.write(self._fin.read(count)) count = int(self._fin.readline()) def redirect(self): pass -def _sendresponse(self, v): -self._fout.write("%d\n" % len(v)) -self._fout.write(v) -self._fout.flush() - -def _sendstream(self, source): -write = self._fout.write -for chunk in source.gen: -write(chunk) -self._fout.flush() - -def _sendpushresponse(self, rsp): -self._sendresponse('') -self._sendresponse(str(rsp.res)) - -def _sendpusherror(self, rsp): -self._sendresponse(rsp.res) - -def _sendooberror(self, rsp): -self._ui.ferr.write('%s\n-\n' % rsp.message) -self._ui.ferr.flush() -self._fout.write('\n') -self._fout.flush() - def serve_forever(self): while self.serve_one(): pass sys.exit(0) -_handlers = { -str: _sendresponse, -wireproto.streamres: _sendstream, -wireproto.streamres_legacy: _sendstream, -wireproto.pushres: _sendpushresponse, -wireproto.pusherr: _sendpusherror, -wireproto.ooberror: _sendooberror, -} - def serve_one(self): cmd = self._fin.readline()[:-1] if cmd and wireproto.commands.commandavailable(cmd, self): rsp = wireproto.dispatch(self._repo, self, cmd) -self._handlers[rsp.__class__](self, rsp) + +if isinstance(rsp, bytes): +_sshv1respondbytes(self._fout, rsp) +elif isinstance(rsp, wireproto.streamres): +_sshv1respondstream(self._fout, rsp) +elif isinstance(rsp, wireproto.streamres_legacy): +_sshv1respondstream(self._fout, rsp) +elif isinstance(rsp, wireproto.pushres): +_sshv1respondbytes(self._fout, b'') +_sshv1respondbytes(self._fout, bytes(rsp.res)) +elif isinstance(rsp, wireproto.pusherr): +_sshv1respondbytes(self._fout, rsp.res) +elif isinstance(rsp, wireproto.ooberror): +_sshv1respondooberror(self._fout, self._ui.ferr, rsp.message) +else: +raise error.ProgrammingError('unhandled response type from ' + 'wire protocol command: %s' % rsp) elif cmd: -self._sendresponse("") +_sshv1respondbytes(self._fout, b'') return cmd != '' def _client(self): To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[PATCH] tests: stabilize ssh tests on Windows
# HG changeset patch # User Matt Harbison# Date 1518064968 18000 # Wed Feb 07 23:42:48 2018 -0500 # Node ID 620577fa68a7c6cd6b473c72af9108303bc23167 # Parent 258a474c5f1ba7e9d61f15c5d8f548f5e01e4f95 tests: stabilize ssh tests on Windows This seems like a somewhat common type of failure (double vs single quote), so I'm interested in ideas about how to avoid this. I doubt that we should automatically fall back from single quote to double quote, like with '/' vs '\'. diff --git a/tests/test-debugcommands.t b/tests/test-debugcommands.t --- a/tests/test-debugcommands.t +++ b/tests/test-debugcommands.t @@ -390,7 +390,8 @@ pushable: yes $ hg --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" --debug debugpeer ssh://user@dummy/debugrevlog - running "*" "*/tests/dummyssh" 'user@dummy' 'hg -R debugrevlog serve --stdio' (glob) + running "*" "*/tests/dummyssh" 'user@dummy' 'hg -R debugrevlog serve --stdio' (glob) (no-windows !) + running "*" "*\tests/dummyssh" "user@dummy" "hg -R debugrevlog serve --stdio" (glob) (windows !) devel-peer-request: hello sending hello command devel-peer-request: between diff --git a/tests/test-ssh-proto.t b/tests/test-ssh-proto.t --- a/tests/test-ssh-proto.t +++ b/tests/test-ssh-proto.t @@ -17,7 +17,8 @@ Test a normal behaving server, for sanity $ hg --debug debugpeer ssh://user@dummy/server - running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) devel-peer-request: hello sending hello command devel-peer-request: between @@ -63,7 +64,8 @@ --debug will print the banner $ SSHSERVERMODE=banner hg --debug debugpeer ssh://user@dummy/server - running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) devel-peer-request: hello sending hello command devel-peer-request: between @@ -114,7 +116,8 @@ servers. $ SSHSERVERMODE=no-hello hg --debug debugpeer ssh://user@dummy/server - running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) devel-peer-request: hello sending hello command devel-peer-request: between @@ -141,7 +144,8 @@ $ hg --config sshpeer.mode=extra-handshake-commands --config sshpeer.handshake-mode=pre-no-args --debug debugpeer ssh://user@dummy/server - running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) sending no-args command devel-peer-request: hello sending hello command @@ -176,7 +180,8 @@ $ hg --config sshpeer.mode=extra-handshake-commands --config sshpeer.handshake-mode=pre-multiple-no-args --debug debugpeer ssh://user@dummy/server - running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) sending unknown1 command sending unknown2 command sending unknown3 command @@ -405,7 +410,8 @@ $ hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server - running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) sending upgrade request: * proto=exp-ssh-v2-0001 (glob) devel-peer-request: hello sending hello command @@ -434,7 +440,8 @@ capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN $ SSHSERVERMODE=upgradev2 hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server - running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) sending upgrade request: * proto=exp-ssh-v2-0001 (glob) devel-peer-request: hello sending hello command @@ -449,7 +456,8 @@
D2089: wireproto: introduce type for raw byte responses (API)
indygreg updated this revision to Diff 5341. indygreg edited the summary of this revision. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2089?vs=5325=5341 REVISION DETAIL https://phab.mercurial-scm.org/D2089 AFFECTED FILES hgext/largefiles/proto.py mercurial/wireproto.py mercurial/wireprotoserver.py mercurial/wireprototypes.py tests/sshprotoext.py tests/test-wireproto.py CHANGE DETAILS diff --git a/tests/test-wireproto.py b/tests/test-wireproto.py --- a/tests/test-wireproto.py +++ b/tests/test-wireproto.py @@ -1,8 +1,10 @@ from __future__ import absolute_import, print_function from mercurial import ( +error, util, wireproto, +wireprototypes, ) stringio = util.stringio @@ -42,7 +44,13 @@ return ['batch'] def _call(self, cmd, **args): -return wireproto.dispatch(self.serverrepo, proto(args), cmd) +res = wireproto.dispatch(self.serverrepo, proto(args), cmd) +if isinstance(res, wireprototypes.bytesresponse): +return res.data +elif isinstance(res, bytes): +return res +else: +raise error.Abort('dummy client does not support response type') def _callstream(self, cmd, **args): return stringio(self._call(cmd, **args)) diff --git a/tests/sshprotoext.py b/tests/sshprotoext.py --- a/tests/sshprotoext.py +++ b/tests/sshprotoext.py @@ -49,7 +49,7 @@ l = self._fin.readline() assert l == b'between\n' rsp = wireproto.dispatch(self._repo, self._proto, b'between') -wireprotoserver._sshv1respondbytes(self._fout, rsp) +wireprotoserver._sshv1respondbytes(self._fout, rsp.data) super(prehelloserver, self).serve_forever() @@ -74,7 +74,7 @@ # Send the upgrade response. self._fout.write(b'upgraded %s %s\n' % (token, name)) servercaps = wireproto.capabilities(self._repo, self._proto) -rsp = b'capabilities: %s' % servercaps +rsp = b'capabilities: %s' % servercaps.data self._fout.write(b'%d\n' % len(rsp)) self._fout.write(rsp) self._fout.write(b'\n') diff --git a/mercurial/wireprototypes.py b/mercurial/wireprototypes.py --- a/mercurial/wireprototypes.py +++ b/mercurial/wireprototypes.py @@ -5,6 +5,11 @@ from __future__ import absolute_import +class bytesresponse(object): +"""A wire protocol response consisting of raw bytes.""" +def __init__(self, data): +self.data = data + class ooberror(object): """wireproto reply: failure of a batch of operation diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -274,6 +274,9 @@ if isinstance(rsp, bytes): req.respond(HTTP_OK, HGTYPE, body=rsp) return [] +elif isinstance(rsp, wireprototypes.bytesresponse): +req.respond(HTTP_OK, HGTYPE, body=rsp.data) +return [] elif isinstance(rsp, wireprototypes.streamreslegacy): gen = rsp.gen req.respond(HTTP_OK, HGTYPE) @@ -435,6 +438,8 @@ if isinstance(rsp, bytes): _sshv1respondbytes(self._fout, rsp) +elif isinstance(rsp, wireprototypes.bytesresponse): +_sshv1respondbytes(self._fout, rsp.data) elif isinstance(rsp, wireprototypes.streamres): _sshv1respondstream(self._fout, rsp) elif isinstance(rsp, wireprototypes.streamreslegacy): diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -37,6 +37,7 @@ urlerr = util.urlerr urlreq = util.urlreq +bytesresponse = wireprototypes.bytesresponse ooberror = wireprototypes.ooberror pushres = wireprototypes.pushres pusherr = wireprototypes.pusherr @@ -696,16 +697,24 @@ result = func(repo, proto) if isinstance(result, ooberror): return result + +# For now, all batchable commands must return bytesresponse or +# raw bytes (for backwards compatibility). +assert isinstance(result, (bytesresponse, bytes)) +if isinstance(result, bytesresponse): +result = result.data res.append(escapearg(result)) -return ';'.join(res) + +return bytesresponse(';'.join(res)) @wireprotocommand('between', 'pairs') def between(repo, proto, pairs): pairs = [decodelist(p, '-') for p in pairs.split(" ")] r = [] for b in repo.between(pairs): r.append(encodelist(b) + "\n") -return "".join(r) + +return bytesresponse(''.join(r)) @wireprotocommand('branchmap') def branchmap(repo, proto): @@ -715,15 +724,17 @@ branchname = urlreq.quote(encoding.fromlocal(branch)) branchnodes = encodelist(nodes) heads.append('%s %s' % (branchname, branchnodes)) -return '\n'.join(heads) + +return bytesresponse('\n'.join(heads))
D2086: wireproto: remove unused proto argument from supportedcompengines (API)
indygreg updated this revision to Diff 5338. indygreg retitled this revision from "wireproto: remove unused proto argument from supportedcompengines" to "wireproto: remove unused proto argument from supportedcompengines (API)". REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2086?vs=5320=5338 REVISION DETAIL https://phab.mercurial-scm.org/D2086 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -192,7 +192,7 @@ break # Now find an agreed upon compression format. -for engine in wireproto.supportedcompengines(self._ui, self, +for engine in wireproto.supportedcompengines(self._ui, util.SERVERROLE): if engine.wireprotosupport().name in compformats: opts = {} diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -587,7 +587,7 @@ return ui.configbool('server', 'bundle1') -def supportedcompengines(ui, proto, role): +def supportedcompengines(ui, role): """Obtain the list of supported compression engines for a request.""" assert role in (util.CLIENTROLE, util.SERVERROLE) @@ -824,7 +824,7 @@ # FUTURE advertise minrx and mintx after consulting config option caps.append('httpmediatype=0.1rx,0.1tx,0.2tx') -compengines = supportedcompengines(repo.ui, proto, util.SERVERROLE) +compengines = supportedcompengines(repo.ui, util.SERVERROLE) if compengines: comptypes = ','.join(urlreq.quote(e.wireprotosupport().name) for e in compengines) To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2082: wireproto: use maybecapturestdio() for push responses (API)
indygreg updated this revision to Diff 5334. indygreg edited the summary of this revision. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2082?vs=5316=5334 REVISION DETAIL https://phab.mercurial-scm.org/D2082 AFFECTED FILES hgext/largefiles/proto.py mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -320,15 +320,13 @@ req.respond(HTTP_OK, mediatype) return gen elif isinstance(rsp, wireproto.pushres): -val = proto.restore() -rsp = '%d\n%s' % (rsp.res, val) +rsp = '%d\n%s' % (rsp.res, rsp.output) req.respond(HTTP_OK, HGTYPE, body=rsp) return [] elif isinstance(rsp, wireproto.pusherr): # This is the httplib workaround documented in _handlehttperror(). req.drain() -proto.restore() rsp = '0\n%s\n' % rsp.res req.respond(HTTP_OK, HGTYPE, body=rsp) return [] diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -510,16 +510,18 @@ The call was successful and returned an integer contained in `self.res`. """ -def __init__(self, res): +def __init__(self, res, output): self.res = res +self.output = output class pusherr(object): """wireproto reply: failure The call failed. The `self.res` attribute contains the error message. """ -def __init__(self, res): +def __init__(self, res, output): self.res = res +self.output = output class ooberror(object): """wireproto reply: failure of a batch of operation @@ -997,97 +999,98 @@ def unbundle(repo, proto, heads): their_heads = decodelist(heads) -try: -proto.redirect() - -exchange.check_heads(repo, their_heads, 'preparing changes') - -# write bundle data to temporary file because it can be big -fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') -fp = os.fdopen(fd, pycompat.sysstr('wb+')) -r = 0 +with proto.mayberedirectstdio() as output: try: -proto.getfile(fp) -fp.seek(0) -gen = exchange.readbundle(repo.ui, fp, None) -if (isinstance(gen, changegroupmod.cg1unpacker) -and not bundle1allowed(repo, 'push')): -if proto.name == 'http': -# need to special case http because stderr do not get to -# the http client on failed push so we need to abuse some -# other error type to make sure the message get to the -# user. -return ooberror(bundle2required) -raise error.Abort(bundle2requiredmain, - hint=bundle2requiredhint) +exchange.check_heads(repo, their_heads, 'preparing changes') -r = exchange.unbundle(repo, gen, their_heads, 'serve', - proto._client()) -if util.safehasattr(r, 'addpart'): -# The return looks streamable, we are in the bundle2 case and -# should return a stream. -return streamres_legacy(gen=r.getchunks()) -return pushres(r) - -finally: -fp.close() -os.unlink(tempname) - -except (error.BundleValueError, error.Abort, error.PushRaced) as exc: -# handle non-bundle2 case first -if not getattr(exc, 'duringunbundle2', False): +# write bundle data to temporary file because it can be big +fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') +fp = os.fdopen(fd, pycompat.sysstr('wb+')) +r = 0 try: -raise -except error.Abort: -# The old code we moved used util.stderr directly. -# We did not change it to minimise code change. -# This need to be moved to something proper. -# Feel free to do it. -util.stderr.write("abort: %s\n" % exc) -if exc.hint is not None: -util.stderr.write("(%s)\n" % exc.hint) -return pushres(0) -except error.PushRaced: -return pusherr(str(exc)) +proto.getfile(fp) +fp.seek(0) +gen = exchange.readbundle(repo.ui, fp, None) +if (isinstance(gen, changegroupmod.cg1unpacker) +and not bundle1allowed(repo, 'push')): +if proto.name == 'http': +# need to special case http because stderr do not get to +# the http client on failed push so we need to abuse +# some other error type to make
D2087: wireprotoserver: move responsetype() out of http handler
indygreg updated this revision to Diff 5339. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2087?vs=5321=5339 REVISION DETAIL https://phab.mercurial-scm.org/D2087 AFFECTED FILES mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -170,48 +170,6 @@ urlreq.quote(self._req.env.get('REMOTE_HOST', '')), urlreq.quote(self._req.env.get('REMOTE_USER', ''))) -def responsetype(self, prefer_uncompressed): -"""Determine the appropriate response type and compression settings. - -Returns a tuple of (mediatype, compengine, engineopts). -""" -# Determine the response media type and compression engine based -# on the request parameters. -protocaps = decodevaluefromheaders(self._req, r'X-HgProto').split(' ') - -if '0.2' in protocaps: -# All clients are expected to support uncompressed data. -if prefer_uncompressed: -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 - -# Now find an agreed upon compression format. -for engine in wireproto.supportedcompengines(self._ui, - util.SERVERROLE): -if engine.wireprotosupport().name in compformats: -opts = {} -level = self._ui.configint('server', - '%slevel' % engine.name()) -if level is not None: -opts['level'] = level - -return HGTYPE2, engine, opts - -# No mutually supported compression format. Fall back to the -# legacy protocol. - -# Don't allow untrusted settings because disabling compression or -# setting a very high compression level could lead to flooding -# the server's network or CPU. -opts = {'level': self._ui.configint('server', 'zliblevel')} -return HGTYPE, util.compengines['zlib'], opts - def iscmd(cmd): return cmd in wireproto.commands @@ -252,6 +210,46 @@ 'handleerror': lambda ex: _handlehttperror(ex, req, cmd), } +def _httpresponsetype(ui, req, prefer_uncompressed): +"""Determine the appropriate response type and compression settings. + +Returns a tuple of (mediatype, compengine, engineopts). +""" +# Determine the response media type and compression engine based +# on the request parameters. +protocaps = decodevaluefromheaders(req, r'X-HgProto').split(' ') + +if '0.2' in protocaps: +# All clients are expected to support uncompressed data. +if prefer_uncompressed: +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 + +# Now find an agreed upon compression format. +for engine in wireproto.supportedcompengines(ui, util.SERVERROLE): +if engine.wireprotosupport().name in compformats: +opts = {} +level = ui.configint('server', '%slevel' % engine.name()) +if level is not None: +opts['level'] = level + +return HGTYPE2, engine, opts + +# No mutually supported compression format. Fall back to the +# legacy protocol. + +# Don't allow untrusted settings because disabling compression or +# setting a very high compression level could lead to flooding +# the server's network or CPU. +opts = {'level': ui.configint('server', 'zliblevel')} +return HGTYPE, util.compengines['zlib'], opts + def _callhttp(repo, req, proto, cmd): def genversion2(gen, engine, engineopts): # application/mercurial-0.2 always sends a payload header @@ -284,8 +282,8 @@ # This code for compression should not be streamres specific. It # is here because we only compress streamres at the moment. -mediatype, engine, engineopts = proto.responsetype( -rsp.prefer_uncompressed) +mediatype, engine, engineopts = _httpresponsetype( +repo.ui, req, rsp.prefer_uncompressed) gen = engine.compressstream(gen, engineopts) if mediatype == HGTYPE2: To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org
D2084: wireprotoserver: rename _client to client (API)
indygreg updated this revision to Diff 5336. indygreg edited the summary of this revision. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2084?vs=5318=5336 REVISION DETAIL https://phab.mercurial-scm.org/D2084 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -88,6 +88,10 @@ won't be captured. """ +@abc.abstractmethod +def client(self): +"""Returns a string representation of this client (as bytes).""" + def decodevaluefromheaders(req, headerprefix): """Decode a long value from multiple HTTP request headers. @@ -164,7 +168,7 @@ self._ui.fout = oldout self._ui.ferr = olderr -def _client(self): +def client(self): return 'remote:%s:%s:%s' % ( self._req.env.get('wsgi.url_scheme') or 'http', urlreq.quote(self._req.env.get('REMOTE_HOST', '')), @@ -399,7 +403,7 @@ def mayberedirectstdio(self): yield None -def _client(self): +def client(self): client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] return 'remote:ssh:' + client diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -1023,7 +1023,7 @@ hint=bundle2requiredhint) r = exchange.unbundle(repo, gen, their_heads, 'serve', - proto._client()) + proto.client()) if util.safehasattr(r, 'addpart'): # The return looks streamable, we are in the bundle2 case # and should return a stream. To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2085: wireprotoserver: rename getfile() to forwardpayload() (API)
indygreg updated this revision to Diff 5337. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2085?vs=5319=5337 REVISION DETAIL https://phab.mercurial-scm.org/D2085 AFFECTED FILES hgext/largefiles/proto.py mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -64,14 +64,10 @@ returns a list of values (same order as )""" @abc.abstractmethod -def getfile(self, fp): -"""write the whole content of a file into a file like object +def forwardpayload(self, fp): +"""Read the raw payload and forward to a file. -The file is in the form:: - -(\n)+0\n - -chunk size is the ascii version of the int. +The payload is read in full before the function returns. """ @abc.abstractmethod @@ -145,7 +141,7 @@ args.update(cgi.parse_qs(argvalue, keep_blank_values=True)) return args -def getfile(self, fp): +def forwardpayload(self, fp): length = int(self._req.env[r'CONTENT_LENGTH']) # If httppostargs is used, we need to read Content-Length # minus the amount that was consumed by args. @@ -392,7 +388,12 @@ data[arg] = val return [data[k] for k in keys] -def getfile(self, fpout): +def forwardpayload(self, fpout): +# The file is in the form: +# +# \n +# ... +# 0\n _sshv1respondbytes(self._fout, b'') count = int(self._fin.readline()) while count: diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -1008,7 +1008,7 @@ fp = os.fdopen(fd, pycompat.sysstr('wb+')) r = 0 try: -proto.getfile(fp) +proto.forwardpayload(fp) fp.seek(0) gen = exchange.readbundle(repo.ui, fp, None) if (isinstance(gen, changegroupmod.cg1unpacker) diff --git a/hgext/largefiles/proto.py b/hgext/largefiles/proto.py --- a/hgext/largefiles/proto.py +++ b/hgext/largefiles/proto.py @@ -40,7 +40,7 @@ tmpfp = util.atomictempfile(path, createmode=repo.store.createmode) try: -proto.getfile(tmpfp) +proto.forwardpayload(tmpfp) tmpfp._fp.seek(0) if sha != lfutil.hexsha1(tmpfp._fp): raise IOError(0, _('largefile contents do not match hash')) To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2088: wireprototypes: move wire protocol response types to new module
indygreg updated this revision to Diff 5340. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2088?vs=5324=5340 REVISION DETAIL https://phab.mercurial-scm.org/D2088 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py mercurial/wireprototypes.py CHANGE DETAILS diff --git a/mercurial/wireprototypes.py b/mercurial/wireprototypes.py new file mode 100644 --- /dev/null +++ b/mercurial/wireprototypes.py @@ -0,0 +1,61 @@ +# Copyright 2018 Gregory Szorc+# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +class ooberror(object): +"""wireproto reply: failure of a batch of operation + +Something failed during a batch call. The error message is stored in +`self.message`. +""" +def __init__(self, message): +self.message = message + +class pushres(object): +"""wireproto reply: success with simple integer return + +The call was successful and returned an integer contained in `self.res`. +""" +def __init__(self, res, output): +self.res = res +self.output = output + +class pusherr(object): +"""wireproto reply: failure + +The call failed. The `self.res` attribute contains the error message. +""" +def __init__(self, res, output): +self.res = res +self.output = output + +class streamres(object): +"""wireproto reply: binary stream + +The call was successful and the result is a stream. + +Accepts a generator containing chunks of data to be sent to the client. + +``prefer_uncompressed`` indicates that the data is expected to be +uncompressable and that the stream should therefore use the ``none`` +engine. +""" +def __init__(self, gen=None, prefer_uncompressed=False): +self.gen = gen +self.prefer_uncompressed = prefer_uncompressed + +class streamreslegacy(object): +"""wireproto reply: uncompressed binary stream + +The call was successful and the result is a stream. + +Accepts a generator containing chunks of data to be sent to the client. + +Like ``streamres``, but sends an uncompressed data for "version 1" clients +using the application/mercurial-0.1 media type. +""" +def __init__(self, gen=None): +self.gen = gen diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -20,6 +20,7 @@ pycompat, util, wireproto, +wireprototypes, ) stringio = util.stringio @@ -273,11 +274,11 @@ if isinstance(rsp, bytes): req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.streamres_legacy): +elif isinstance(rsp, wireprototypes.streamreslegacy): gen = rsp.gen req.respond(HTTP_OK, HGTYPE) return gen -elif isinstance(rsp, wireproto.streamres): +elif isinstance(rsp, wireprototypes.streamres): gen = rsp.gen # This code for compression should not be streamres specific. It @@ -291,18 +292,18 @@ req.respond(HTTP_OK, mediatype) return gen -elif isinstance(rsp, wireproto.pushres): +elif isinstance(rsp, wireprototypes.pushres): rsp = '%d\n%s' % (rsp.res, rsp.output) req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.pusherr): +elif isinstance(rsp, wireprototypes.pusherr): # This is the httplib workaround documented in _handlehttperror(). req.drain() rsp = '0\n%s\n' % rsp.res req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.ooberror): +elif isinstance(rsp, wireprototypes.ooberror): rsp = rsp.message req.respond(HTTP_OK, HGERRTYPE, body=rsp) return [] @@ -434,16 +435,16 @@ if isinstance(rsp, bytes): _sshv1respondbytes(self._fout, rsp) -elif isinstance(rsp, wireproto.streamres): +elif isinstance(rsp, wireprototypes.streamres): _sshv1respondstream(self._fout, rsp) -elif isinstance(rsp, wireproto.streamres_legacy): +elif isinstance(rsp, wireprototypes.streamreslegacy): _sshv1respondstream(self._fout, rsp) -elif isinstance(rsp, wireproto.pushres): +elif isinstance(rsp, wireprototypes.pushres): _sshv1respondbytes(self._fout, b'') _sshv1respondbytes(self._fout, bytes(rsp.res)) -elif isinstance(rsp, wireproto.pusherr): +elif isinstance(rsp, wireprototypes.pusherr): _sshv1respondbytes(self._fout, rsp.res) -elif isinstance(rsp, wireproto.ooberror): +elif isinstance(rsp, wireprototypes.ooberror):
D2091: wireprotoserver: extract SSH response handling functions
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY The lookup/dispatch table was cute. But it isn't needed. Future refactors will benefit from the handlers for individual response types living outside the class. As part of this, I snuck in a change that changes a type compare from str to bytes. This has no effect on Python 2. But it might make Python 3 a bit happier. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2091 AFFECTED FILES mercurial/wireprotoserver.py tests/sshprotoext.py CHANGE DETAILS diff --git a/tests/sshprotoext.py b/tests/sshprotoext.py --- a/tests/sshprotoext.py +++ b/tests/sshprotoext.py @@ -45,11 +45,11 @@ l = self._fin.readline() assert l == b'hello\n' # Respond to unknown commands with an empty reply. -self._sendresponse(b'') +wireprotoserver._sshv1respondbytes(self._fout, b'') l = self._fin.readline() assert l == b'between\n' rsp = wireproto.dispatch(self._repo, self, b'between') -self._handlers[rsp.__class__](self, rsp) +wireprotoserver._sshv1respondbytes(self._fout, rsp) super(prehelloserver, self).serve_forever() diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -336,6 +336,24 @@ return '' +def _sshv1respondbytes(fout, value): +"""Send a bytes response for protocol version 1.""" +fout.write('%d\n' % len(value)) +fout.write(value) +fout.flush() + +def _sshv1respondstream(fout, source): +write = fout.write +for chunk in source.gen: +write(chunk) +fout.flush() + +def _sshv1sendooberror(fout, ferr, rsp): +ferr.write(b'%s\n-\n' % rsp.message) +ferr.flush() +fout.write(b'\n') +fout.flush() + class sshserver(baseprotocolhandler): def __init__(self, ui, repo): self._ui = ui @@ -376,60 +394,43 @@ return [data[k] for k in keys] def getfile(self, fpout): -self._sendresponse('') +_sshv1respondbytes(self._fout, b'') count = int(self._fin.readline()) while count: fpout.write(self._fin.read(count)) count = int(self._fin.readline()) def redirect(self): pass -def _sendresponse(self, v): -self._fout.write("%d\n" % len(v)) -self._fout.write(v) -self._fout.flush() - -def _sendstream(self, source): -write = self._fout.write -for chunk in source.gen: -write(chunk) -self._fout.flush() - -def _sendpushresponse(self, rsp): -self._sendresponse('') -self._sendresponse(str(rsp.res)) - -def _sendpusherror(self, rsp): -self._sendresponse(rsp.res) - -def _sendooberror(self, rsp): -self._ui.ferr.write('%s\n-\n' % rsp.message) -self._ui.ferr.flush() -self._fout.write('\n') -self._fout.flush() - def serve_forever(self): while self.serve_one(): pass sys.exit(0) -_handlers = { -str: _sendresponse, -wireproto.streamres: _sendstream, -wireproto.streamres_legacy: _sendstream, -wireproto.pushres: _sendpushresponse, -wireproto.pusherr: _sendpusherror, -wireproto.ooberror: _sendooberror, -} - def serve_one(self): cmd = self._fin.readline()[:-1] if cmd and wireproto.commands.commandavailable(cmd, self): rsp = wireproto.dispatch(self._repo, self, cmd) -self._handlers[rsp.__class__](self, rsp) + +if isinstance(rsp, bytes): +_sshv1respondbytes(self._fout, rsp) +elif isinstance(rsp, wireproto.streamres): +_sshv1respondstream(self._fout, rsp) +elif isinstance(rsp, wireproto.streamres_legacy): +_sshv1respondstream(self._fout, rsp) +elif isinstance(rsp, wireproto.pushres): +_sshv1respondbytes(self._fout, b'') +_sshv1respondbytes(self._fout, bytes(rsp.res)) +elif isinstance(rsp, wireproto.pusherr): +_sshv1respondbytes(self._fout, rsp.res) +elif isinstance(rsp, wireproto.ooberror): +_sshv1sendooberror(self._fout, self._ui.ferr, rsp) +else: +raise error.ProgrammingError('unhandled response type from ' + 'wire protocol command: %s' % rsp) elif cmd: -self._sendresponse("") +_sshv1respondbytes(self._fout, b'') return cmd != '' def _client(self): To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2080: wireprotoserver: split ssh protocol handler and server
indygreg updated this revision to Diff 5332. indygreg edited the summary of this revision. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2080?vs=5314=5332 REVISION DETAIL https://phab.mercurial-scm.org/D2080 AFFECTED FILES mercurial/wireprotoserver.py tests/sshprotoext.py tests/test-sshserver.py CHANGE DETAILS diff --git a/tests/test-sshserver.py b/tests/test-sshserver.py --- a/tests/test-sshserver.py +++ b/tests/test-sshserver.py @@ -24,7 +24,7 @@ def assertparse(self, cmd, input, expected): server = mockserver(input) _func, spec = wireproto.commands[cmd] -self.assertEqual(server.getargs(spec), expected) +self.assertEqual(server._proto.getargs(spec), expected) def mockserver(inbytes): ui = mockui(inbytes) diff --git a/tests/sshprotoext.py b/tests/sshprotoext.py --- a/tests/sshprotoext.py +++ b/tests/sshprotoext.py @@ -48,7 +48,7 @@ wireprotoserver._sshv1respondbytes(self._fout, b'') l = self._fin.readline() assert l == b'between\n' -rsp = wireproto.dispatch(self._repo, self, b'between') +rsp = wireproto.dispatch(self._repo, self._proto, b'between') wireprotoserver._sshv1respondbytes(self._fout, rsp) super(prehelloserver, self).serve_forever() @@ -73,7 +73,7 @@ # Send the upgrade response. self._fout.write(b'upgraded %s %s\n' % (token, name)) -servercaps = wireproto.capabilities(self._repo, self) +servercaps = wireproto.capabilities(self._repo, self._proto) rsp = b'capabilities: %s' % servercaps self._fout.write(b'%d\n' % len(rsp)) self._fout.write(rsp) diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -354,19 +354,12 @@ fout.write(b'\n') fout.flush() -class sshserver(baseprotocolhandler): -def __init__(self, ui, repo): +class sshv1protocolhandler(baseprotocolhandler): +"""Handler for requests services via version 1 of SSH protocol.""" +def __init__(self, ui, fin, fout): self._ui = ui -self._repo = repo -self._fin = ui.fin -self._fout = ui.fout - -hook.redirect(True) -ui.fout = repo.ui.fout = ui.ferr - -# Prevent insertion/deletion of CRs -util.setbinary(self._fin) -util.setbinary(self._fout) +self._fin = fin +self._fout = fout @property def name(self): @@ -403,15 +396,35 @@ def redirect(self): pass +def _client(self): +client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] +return 'remote:ssh:' + client + +class sshserver(object): +def __init__(self, ui, repo): +self._ui = ui +self._repo = repo +self._fin = ui.fin +self._fout = ui.fout + +hook.redirect(True) +ui.fout = repo.ui.fout = ui.ferr + +# Prevent insertion/deletion of CRs +util.setbinary(self._fin) +util.setbinary(self._fout) + +self._proto = sshv1protocolhandler(self._ui, self._fin, self._fout) + def serve_forever(self): while self.serve_one(): pass sys.exit(0) def serve_one(self): cmd = self._fin.readline()[:-1] -if cmd and wireproto.commands.commandavailable(cmd, self): -rsp = wireproto.dispatch(self._repo, self, cmd) +if cmd and wireproto.commands.commandavailable(cmd, self._proto): +rsp = wireproto.dispatch(self._repo, self._proto, cmd) if isinstance(rsp, bytes): _sshv1respondbytes(self._fout, rsp) @@ -432,7 +445,3 @@ elif cmd: _sshv1respondbytes(self._fout, b'') return cmd != '' - -def _client(self): -client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] -return 'remote:ssh:' + client To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2083: wireprotoserver: remove redirect() and restore() (API)
indygreg updated this revision to Diff 5335. indygreg edited the summary of this revision. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2083?vs=5317=5335 REVISION DETAIL https://phab.mercurial-scm.org/D2083 AFFECTED FILES mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -88,23 +88,6 @@ won't be captured. """ -@abc.abstractmethod -def redirect(self): -"""may setup interception for stdout and stderr - -See also the `restore` method.""" - -# If the `redirect` function does install interception, the `restore` -# function MUST be defined. If interception is not used, this function -# MUST NOT be defined. -# -# left commented here on purpose -# -#def restore(self): -#"""reinstall previous stdout and stderr and return intercepted stdout -#""" -#raise NotImplementedError() - def decodevaluefromheaders(req, headerprefix): """Decode a long value from multiple HTTP request headers. @@ -181,15 +164,6 @@ self._ui.fout = oldout self._ui.ferr = olderr -def redirect(self): -self._oldio = self._ui.fout, self._ui.ferr -self._ui.ferr = self._ui.fout = stringio() - -def restore(self): -val = self._ui.fout.getvalue() -self._ui.ferr, self._ui.fout = self._oldio -return val - def _client(self): return 'remote:%s:%s:%s' % ( self._req.env.get('wsgi.url_scheme') or 'http', @@ -425,9 +399,6 @@ def mayberedirectstdio(self): yield None -def redirect(self): -pass - def _client(self): client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] return 'remote:ssh:' + client To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2081: wireprotoserver: add context manager mechanism for redirecting stdio
indygreg updated this revision to Diff 5333. indygreg edited the summary of this revision. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2081?vs=5315=5333 REVISION DETAIL https://phab.mercurial-scm.org/D2081 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -8,6 +8,7 @@ import abc import cgi +import contextlib import struct import sys @@ -74,6 +75,20 @@ """ @abc.abstractmethod +def mayberedirectstdio(self): +"""Context manager to possibly redirect stdio. + +The context manager yields a file-object like object that receives +stdout and stderr output when the context manager is active. Or it +yields ``None`` if no I/O redirection occurs. + +The intent of this context manager is to capture stdio output +so it may be sent in the response. Some transports support streaming +stdio to the client in real time. For these transports, stdio output +won't be captured. +""" + +@abc.abstractmethod def redirect(self): """may setup interception for stdout and stderr @@ -151,6 +166,21 @@ for s in util.filechunkiter(self._req, limit=length): fp.write(s) +@contextlib.contextmanager +def mayberedirectstdio(self): +oldout = self._ui.fout +olderr = self._ui.ferr + +out = util.stringio() + +try: +self._ui.fout = out +self._ui.ferr = out +yield out +finally: +self._ui.fout = oldout +self._ui.ferr = olderr + def redirect(self): self._oldio = self._ui.fout, self._ui.ferr self._ui.ferr = self._ui.fout = stringio() @@ -393,6 +423,10 @@ fpout.write(self._fin.read(count)) count = int(self._fin.readline()) +@contextlib.contextmanager +def mayberedirectstdio(self): +yield None + def redirect(self): pass diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -978,20 +978,12 @@ else: new = encoding.tolocal(new) # normal path -if util.safehasattr(proto, 'restore'): - -proto.redirect() - +with proto.mayberedirectstdio() as output: r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), encoding.tolocal(old), new) or False -output = proto.restore() - -return '%s\n%s' % (int(r), output) - -r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), - encoding.tolocal(old), new) -return '%s\n' % int(r) +output = output.getvalue() if output else '' +return '%s\n%s' % (int(r), output) @wireprotocommand('stream_out') def stream(repo, proto): To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: FYI: Mercurial 4.6 sprint is March 2nd-4th at Google's Cambridge, MA office
> On Feb 6, 2018, at 09:01, Augie Facklerwrote: > > https://www.mercurial-scm.org/wiki/4.6sprint has all the details, and I'll > fill in more as I have them. > > Sorry for not doing the usual planning routine on this, but time got away > from us and we decided to make some decisions quickly rather than waiting for > the usual date-picking routine. > > See (some) of you next month! If you need travel funding, please add yourself to the wiki page soon! We have some travel funding available. If you'd like to take advantage of it, flights need to be booked at least 14 days in advance per the Software Freedom Conservancy's travel policy[1]. I encourage you to e-mail me off-list if you have further questions about travel sponsorship. Likewise, if you or your company would like to donate to cover people's travel expenses, please let me know! [1]: https://sfconservancy.org/projects/policies/conservancy-travel-policy.html pacem in terris / мир / शान्ति / سَلاَم / 平和 Kevin R. Bullock ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2057: translate base85.c into rust code
krbullock added a comment. What would be the advantage of taking this? Since we already have the C implementation, it's not likely to gain us any performance. On the other hand, it might make a good test case for integrating Rust and Python, finding the right API boundaries and experimenting with different approaches, precisely //because// we already have a C implementation. @indygreg @durin42 what are your thoughts about it? REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2057 To: Ivzhh, #hg-reviewers Cc: krbullock, indygreg, durin42, kevincox, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2090: fancyopts: add support for custom multi-arg opts in fancyopts.py
dploch added a comment. In https://phab.mercurial-scm.org/D2090#34906, @indygreg wrote: > The fancyopts code is some of the oldest in Mercurial. We've been wanting to rewrite it for a while. This patch seems like an interesting and more powerful direction to take the parser. > > Out of curiosity, do you have an intended use case in mind? Will that use case be in Mercurial itself or is this for an extension? I didn't have a use case for Mercurial itself in mind, but I wouldn't be surprised if there was one. My intended use case is the 'csv' flag example in the commit description: We have a lot of flags in our internal extensions that require the ["alice,bob", "charlie"] -> ["alice", "bob", "charlie"] behavior, so it would be really nice to be able to declare a shareable customopt for these instead of needing to individually wrap every applicable `opts['flag']` lookup. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2090 To: dploch, #hg-reviewers Cc: indygreg, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2090: fancyopts: add support for custom multi-arg opts in fancyopts.py
indygreg added a comment. The fancyopts code is some of the oldest in Mercurial. We've been wanting to rewrite it for a while. This patch seems like an interesting and more powerful direction to take the parser. Out of curiosity, do you have an intended use case in mind? Will that use case be in Mercurial itself or is this for an extension? REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2090 To: dploch, #hg-reviewers Cc: indygreg, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2090: fancyopts: add support for custom multi-arg opts in fancyopts.py
dploch created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY This allows for more complex multi-arg opt logic, such as "--sum 1 --sum 2" -> 3, "--csv alice,bob --csv charlie" -> ["alice","bob","charlie"]. The current support for callables is insufficient for this. This is done by introducing a 'customopt' class which can be extended for more powerful opts logic. All existing opt-types are converted to use this class, simplifying the fancyopts() logic. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2090 AFFECTED FILES mercurial/fancyopts.py CHANGE DETAILS diff --git a/mercurial/fancyopts.py b/mercurial/fancyopts.py --- a/mercurial/fancyopts.py +++ b/mercurial/fancyopts.py @@ -7,6 +7,7 @@ from __future__ import absolute_import +import abc import functools from .i18n import _ @@ -201,6 +202,66 @@ parsedargs.extend(args[pos:]) return parsedopts, parsedargs +class customopt(object): +"""Manage defaults and mutations for any type of opt.""" + +__metaclass__ = abc.ABCMeta + +def __init__(self, defaultvalue): +self.defaultvalue = defaultvalue + +def _isboolopt(self): +return False + +@abc.abstractmethod +def newstate(self, oldstate, newparam, abort): +"""Adds newparam to oldstate and returns the new state. + +On failure, abort can be called with a string error message.""" + +class _simpleopt(customopt): +def _isboolopt(self): +t = type(self.defaultvalue) +return t is type(False) or t is type(None) + +def newstate(self, oldstate, newparam, abort): +return newparam + +class _callableopt(customopt): +def __init__(self, callable): +self.callable = callable +super(_callableopt, self).__init__(None) + +def newstate(self, oldstate, newparam, abort): +return self.callable(newparam) + +class _listopt(customopt): +def newstate(self, oldstate, newparam, abort): +oldstate.append(newparam) +return oldstate + +class _intopt(customopt): +def newstate(self, oldstate, newparam, abort): +try: +return int(newparam) +except ValueError: +abort(_('expected int')) + +def _defaultopt(default): +"""Returns a default opt implementation, given a default value.""" + +t = type(default) +if isinstance(default, customopt): +return default +elif callable(default): +return _callableopt(default) +elif t is type([]): +return _listopt(default[:]) +elif t is type(1): +return _intopt(default) +else: +return _simpleopt(default) + def fancyopts(args, options, state, gnu=False, early=False, optaliases=None): """ read args, parse options, and store options in state @@ -220,6 +281,7 @@ list - parameter string is added to a list integer - parameter strings is stored as int function - call function with parameter + customopt - subclass of 'customopt' optaliases is a mapping from a canonical option name to a list of additional long options. This exists for preserving backward compatibility @@ -250,18 +312,13 @@ argmap['-' + short] = name for n in onames: argmap['--' + n] = name -defmap[name] = default +defmap[name] = _defaultopt(default) # copy defaults to state -if isinstance(default, list): -state[name] = default[:] -elif callable(default): -state[name] = None -else: -state[name] = default +state[name] = defmap[name].defaultvalue # does it take a parameter? -if not (default is None or default is True or default is False): +if not defmap[name]._isboolopt(): if short: short += ':' onames = [n + '=' for n in onames] @@ -301,21 +358,13 @@ boolval = False name = argmap[opt] obj = defmap[name] -t = type(obj) -if callable(obj): -state[name] = defmap[name](val) -elif t is type(1): -try: -state[name] = int(val) -except ValueError: -raise error.Abort(_('invalid value %r for option %s, ' - 'expected int') % (val, opt)) -elif t is type(''): -state[name] = val -elif t is type([]): -state[name].append(val) -elif t is type(None) or t is type(False): +if obj._isboolopt(): state[name] = boolval +else: +def abort(s): +raise error.Abort( +_('invalid value %r for option %s, %s') % (val, opt, s)) +state[name] = defmap[name].newstate(state[name], val, abort) # return unparsed args return args To: dploch, #hg-reviewers Cc: mercurial-devel
D2068: revlog: do not use delta for lfs revisions
quark updated this revision to Diff 5329. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2068?vs=5328=5329 REVISION DETAIL https://phab.mercurial-scm.org/D2068 AFFECTED FILES mercurial/revlog.py tests/test-lfs-bundle.t tests/test-revlog-raw.py CHANGE DETAILS diff --git a/tests/test-revlog-raw.py b/tests/test-revlog-raw.py --- a/tests/test-revlog-raw.py +++ b/tests/test-revlog-raw.py @@ -114,6 +114,8 @@ else: # suboptimal deltaparent deltaparent = min(0, parentrev) +if not rlog.candelta(deltaparent, r): +deltaparent = -1 return {'node': rlog.node(r), 'p1': pnode, 'p2': node.nullid, 'cs': rlog.node(rlog.linkrev(r)), 'flags': rlog.flags(r), 'deltabase': rlog.node(deltaparent), @@ -151,12 +153,12 @@ for r in rlog: p1 = rlog.node(r - 1) p2 = node.nullid -if r == 0: +if r == 0 or rlog.flags(r): text = rlog.revision(r, raw=True) cachedelta = None else: -# deltaparent is more interesting if it has the EXTSTORED flag. -deltaparent = max([0] + [p for p in range(r - 2) if rlog.flags(p)]) +# deltaparent cannot have EXTSTORED flag. +deltaparent = max([-1] + [p for p in range(r) if not rlog.flags(p)]) text = None cachedelta = (deltaparent, rlog.revdiff(deltaparent, r)) flags = rlog.flags(r) @@ -262,8 +264,9 @@ result.append((text, rawtext)) # Verify flags like isdelta, isext work as expected -if bool(rlog.deltaparent(rev) > -1) != isdelta: -abort('rev %d: isdelta is ineffective' % rev) +# isdelta can be overridden to False if this or p1 has isext set +if bool(rlog.deltaparent(rev) > -1) and not isdelta: +abort('rev %d: isdelta is unexpected' % rev) if bool(rlog.flags(rev)) != isext: abort('rev %d: isext is ineffective' % rev) return result diff --git a/tests/test-lfs-bundle.t b/tests/test-lfs-bundle.t --- a/tests/test-lfs-bundle.t +++ b/tests/test-lfs-bundle.t @@ -90,10 +90,7 @@ Applying src-normal.bundle to dst-normal OK Applying src-normal.bundle to dst-lfs - X@2: unpacking bcc7d23fa6b7: integrity check failed on data/X.i:2 - Y@2: unpacking 46017a6640e7: integrity check failed on data/Y.i:2 - 2 integrity errors encountered! - (first damaged changeset appears to be 2) + CRASHED Applying src-lfs.bundle to dst-normal OK Applying src-lfs.bundle to dst-lfs diff --git a/mercurial/revlog.py b/mercurial/revlog.py --- a/mercurial/revlog.py +++ b/mercurial/revlog.py @@ -404,6 +404,9 @@ for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta): nominateddeltas = [] for candidaterev in candidaterevs: +# no delta for flag processor revision (see "candelta" for why) +if revlog.flags(candidaterev) & REVIDX_KNOWN_FLAGS: +continue candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh) if revlog._isgooddeltainfo(candidatedelta, revinfo.textlen): nominateddeltas.append(candidatedelta) @@ -2087,7 +2090,12 @@ deltacomputer = _deltacomputer(self) revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags) -deltainfo = deltacomputer.finddeltainfo(revinfo, fh) + +# no delta for flag processor revision (see "candelta" for why) +if flags & REVIDX_KNOWN_FLAGS: +deltainfo = None +else: +deltainfo = deltacomputer.finddeltainfo(revinfo, fh) if deltainfo is not None: base = deltainfo.base To: quark, indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2068: revlog: do not use delta for lfs revisions
quark updated this revision to Diff 5328. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2068?vs=5274=5328 REVISION DETAIL https://phab.mercurial-scm.org/D2068 AFFECTED FILES mercurial/revlog.py tests/test-lfs-bundle.t tests/test-revlog-raw.py CHANGE DETAILS diff --git a/tests/test-revlog-raw.py b/tests/test-revlog-raw.py --- a/tests/test-revlog-raw.py +++ b/tests/test-revlog-raw.py @@ -114,6 +114,8 @@ else: # suboptimal deltaparent deltaparent = min(0, parentrev) +if not rlog.candelta(deltaparent, r): +deltaparent = -1 return {'node': rlog.node(r), 'p1': pnode, 'p2': node.nullid, 'cs': rlog.node(rlog.linkrev(r)), 'flags': rlog.flags(r), 'deltabase': rlog.node(deltaparent), @@ -151,12 +153,12 @@ for r in rlog: p1 = rlog.node(r - 1) p2 = node.nullid -if r == 0: +if r == 0 or rlog.flags(r): text = rlog.revision(r, raw=True) cachedelta = None else: -# deltaparent is more interesting if it has the EXTSTORED flag. -deltaparent = max([0] + [p for p in range(r - 2) if rlog.flags(p)]) +# deltaparent cannot have EXTSTORED flag. +deltaparent = max([-1] + [p for p in range(r) if not rlog.flags(p)]) text = None cachedelta = (deltaparent, rlog.revdiff(deltaparent, r)) flags = rlog.flags(r) @@ -262,8 +264,9 @@ result.append((text, rawtext)) # Verify flags like isdelta, isext work as expected -if bool(rlog.deltaparent(rev) > -1) != isdelta: -abort('rev %d: isdelta is ineffective' % rev) +# isdelta can be overridden to False if this or p1 has isext set +if bool(rlog.deltaparent(rev) > -1) and not isdelta: +abort('rev %d: isdelta is unexpected' % rev) if bool(rlog.flags(rev)) != isext: abort('rev %d: isext is ineffective' % rev) return result diff --git a/tests/test-lfs-bundle.t b/tests/test-lfs-bundle.t --- a/tests/test-lfs-bundle.t +++ b/tests/test-lfs-bundle.t @@ -90,10 +90,7 @@ Applying src-normal.bundle to dst-normal OK Applying src-normal.bundle to dst-lfs - X@2: unpacking bcc7d23fa6b7: integrity check failed on data/X.i:2 - Y@2: unpacking 46017a6640e7: integrity check failed on data/Y.i:2 - 2 integrity errors encountered! - (first damaged changeset appears to be 2) + CRASHED Applying src-lfs.bundle to dst-normal OK Applying src-lfs.bundle to dst-lfs diff --git a/mercurial/revlog.py b/mercurial/revlog.py --- a/mercurial/revlog.py +++ b/mercurial/revlog.py @@ -404,6 +404,9 @@ for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta): nominateddeltas = [] for candidaterev in candidaterevs: +# no delta for flag processor revision (see "candelta" for why) +if revlog.flags(candidaterev) & REVIDX_KNOWN_FLAGS: +continue candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh) if revlog._isgooddeltainfo(candidatedelta, revinfo.textlen): nominateddeltas.append(candidatedelta) @@ -2087,7 +2090,12 @@ deltacomputer = _deltacomputer(self) revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags) -deltainfo = deltacomputer.finddeltainfo(revinfo, fh) + +# no delta for flag processor revision (see "candelta" for why) +if flags & REVIDX_KNOWN_FLAGS: +deltainfo = deltacomputer.finddeltainfo(revinfo, fh) +else: +deltainfo = None if deltainfo is not None: base = deltainfo.base To: quark, indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2067: changegroup: do not delta lfs revisions
quark updated this revision to Diff 5327. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2067?vs=5273=5327 REVISION DETAIL https://phab.mercurial-scm.org/D2067 AFFECTED FILES mercurial/changegroup.py mercurial/revlog.py tests/test-lfs-bundle.t tests/test-lfs.t CHANGE DETAILS diff --git a/tests/test-lfs.t b/tests/test-lfs.t --- a/tests/test-lfs.t +++ b/tests/test-lfs.t @@ -349,7 +349,7 @@ uncompressed size of bundle content: * (changelog) (glob) * (manifests) (glob) - * a (glob) + * a (glob) $ hg --config extensions.strip= strip -r 2 --no-backup --force -q $ hg -R bundle.hg log -p -T '{rev} {desc}\n' a 5 branching diff --git a/tests/test-lfs-bundle.t b/tests/test-lfs-bundle.t --- a/tests/test-lfs-bundle.t +++ b/tests/test-lfs-bundle.t @@ -95,6 +95,6 @@ 2 integrity errors encountered! (first damaged changeset appears to be 2) Applying src-lfs.bundle to dst-normal - CRASHED + OK Applying src-lfs.bundle to dst-lfs OK diff --git a/mercurial/revlog.py b/mercurial/revlog.py --- a/mercurial/revlog.py +++ b/mercurial/revlog.py @@ -713,6 +713,18 @@ except KeyError: return False +def candelta(self, baserev, rev): +"""whether two revisions (prev, rev) can be delta-ed or not""" +# Disable delta if either rev requires flag processor (ex. LFS) +# This is because a flag processor can alter the rawtext content that +# the delta will be based on, and two clients could have a same revlog +# node with different flags (i.e. different rawtext contents) and the +# delta could be incompatible. +if ((self.flags(baserev) & REVIDX_KNOWN_FLAGS) +or (self.flags(rev) & REVIDX_KNOWN_FLAGS)): +return False +return True + def clearcaches(self): self._cache = None self._chainbasecache.clear() diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py --- a/mercurial/changegroup.py +++ b/mercurial/changegroup.py @@ -770,6 +770,8 @@ progress(msgbundling, None) def deltaparent(self, revlog, rev, p1, p2, prev): +if not revlog.candelta(prev, rev): +raise error.ProgrammingError('cg1 should not be used in this case') return prev def revchunk(self, revlog, rev, prev, linknode): @@ -829,16 +831,19 @@ # expensive. The revlog caches should have prev cached, meaning # less CPU for changegroup generation. There is likely room to add # a flag and/or config option to control this behavior. -return prev +base = prev elif dp == nullrev: # revlog is configured to use full snapshot for a reason, # stick to full snapshot. -return nullrev +base = nullrev elif dp not in (p1, p2, prev): # Pick prev when we can't be sure remote has the base revision. return prev else: -return dp +base = dp +if base != nullrev and not revlog.candelta(base, rev): +base = nullrev +return base def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags): # Do nothing with flags, it is implicitly 0 in cg1 and cg2 To: quark, indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2066: lfs: add a test showing bundle application could be broken
quark updated this revision to Diff 5326. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2066?vs=5275=5326 REVISION DETAIL https://phab.mercurial-scm.org/D2066 AFFECTED FILES tests/drawdag.py tests/test-lfs-bundle.t CHANGE DETAILS diff --git a/tests/test-lfs-bundle.t b/tests/test-lfs-bundle.t new file mode 100644 --- /dev/null +++ b/tests/test-lfs-bundle.t @@ -0,0 +1,100 @@ +In this test, we want to test LFS bundle application on both LFS and non-LFS +repos. + +To make it more interesting, the file revisions will contain hg filelog +metadata ('\1\n'). The bundle will have 1 file revision overlapping with the +destination repo. + +# rev 1 2 3 +# repo:yesyes no +# bundle: no (base) yes yes (deltabase: 2 if possible) + +It is interesting because rev 2 could have been stored as LFS in the repo, and +non-LFS in the bundle; or vice-versa. + +Init + + $ cat >> $HGRCPATH << EOF + > [extensions] + > lfs= + > drawdag=$TESTDIR/drawdag.py + > [lfs] + > url=file://$TESTTMP/lfs-remote + > EOF + +Helper functions + + $ commitxy() { + > hg debugdrawdag "$@" <<'EOS' + > Y # Y/X=\1\n\nE\nF + > | # Y/Y=\1\n\nG\nH + > X # X/X=\1\n\nC\n + > # X/Y=\1\n\nD\n + > EOS + > } + + $ commitz() { + > hg debugdrawdag "$@" <<'EOS' + > Z # Z/X=\1\n\nI\n + > | # Z/Y=\1\n\nJ\n + > | # Z/Z=\1\nZ + > Y + > EOS + > } + + $ enablelfs() { + > cat >> .hg/hgrc < [lfs] + > track=all() + > EOF + > } + +Generate bundles + + $ for i in normal lfs; do + > NAME=src-$i + > hg init $TESTTMP/$NAME + > cd $TESTTMP/$NAME + > [ $i = lfs ] && enablelfs + > commitxy + > commitz + > hg bundle -q --base X -r Y+Z $TESTTMP/$NAME.bundle + > SRCNAMES="$SRCNAMES $NAME" + > done + +Prepare destination repos + + $ for i in normal lfs; do + > NAME=dst-$i + > hg init $TESTTMP/$NAME + > cd $TESTTMP/$NAME + > [ $i = lfs ] && enablelfs + > commitxy + > DSTNAMES="$DSTNAMES $NAME" + > done + +Apply bundles + + $ for i in $SRCNAMES; do + > for j in $DSTNAMES; do + > echo Applying $i.bundle to $j + > cp -R $TESTTMP/$j $TESTTMP/tmp-$i-$j + > cd $TESTTMP/tmp-$i-$j + > if hg unbundle $TESTTMP/$i.bundle -q 2>/dev/null; then + > hg verify -q && echo OK + > else + > echo CRASHED + > fi + > done + > done + Applying src-normal.bundle to dst-normal + OK + Applying src-normal.bundle to dst-lfs + X@2: unpacking bcc7d23fa6b7: integrity check failed on data/X.i:2 + Y@2: unpacking 46017a6640e7: integrity check failed on data/Y.i:2 + 2 integrity errors encountered! + (first damaged changeset appears to be 2) + Applying src-lfs.bundle to dst-normal + CRASHED + Applying src-lfs.bundle to dst-lfs + OK diff --git a/tests/drawdag.py b/tests/drawdag.py --- a/tests/drawdag.py +++ b/tests/drawdag.py @@ -371,7 +371,8 @@ comments = list(_getcomments(text)) filere = re.compile(br'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M) for name, path, content in filere.findall(b'\n'.join(comments)): -files[name][path] = content.replace(br'\n', b'\n') +content = content.replace(br'\n', b'\n').replace(br'\1', b'\1') +files[name][path] = content committed = {None: node.nullid} # {name: node} To: quark, #hg-reviewers, indygreg Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2088: wireprototypes: move wire protocol response types to new module
indygreg updated this revision to Diff 5324. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2088?vs=5322=5324 REVISION DETAIL https://phab.mercurial-scm.org/D2088 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py mercurial/wireprototypes.py CHANGE DETAILS diff --git a/mercurial/wireprototypes.py b/mercurial/wireprototypes.py new file mode 100644 --- /dev/null +++ b/mercurial/wireprototypes.py @@ -0,0 +1,61 @@ +# Copyright 2018 Gregory Szorc+# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +class ooberror(object): +"""wireproto reply: failure of a batch of operation + +Something failed during a batch call. The error message is stored in +`self.message`. +""" +def __init__(self, message): +self.message = message + +class pushres(object): +"""wireproto reply: success with simple integer return + +The call was successful and returned an integer contained in `self.res`. +""" +def __init__(self, res, output): +self.res = res +self.output = output + +class pusherr(object): +"""wireproto reply: failure + +The call failed. The `self.res` attribute contains the error message. +""" +def __init__(self, res, output): +self.res = res +self.output = output + +class streamres(object): +"""wireproto reply: binary stream + +The call was successful and the result is a stream. + +Accepts a generator containing chunks of data to be sent to the client. + +``prefer_uncompressed`` indicates that the data is expected to be +uncompressable and that the stream should therefore use the ``none`` +engine. +""" +def __init__(self, gen=None, prefer_uncompressed=False): +self.gen = gen +self.prefer_uncompressed = prefer_uncompressed + +class streamreslegacy(object): +"""wireproto reply: uncompressed binary stream + +The call was successful and the result is a stream. + +Accepts a generator containing chunks of data to be sent to the client. + +Like ``streamres``, but sends an uncompressed data for "version 1" clients +using the application/mercurial-0.1 media type. +""" +def __init__(self, gen=None): +self.gen = gen diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -20,6 +20,7 @@ pycompat, util, wireproto, +wireprototypes, ) stringio = util.stringio @@ -273,11 +274,11 @@ if isinstance(rsp, bytes): req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.streamres_legacy): +elif isinstance(rsp, wireprototypes.streamreslegacy): gen = rsp.gen req.respond(HTTP_OK, HGTYPE) return gen -elif isinstance(rsp, wireproto.streamres): +elif isinstance(rsp, wireprototypes.streamres): gen = rsp.gen # This code for compression should not be streamres specific. It @@ -291,18 +292,18 @@ req.respond(HTTP_OK, mediatype) return gen -elif isinstance(rsp, wireproto.pushres): +elif isinstance(rsp, wireprototypes.pushres): rsp = '%d\n%s' % (rsp.res, rsp.output) req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.pusherr): +elif isinstance(rsp, wireprototypes.pusherr): # This is the httplib workaround documented in _handlehttperror(). req.drain() rsp = '0\n%s\n' % rsp.res req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.ooberror): +elif isinstance(rsp, wireprototypes.ooberror): rsp = rsp.message req.respond(HTTP_OK, HGERRTYPE, body=rsp) return [] @@ -409,11 +410,11 @@ _handlers = { str: _sendresponse, -wireproto.streamres: _sendstream, -wireproto.streamres_legacy: _sendstream, -wireproto.pushres: _sendpushresponse, -wireproto.pusherr: _sendpusherror, -wireproto.ooberror: _sendooberror, +wireprototypes.streamres: _sendstream, +wireprototypes.streamreslegacy: _sendstream, +wireprototypes.pushres: _sendpushresponse, +wireprototypes.pusherr: _sendpusherror, +wireprototypes.ooberror: _sendooberror, } def client(self): diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -31,11 +31,18 @@ repository, streamclone, util, +wireprototypes, ) urlerr = util.urlerr urlreq = util.urlreq +ooberror = wireprototypes.ooberror +pushres = wireprototypes.pushres +pusherr = wireprototypes.pusherr +streamres = wireprototypes.streamres +streamres_legacy =
D2089: wireproto: introduce type for raw byte responses (API)
indygreg updated this revision to Diff 5325. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2089?vs=5323=5325 REVISION DETAIL https://phab.mercurial-scm.org/D2089 AFFECTED FILES hgext/largefiles/proto.py mercurial/wireproto.py mercurial/wireprotoserver.py mercurial/wireprototypes.py tests/sshprotoext.py tests/test-wireproto.py CHANGE DETAILS diff --git a/tests/test-wireproto.py b/tests/test-wireproto.py --- a/tests/test-wireproto.py +++ b/tests/test-wireproto.py @@ -1,8 +1,10 @@ from __future__ import absolute_import, print_function from mercurial import ( +error, util, wireproto, +wireprototypes, ) stringio = util.stringio @@ -42,7 +44,13 @@ return ['batch'] def _call(self, cmd, **args): -return wireproto.dispatch(self.serverrepo, proto(args), cmd) +res = wireproto.dispatch(self.serverrepo, proto(args), cmd) +if isinstance(res, wireprototypes.bytesresponse): +return res.data +elif isinstance(res, bytes): +return res +else: +raise error.Abort('dummy client does not support response type') def _callstream(self, cmd, **args): return stringio(self._call(cmd, **args)) diff --git a/tests/sshprotoext.py b/tests/sshprotoext.py --- a/tests/sshprotoext.py +++ b/tests/sshprotoext.py @@ -74,7 +74,7 @@ # Send the upgrade response. self._fout.write(b'upgraded %s %s\n' % (token, name)) servercaps = wireproto.capabilities(self._repo, self._proto) -rsp = b'capabilities: %s' % servercaps +rsp = b'capabilities: %s' % servercaps.data self._fout.write(b'%d\n' % len(rsp)) self._fout.write(rsp) self._fout.write(b'\n') diff --git a/mercurial/wireprototypes.py b/mercurial/wireprototypes.py --- a/mercurial/wireprototypes.py +++ b/mercurial/wireprototypes.py @@ -5,6 +5,11 @@ from __future__ import absolute_import +class bytesresponse(object): +"""A wire protocol response consisting of raw bytes.""" +def __init__(self, data): +self.data = data + class ooberror(object): """wireproto reply: failure of a batch of operation diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -274,6 +274,9 @@ if isinstance(rsp, bytes): req.respond(HTTP_OK, HGTYPE, body=rsp) return [] +elif isinstance(rsp, wireprototypes.bytesresponse): +req.respond(HTTP_OK, HGTYPE, body=rsp.data) +return [] elif isinstance(rsp, wireprototypes.streamreslegacy): gen = rsp.gen req.respond(HTTP_OK, HGTYPE) @@ -389,6 +392,9 @@ self._fout.write(v) self._fout.flush() +def _sendbytes(self, v): +self._sendresponse(v.data) + def _sendstream(self, source): write = self._fout.write for chunk in source.gen: @@ -409,7 +415,8 @@ self._fout.flush() _handlers = { -str: _sendresponse, +bytes: _sendresponse, +wireprototypes.bytesresponse: _sendbytes, wireprototypes.streamres: _sendstream, wireprototypes.streamreslegacy: _sendstream, wireprototypes.pushres: _sendpushresponse, diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -37,6 +37,7 @@ urlerr = util.urlerr urlreq = util.urlreq +bytesresponse = wireprototypes.bytesresponse ooberror = wireprototypes.ooberror pushres = wireprototypes.pushres pusherr = wireprototypes.pusherr @@ -696,16 +697,24 @@ result = func(repo, proto) if isinstance(result, ooberror): return result + +# For now, all batchable commands must return bytesresponse or +# raw bytes (for backwards compatibility). +assert isinstance(result, (bytesresponse, bytes)) +if isinstance(result, bytesresponse): +result = result.data res.append(escapearg(result)) -return ';'.join(res) + +return bytesresponse(';'.join(res)) @wireprotocommand('between', 'pairs') def between(repo, proto, pairs): pairs = [decodelist(p, '-') for p in pairs.split(" ")] r = [] for b in repo.between(pairs): r.append(encodelist(b) + "\n") -return "".join(r) + +return bytesresponse(''.join(r)) @wireprotocommand('branchmap') def branchmap(repo, proto): @@ -715,15 +724,17 @@ branchname = urlreq.quote(encoding.fromlocal(branch)) branchnodes = encodelist(nodes) heads.append('%s %s' % (branchname, branchnodes)) -return '\n'.join(heads) + +return bytesresponse('\n'.join(heads)) @wireprotocommand('branches', 'nodes') def branches(repo, proto, nodes): nodes = decodelist(nodes) r = [] for b in repo.branches(nodes): r.append(encodelist(b) + "\n") -return "".join(r) + +return
D2088: wireprototypes: move wire protocol response types to new module
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY We'll be introducing more types as part of wire protocol version 2. These types are shared between the command handling code (in wireproto.py) and the protocol/transport code in wireprotoserver.py. So they need to go in a new module to prevent a cycle. The types are aliased into the wireproto module, so API compatibility is preserved. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2088 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py mercurial/wireprototypes.py CHANGE DETAILS diff --git a/mercurial/wireprototypes.py b/mercurial/wireprototypes.py new file mode 100644 --- /dev/null +++ b/mercurial/wireprototypes.py @@ -0,0 +1,61 @@ +# Copyright 2018 Gregory Szorc+# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +class ooberror(object): +"""wireproto reply: failure of a batch of operation + +Something failed during a batch call. The error message is stored in +`self.message`. +""" +def __init__(self, message): +self.message = message + +class pushres(object): +"""wireproto reply: success with simple integer return + +The call was successful and returned an integer contained in `self.res`. +""" +def __init__(self, res, output): +self.res = res +self.output = output + +class pusherr(object): +"""wireproto reply: failure + +The call failed. The `self.res` attribute contains the error message. +""" +def __init__(self, res, output): +self.res = res +self.output = output + +class streamres(object): +"""wireproto reply: binary stream + +The call was successful and the result is a stream. + +Accepts a generator containing chunks of data to be sent to the client. + +``prefer_uncompressed`` indicates that the data is expected to be +uncompressable and that the stream should therefore use the ``none`` +engine. +""" +def __init__(self, gen=None, prefer_uncompressed=False): +self.gen = gen +self.prefer_uncompressed = prefer_uncompressed + +class streamreslegacy(object): +"""wireproto reply: uncompressed binary stream + +The call was successful and the result is a stream. + +Accepts a generator containing chunks of data to be sent to the client. + +Like ``streamres``, but sends an uncompressed data for "version 1" clients +using the application/mercurial-0.1 media type. +""" +def __init__(self, gen=None): +self.gen = gen diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -20,6 +20,7 @@ pycompat, util, wireproto, +wireprototypes, ) stringio = util.stringio @@ -273,11 +274,11 @@ if isinstance(rsp, bytes): req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.streamres_legacy): +elif isinstance(rsp, wireprototypes.streamres_legacy): gen = rsp.gen req.respond(HTTP_OK, HGTYPE) return gen -elif isinstance(rsp, wireproto.streamres): +elif isinstance(rsp, wireprototypes.streamres): gen = rsp.gen # This code for compression should not be streamres specific. It @@ -291,18 +292,18 @@ req.respond(HTTP_OK, mediatype) return gen -elif isinstance(rsp, wireproto.pushres): +elif isinstance(rsp, wireprototypes.pushres): rsp = '%d\n%s' % (rsp.res, rsp.output) req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.pusherr): +elif isinstance(rsp, wireprototypes.pusherr): # This is the httplib workaround documented in _handlehttperror(). req.drain() rsp = '0\n%s\n' % rsp.res req.respond(HTTP_OK, HGTYPE, body=rsp) return [] -elif isinstance(rsp, wireproto.ooberror): +elif isinstance(rsp, wireprototypes.ooberror): rsp = rsp.message req.respond(HTTP_OK, HGERRTYPE, body=rsp) return [] @@ -409,11 +410,11 @@ _handlers = { str: _sendresponse, -wireproto.streamres: _sendstream, -wireproto.streamres_legacy: _sendstream, -wireproto.pushres: _sendpushresponse, -wireproto.pusherr: _sendpusherror, -wireproto.ooberror: _sendooberror, +wireprototypes.streamres: _sendstream, +wireprototypes.streamres_legacy: _sendstream, +wireprototypes.pushres: _sendpushresponse, +wireprototypes.pusherr: _sendpusherror, +wireprototypes.ooberror: _sendooberror, } def client(self): diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
D2086: wireproto: remove unused proto argument from supportedcompengines
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY In theory, the protocol should be passed to this function. But the argument isn't being used and it is getting in the way of refactoring. So let's remove it. We can always add it back later if we need it again. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2086 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -192,7 +192,7 @@ break # Now find an agreed upon compression format. -for engine in wireproto.supportedcompengines(self._ui, self, +for engine in wireproto.supportedcompengines(self._ui, util.SERVERROLE): if engine.wireprotosupport().name in compformats: opts = {} diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -587,7 +587,7 @@ return ui.configbool('server', 'bundle1') -def supportedcompengines(ui, proto, role): +def supportedcompengines(ui, role): """Obtain the list of supported compression engines for a request.""" assert role in (util.CLIENTROLE, util.SERVERROLE) @@ -824,7 +824,7 @@ # FUTURE advertise minrx and mintx after consulting config option caps.append('httpmediatype=0.1rx,0.1tx,0.2tx') -compengines = supportedcompengines(repo.ui, proto, util.SERVERROLE) +compengines = supportedcompengines(repo.ui, util.SERVERROLE) if compengines: comptypes = ','.join(urlreq.quote(e.wireprotosupport().name) for e in compengines) To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2089: wireproto: introduce type for raw byte responses (API)
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY Right now we simply return a str/bytes instance for simple responses. I want all wire protocol response types to be strongly typed. So let's invent and use a type for raw bytes responses. While I was here, I also switched a `str` to `bytes` in the ssh protocol handler. That should make Python 3 a bit happier. .. api:: Wire protocol command handlers now return a wireprototypes.bytesresponse instead of a raw bytes instance. Protocol handlers will continue handling bytes instances. However, any extensions wrapping wire protocol commands will need to handle the new type. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2089 AFFECTED FILES hgext/largefiles/proto.py mercurial/wireproto.py mercurial/wireprotoserver.py mercurial/wireprototypes.py tests/sshprotoext.py tests/test-wireproto.py CHANGE DETAILS diff --git a/tests/test-wireproto.py b/tests/test-wireproto.py --- a/tests/test-wireproto.py +++ b/tests/test-wireproto.py @@ -1,8 +1,10 @@ from __future__ import absolute_import, print_function from mercurial import ( +error, util, wireproto, +wireprototypes, ) stringio = util.stringio @@ -42,7 +44,13 @@ return ['batch'] def _call(self, cmd, **args): -return wireproto.dispatch(self.serverrepo, proto(args), cmd) +res = wireproto.dispatch(self.serverrepo, proto(args), cmd) +if isinstance(res, wireprototypes.bytesresponse): +return res.data +elif isinstance(res, bytes): +return res +else: +raise error.Abort('dummy client does not support response type') def _callstream(self, cmd, **args): return stringio(self._call(cmd, **args)) diff --git a/tests/sshprotoext.py b/tests/sshprotoext.py --- a/tests/sshprotoext.py +++ b/tests/sshprotoext.py @@ -74,7 +74,7 @@ # Send the upgrade response. self._fout.write(b'upgraded %s %s\n' % (token, name)) servercaps = wireproto.capabilities(self._repo, self._proto) -rsp = b'capabilities: %s' % servercaps +rsp = b'capabilities: %s' % servercaps.data self._fout.write(b'%d\n' % len(rsp)) self._fout.write(rsp) self._fout.write(b'\n') diff --git a/mercurial/wireprototypes.py b/mercurial/wireprototypes.py --- a/mercurial/wireprototypes.py +++ b/mercurial/wireprototypes.py @@ -5,6 +5,11 @@ from __future__ import absolute_import +class bytesresponse(object): +"""A wire protocol response consisting of raw bytes.""" +def __init__(self, data): +self.data = data + class ooberror(object): """wireproto reply: failure of a batch of operation diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -274,6 +274,9 @@ if isinstance(rsp, bytes): req.respond(HTTP_OK, HGTYPE, body=rsp) return [] +elif isinstance(rsp, wireprototypes.bytesresponse): +req.respond(HTTP_OK, HGTYPE, body=rsp.data) +return [] elif isinstance(rsp, wireprototypes.streamres_legacy): gen = rsp.gen req.respond(HTTP_OK, HGTYPE) @@ -389,6 +392,9 @@ self._fout.write(v) self._fout.flush() +def _sendbytes(self, v): +self._sendresponse(v.data) + def _sendstream(self, source): write = self._fout.write for chunk in source.gen: @@ -409,7 +415,8 @@ self._fout.flush() _handlers = { -str: _sendresponse, +bytes: _sendresponse, +wireprototypes.bytesresponse: _sendbytes, wireprototypes.streamres: _sendstream, wireprototypes.streamres_legacy: _sendstream, wireprototypes.pushres: _sendpushresponse, diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -37,6 +37,7 @@ urlerr = util.urlerr urlreq = util.urlreq +bytesresponse = wireprototypes.bytesresponse ooberror = wireprototypes.ooberror pushres = wireprototypes.pushres pusherr = wireprototypes.pusherr @@ -696,16 +697,24 @@ result = func(repo, proto) if isinstance(result, ooberror): return result + +# For now, all batchable commands must return bytesresponse or +# raw bytes (for backwards compatibility). +assert isinstance(result, (bytesresponse, bytes)) +if isinstance(result, bytesresponse): +result = result.data res.append(escapearg(result)) -return ';'.join(res) + +return bytesresponse(';'.join(res)) @wireprotocommand('between', 'pairs') def between(repo, proto, pairs): pairs = [decodelist(p, '-') for p in pairs.split(" ")] r = [] for b in repo.between(pairs): r.append(encodelist(b) +
D2084: wireprotoserver: rename _client to client (API)
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY This method is called in wireproto.py. It should be part of the public API. .. api:: The ``_client()`` method of the wire protocol handler interface has been renamed to ``client()``. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2084 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -88,6 +88,10 @@ won't be captured. """ +@abc.abstractmethod +def client(self): +"""Returns a string representation of this client (as bytes).""" + def decodevaluefromheaders(req, headerprefix): """Decode a long value from multiple HTTP request headers. @@ -164,7 +168,7 @@ self._ui.fout = oldout self._ui.ferr = olderr -def _client(self): +def client(self): return 'remote:%s:%s:%s' % ( self._req.env.get('wsgi.url_scheme') or 'http', urlreq.quote(self._req.env.get('REMOTE_HOST', '')), @@ -413,7 +417,7 @@ wireproto.ooberror: _sendooberror, } -def _client(self): +def client(self): client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] return 'remote:ssh:' + client diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -1023,7 +1023,7 @@ hint=bundle2requiredhint) r = exchange.unbundle(repo, gen, their_heads, 'serve', - proto._client()) + proto.client()) if util.safehasattr(r, 'addpart'): # The return looks streamable, we are in the bundle2 case # and should return a stream. To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2083: wireprotoserver: remove redirect() and restore() (API)
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY They are no longer used in core. .. api:: redirect() and restore() have been removed from the wire protocol handler interface. Use mayberedirectstdio() instead. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2083 AFFECTED FILES mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -88,23 +88,6 @@ won't be captured. """ -@abc.abstractmethod -def redirect(self): -"""may setup interception for stdout and stderr - -See also the `restore` method.""" - -# If the `redirect` function does install interception, the `restore` -# function MUST be defined. If interception is not used, this function -# MUST NOT be defined. -# -# left commented here on purpose -# -#def restore(self): -#"""reinstall previous stdout and stderr and return intercepted stdout -#""" -#raise NotImplementedError() - def decodevaluefromheaders(req, headerprefix): """Decode a long value from multiple HTTP request headers. @@ -181,15 +164,6 @@ self._ui.fout = oldout self._ui.ferr = olderr -def redirect(self): -self._oldio = self._ui.fout, self._ui.ferr -self._ui.ferr = self._ui.fout = stringio() - -def restore(self): -val = self._ui.fout.getvalue() -self._ui.ferr, self._ui.fout = self._oldio -return val - def _client(self): return 'remote:%s:%s:%s' % ( self._req.env.get('wsgi.url_scheme') or 'http', @@ -406,9 +380,6 @@ def mayberedirectstdio(self): yield None -def redirect(self): -pass - def _sendresponse(self, v): self._fout.write("%d\n" % len(v)) self._fout.write(v) To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2085: wireprotoserver: rename getfile() to forwardpayload() (API)
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY "file" can mean a lot of things. Let's rename the interface method to something more descriptive. While I was here, I moved the docs about the payload format to the implementation of the SSH protocol, because it was lying about what the HTTP payload looked like. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2085 AFFECTED FILES hgext/largefiles/proto.py mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -64,14 +64,10 @@ returns a list of values (same order as )""" @abc.abstractmethod -def getfile(self, fp): -"""write the whole content of a file into a file like object +def forwardpayload(self, fp): +"""Read the raw payload and forward to a file. -The file is in the form:: - -(\n)+0\n - -chunk size is the ascii version of the int. +The payload is read in full before the function returns. """ @abc.abstractmethod @@ -145,7 +141,7 @@ args.update(cgi.parse_qs(argvalue, keep_blank_values=True)) return args -def getfile(self, fp): +def forwardpayload(self, fp): length = int(self._req.env[r'CONTENT_LENGTH']) # If httppostargs is used, we need to read Content-Length # minus the amount that was consumed by args. @@ -373,7 +369,12 @@ data[arg] = val return [data[k] for k in keys] -def getfile(self, fpout): +def forwardpayload(self, fpout): +# The file is in the form: +# +# \n +# ... +# 0\n self._sendresponse('') count = int(self._fin.readline()) while count: diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -1008,7 +1008,7 @@ fp = os.fdopen(fd, pycompat.sysstr('wb+')) r = 0 try: -proto.getfile(fp) +proto.forwardpayload(fp) fp.seek(0) gen = exchange.readbundle(repo.ui, fp, None) if (isinstance(gen, changegroupmod.cg1unpacker) diff --git a/hgext/largefiles/proto.py b/hgext/largefiles/proto.py --- a/hgext/largefiles/proto.py +++ b/hgext/largefiles/proto.py @@ -40,7 +40,7 @@ tmpfp = util.atomictempfile(path, createmode=repo.store.createmode) try: -proto.getfile(tmpfp) +proto.forwardpayload(tmpfp) tmpfp._fp.seek(0) if sha != lfutil.hexsha1(tmpfp._fp): raise IOError(0, _('largefile contents do not match hash')) To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2087: wireprotoserver: move responsetype() out of http handler
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY This is our last public attribute not part of the protocol interface! REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2087 AFFECTED FILES mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -170,48 +170,6 @@ urlreq.quote(self._req.env.get('REMOTE_HOST', '')), urlreq.quote(self._req.env.get('REMOTE_USER', ''))) -def responsetype(self, prefer_uncompressed): -"""Determine the appropriate response type and compression settings. - -Returns a tuple of (mediatype, compengine, engineopts). -""" -# Determine the response media type and compression engine based -# on the request parameters. -protocaps = decodevaluefromheaders(self._req, r'X-HgProto').split(' ') - -if '0.2' in protocaps: -# All clients are expected to support uncompressed data. -if prefer_uncompressed: -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 - -# Now find an agreed upon compression format. -for engine in wireproto.supportedcompengines(self._ui, - util.SERVERROLE): -if engine.wireprotosupport().name in compformats: -opts = {} -level = self._ui.configint('server', - '%slevel' % engine.name()) -if level is not None: -opts['level'] = level - -return HGTYPE2, engine, opts - -# No mutually supported compression format. Fall back to the -# legacy protocol. - -# Don't allow untrusted settings because disabling compression or -# setting a very high compression level could lead to flooding -# the server's network or CPU. -opts = {'level': self._ui.configint('server', 'zliblevel')} -return HGTYPE, util.compengines['zlib'], opts - def iscmd(cmd): return cmd in wireproto.commands @@ -252,6 +210,46 @@ 'handleerror': lambda ex: _handlehttperror(ex, req, cmd), } +def _httpresponsetype(ui, req, prefer_uncompressed): +"""Determine the appropriate response type and compression settings. + +Returns a tuple of (mediatype, compengine, engineopts). +""" +# Determine the response media type and compression engine based +# on the request parameters. +protocaps = decodevaluefromheaders(req, r'X-HgProto').split(' ') + +if '0.2' in protocaps: +# All clients are expected to support uncompressed data. +if prefer_uncompressed: +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 + +# Now find an agreed upon compression format. +for engine in wireproto.supportedcompengines(ui, util.SERVERROLE): +if engine.wireprotosupport().name in compformats: +opts = {} +level = ui.configint('server', '%slevel' % engine.name()) +if level is not None: +opts['level'] = level + +return HGTYPE2, engine, opts + +# No mutually supported compression format. Fall back to the +# legacy protocol. + +# Don't allow untrusted settings because disabling compression or +# setting a very high compression level could lead to flooding +# the server's network or CPU. +opts = {'level': ui.configint('server', 'zliblevel')} +return HGTYPE, util.compengines['zlib'], opts + def _callhttp(repo, req, proto, cmd): def genversion2(gen, engine, engineopts): # application/mercurial-0.2 always sends a payload header @@ -284,8 +282,8 @@ # This code for compression should not be streamres specific. It # is here because we only compress streamres at the moment. -mediatype, engine, engineopts = proto.responsetype( -rsp.prefer_uncompressed) +mediatype, engine, engineopts = _httpresponsetype( +repo.ui, req, rsp.prefer_uncompressed) gen = engine.compressstream(gen, engineopts) if mediatype == HGTYPE2: To: indygreg, #hg-reviewers Cc: mercurial-devel ___
D2082: wireproto: use maybecapturestdio() for push responses (API)
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY The "pushres" and "pusherr" response types currently call proto.restore() in the HTTP protocol. This completes the pairing with proto.redirect() that occurs in the @wireprotocommand functions. (But since the SSH protocol has a no-op redirect(), it doesn't bother calling restore() because it would also be a no-op.) Having the disconnect between these paired calls is very confusing. Knowing that you must use proto.redirect() if returning a "pushres" or a "pusherr" is even wonkier. We replace this confusing code with our new context manager for capturing output (maybe). The "pushres" and "pusherr" types have gained an "output" argument to their constructor and an attribute to hold captured data. The HTTP protocol now retrieves output from these objects. .. api:: ``wireproto.pushres`` and ``wireproto.pusherr`` now explicitly track stdio output. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2082 AFFECTED FILES hgext/largefiles/proto.py mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -320,15 +320,13 @@ req.respond(HTTP_OK, mediatype) return gen elif isinstance(rsp, wireproto.pushres): -val = proto.restore() -rsp = '%d\n%s' % (rsp.res, val) +rsp = '%d\n%s' % (rsp.res, rsp.output) req.respond(HTTP_OK, HGTYPE, body=rsp) return [] elif isinstance(rsp, wireproto.pusherr): # This is the httplib workaround documented in _handlehttperror(). req.drain() -proto.restore() rsp = '0\n%s\n' % rsp.res req.respond(HTTP_OK, HGTYPE, body=rsp) return [] diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -510,16 +510,18 @@ The call was successful and returned an integer contained in `self.res`. """ -def __init__(self, res): +def __init__(self, res, output): self.res = res +self.output = output class pusherr(object): """wireproto reply: failure The call failed. The `self.res` attribute contains the error message. """ -def __init__(self, res): +def __init__(self, res, output): self.res = res +self.output = output class ooberror(object): """wireproto reply: failure of a batch of operation @@ -997,97 +999,98 @@ def unbundle(repo, proto, heads): their_heads = decodelist(heads) -try: -proto.redirect() - -exchange.check_heads(repo, their_heads, 'preparing changes') - -# write bundle data to temporary file because it can be big -fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') -fp = os.fdopen(fd, pycompat.sysstr('wb+')) -r = 0 +with proto.mayberedirectstdio() as output: try: -proto.getfile(fp) -fp.seek(0) -gen = exchange.readbundle(repo.ui, fp, None) -if (isinstance(gen, changegroupmod.cg1unpacker) -and not bundle1allowed(repo, 'push')): -if proto.name == 'http': -# need to special case http because stderr do not get to -# the http client on failed push so we need to abuse some -# other error type to make sure the message get to the -# user. -return ooberror(bundle2required) -raise error.Abort(bundle2requiredmain, - hint=bundle2requiredhint) +exchange.check_heads(repo, their_heads, 'preparing changes') -r = exchange.unbundle(repo, gen, their_heads, 'serve', - proto._client()) -if util.safehasattr(r, 'addpart'): -# The return looks streamable, we are in the bundle2 case and -# should return a stream. -return streamres_legacy(gen=r.getchunks()) -return pushres(r) - -finally: -fp.close() -os.unlink(tempname) - -except (error.BundleValueError, error.Abort, error.PushRaced) as exc: -# handle non-bundle2 case first -if not getattr(exc, 'duringunbundle2', False): +# write bundle data to temporary file because it can be big +fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') +fp = os.fdopen(fd, pycompat.sysstr('wb+')) +r = 0 try: -raise -except error.Abort: -# The old code we moved used util.stderr directly. -# We did not change it to minimise code change. -#
D2081: wireprotoserver: add context manager mechanism for redirecting stdio
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY Today, proto.redirect() sets up redirecting stdio and proto.restore() undoes that. The API is a bit wonky because restore() is only implemented on the HTTP protocol. Furthermore, not all calls to redirect() are obviously paired with calls to restore(). For example, the call to restore() for "unbundle" requests is handled by the response handler for the HTTP protocol. This commit introduces a new method on the protocol handler interface to maybe capture stdio. It emits a file object or None depending on whether stdio capture is used by the transport. To prove it works, the "pushkey" wire protocol command has been updated to use the new API. I'm not convinced this is the best mechanism to capture stdio. I may need to come up with something better once the new wire protocol emerges into existence. But it is strictly better than before and gets us closer to a unified interface between the SSH and HTTP transports. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2081 AFFECTED FILES mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -8,6 +8,7 @@ import abc import cgi +import contextlib import struct import sys @@ -74,6 +75,20 @@ """ @abc.abstractmethod +def mayberedirectstdio(self): +"""Context manager to possibly redirect stdio. + +The context manager yields a file-object like object that receives +stdout and stderr output when the context manager is active. Or it +yields ``None`` if no I/O redirection occurs. + +The intent of this context manager is to capture stdio output +so it may be sent in the response. Some transports support streaming +stdio to the client in real time. For these transports, stdio output +won't be captured. +""" + +@abc.abstractmethod def redirect(self): """may setup interception for stdout and stderr @@ -151,6 +166,21 @@ for s in util.filechunkiter(self._req, limit=length): fp.write(s) +@contextlib.contextmanager +def mayberedirectstdio(self): +oldout = self._ui.fout +olderr = self._ui.ferr + +out = util.stringio() + +try: +self._ui.fout = out +self._ui.ferr = out +yield out +finally: +self._ui.fout = oldout +self._ui.ferr = olderr + def redirect(self): self._oldio = self._ui.fout, self._ui.ferr self._ui.ferr = self._ui.fout = stringio() @@ -374,6 +404,10 @@ fpout.write(self._fin.read(count)) count = int(self._fin.readline()) +@contextlib.contextmanager +def mayberedirectstdio(self): +yield None + def redirect(self): pass diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -978,20 +978,12 @@ else: new = encoding.tolocal(new) # normal path -if util.safehasattr(proto, 'restore'): - -proto.redirect() - +with proto.mayberedirectstdio() as output: r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), encoding.tolocal(old), new) or False -output = proto.restore() - -return '%s\n%s' % (int(r), output) - -r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), - encoding.tolocal(old), new) -return '%s\n' % int(r) +output = output.getvalue() if output else '' +return '%s\n%s' % (int(r), output) @wireprotocommand('stream_out') def stream(repo, proto): To: indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2080: wireprotoserver: split ssh protocol handler and server
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY We want to formalize the interface for protocol handlers. Today, server functionality (which is domain specific) is interleaved with protocol handling functionality (which conforms to a generic interface) in the sshserver class. This commit splits the ssh protocol handling code out of the sshserver class. The state of the new code isn't great in terms of purity: there are still some private attribute accesses from sshserver into sshprotocolhandler that shouldn't be there. This will likely require some interface changes to address. I'm not yet sure how to reconcile things. But this split seems strictly better in terms of isolating the wire protocol interface from the rest of the code. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2080 AFFECTED FILES mercurial/wireprotoserver.py tests/sshprotoext.py tests/test-sshserver.py CHANGE DETAILS diff --git a/tests/test-sshserver.py b/tests/test-sshserver.py --- a/tests/test-sshserver.py +++ b/tests/test-sshserver.py @@ -24,7 +24,7 @@ def assertparse(self, cmd, input, expected): server = mockserver(input) _func, spec = wireproto.commands[cmd] -self.assertEqual(server.getargs(spec), expected) +self.assertEqual(server._proto.getargs(spec), expected) def mockserver(inbytes): ui = mockui(inbytes) diff --git a/tests/sshprotoext.py b/tests/sshprotoext.py --- a/tests/sshprotoext.py +++ b/tests/sshprotoext.py @@ -45,11 +45,11 @@ l = self._fin.readline() assert l == b'hello\n' # Respond to unknown commands with an empty reply. -self._sendresponse(b'') +self._proto._sendresponse(b'') l = self._fin.readline() assert l == b'between\n' -rsp = wireproto.dispatch(self._repo, self, b'between') -self._handlers[rsp.__class__](self, rsp) +rsp = wireproto.dispatch(self._repo, self._proto, b'between') +self._proto._handlers[rsp.__class__](self._proto, rsp) super(prehelloserver, self).serve_forever() @@ -73,7 +73,7 @@ # Send the upgrade response. self._fout.write(b'upgraded %s %s\n' % (token, name)) -servercaps = wireproto.capabilities(self._repo, self) +servercaps = wireproto.capabilities(self._repo, self._proto) rsp = b'capabilities: %s' % servercaps self._fout.write(b'%d\n' % len(rsp)) self._fout.write(rsp) diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -336,19 +336,11 @@ return '' -class sshserver(baseprotocolhandler): -def __init__(self, ui, repo): +class sshprotocolhandler(baseprotocolhandler): +def __init__(self, ui, fin, fout): self._ui = ui -self._repo = repo -self._fin = ui.fin -self._fout = ui.fout - -hook.redirect(True) -ui.fout = repo.ui.fout = ui.ferr - -# Prevent insertion/deletion of CRs -util.setbinary(self._fin) -util.setbinary(self._fout) +self._fin = fin +self._fout = fout @property def name(self): @@ -409,11 +401,6 @@ self._fout.write('\n') self._fout.flush() -def serve_forever(self): -while self.serve_one(): -pass -sys.exit(0) - _handlers = { str: _sendresponse, wireproto.streamres: _sendstream, @@ -423,15 +410,38 @@ wireproto.ooberror: _sendooberror, } -def serve_one(self): -cmd = self._fin.readline()[:-1] -if cmd and wireproto.commands.commandavailable(cmd, self): -rsp = wireproto.dispatch(self._repo, self, cmd) -self._handlers[rsp.__class__](self, rsp) -elif cmd: -self._sendresponse("") -return cmd != '' - def _client(self): client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] return 'remote:ssh:' + client + +class sshserver(object): +def __init__(self, ui, repo): +self._ui = ui +self._repo = repo +self._fin = ui.fin +self._fout = ui.fout + +hook.redirect(True) +ui.fout = repo.ui.fout = ui.ferr + +# Prevent insertion/deletion of CRs +util.setbinary(self._fin) +util.setbinary(self._fout) + +self._proto = sshprotocolhandler(self._ui, self._fin, self._fout) + +def serve_forever(self): +while self.serve_one(): +pass +sys.exit(0) + +def serve_one(self): +# TODO improve boundary between transport layer and protocol handler. +cmd = self._fin.readline()[:-1] +if cmd and wireproto.commands.commandavailable(cmd, self._proto): +rsp = wireproto.dispatch(self._repo, self._proto, cmd) +
D2065: wireprotoserver: rename abstractserverproto and improve docstring
This revision was automatically updated to reflect the committed changes. Closed by commit rHG04231e893a12: wireprotoserver: rename abstractserverproto and improve docstring (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2065?vs=5299=5313 REVISION DETAIL https://phab.mercurial-scm.org/D2065 AFFECTED FILES mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -38,10 +38,13 @@ # to reflect BC breakages. SSHV2 = 'exp-ssh-v2-0001' -class abstractserverproto(object): -"""abstract class that summarizes the protocol API +class baseprotocolhandler(object): +"""Abstract base class for wire protocol handlers. -Used as reference and documentation. +A wire protocol handler serves as an interface between protocol command +handlers and the wire protocol transport layer. Protocol handlers provide +methods to read command arguments, redirect stdio for the duration of +the request, handle response types, etc. """ __metaclass__ = abc.ABCMeta @@ -104,7 +107,7 @@ return ''.join(chunks) -class webproto(abstractserverproto): +class webproto(baseprotocolhandler): def __init__(self, req, ui): self._req = req self._ui = ui @@ -333,7 +336,7 @@ return '' -class sshserver(abstractserverproto): +class sshserver(baseprotocolhandler): def __init__(self, ui, repo): self._ui = ui self._repo = repo To: indygreg, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2018: largefiles: register wire protocol commands with modern APIs
This revision was automatically updated to reflect the committed changes. Closed by commit rHGf540b6448738: largefiles: register wire protocol commands with modern APIs (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2018?vs=5172=5308 REVISION DETAIL https://phab.mercurial-scm.org/D2018 AFFECTED FILES hgext/largefiles/uisetup.py CHANGE DETAILS diff --git a/hgext/largefiles/uisetup.py b/hgext/largefiles/uisetup.py --- a/hgext/largefiles/uisetup.py +++ b/hgext/largefiles/uisetup.py @@ -165,13 +165,13 @@ overrides.openlargefile) # create the new wireproto commands ... -wireproto.commands['putlfile'] = (proto.putlfile, 'sha') -wireproto.commands['getlfile'] = (proto.getlfile, 'sha') -wireproto.commands['statlfile'] = (proto.statlfile, 'sha') +wireproto.wireprotocommand('putlfile', 'sha')(proto.putlfile) +wireproto.wireprotocommand('getlfile', 'sha')(proto.getlfile) +wireproto.wireprotocommand('statlfile', 'sha')(proto.statlfile) +wireproto.wireprotocommand('lheads', '')(wireproto.heads) # ... and wrap some existing ones -wireproto.commands['heads'] = (proto.heads, '') -wireproto.commands['lheads'] = (wireproto.heads, '') +wireproto.commands['heads'].func = proto.heads # make putlfile behave the same as push and {get,stat}lfile behave # the same as pull w.r.t. permissions checks To: indygreg, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1999: wireproto: function for testing if wire protocol command is available
This revision was automatically updated to reflect the committed changes. Closed by commit rHG5a56bf4180ad: wireproto: function for testing if wire protocol command is available (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D1999?vs=5258=5307 REVISION DETAIL https://phab.mercurial-scm.org/D1999 AFFECTED FILES mercurial/hgweb/hgweb_mod.py mercurial/wireproto.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -223,6 +223,13 @@ yield chunk rsp = wireproto.dispatch(repo, proto, cmd) + +if not wireproto.commands.commandavailable(cmd, proto): +req.respond(HTTP_OK, HGERRTYPE, +body=_('requested wire protocol command is not available ' + 'over HTTP')) +return [] + if isinstance(rsp, bytes): req.respond(HTTP_OK, HGTYPE, body=rsp) return [] @@ -351,7 +358,7 @@ def serve_one(self): cmd = self._fin.readline()[:-1] -if cmd and cmd in wireproto.commands: +if cmd and wireproto.commands.commandavailable(cmd, self): rsp = wireproto.dispatch(self._repo, self, cmd) self._handlers[rsp.__class__](self, rsp) elif cmd: diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -691,6 +691,12 @@ return super(commanddict, self).__setitem__(k, v) +def commandavailable(self, command, proto): +"""Determine if a command is available for the requested protocol.""" +# For now, commands are available for all protocols. So do a simple +# membership test. +return command in self + commands = commanddict() def wireprotocommand(name, args=''): diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -357,6 +357,14 @@ query = req.env[r'QUERY_STRING'].partition(r'&')[0] query = query.partition(r';')[0] +# The ``cmd`` request parameter is used by both the wire protocol +# and hgweb. We route all known wire protocol commands to the +# wire protocol handler, even if the command isn't available for +# this transport. That's better for machine clients in the case +# of an errant request to an unavailable protocol command. And it +# prevents hgweb from accidentally using ``cmd`` values used by +# the wire protocol. + # process this if it's a protocol request # protocol bits don't need to create any URLs # and the clients always use the old URL structure To: indygreg, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2021: wireprotoserver: move error response handling out of hgweb
This revision was automatically updated to reflect the committed changes. Closed by commit rHG98a00aa0288d: wireprotoserver: move error response handling out of hgweb (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2021?vs=5260=5311 REVISION DETAIL https://phab.mercurial-scm.org/D2021 AFFECTED FILES mercurial/hgweb/hgweb_mod.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -242,6 +242,7 @@ 'cmd': cmd, 'proto': proto, 'dispatch': lambda: _callhttp(repo, req, proto, cmd), +'handleerror': lambda ex: _handlehttperror(ex, req, cmd), } def _callhttp(repo, req, proto, cmd): @@ -303,6 +304,22 @@ return [] raise error.ProgrammingError('hgweb.protocol internal failure', rsp) +def _handlehttperror(e, req, cmd): +"""Called when an ErrorResponse is raised during HTTP request processing.""" +# A client that sends unbundle without 100-continue will +# break if we respond early. +if (cmd == 'unbundle' and +(req.env.get('HTTP_EXPECT', + '').lower() != '100-continue') or +req.env.get('X-HgHttp2', '')): +req.drain() +else: +req.headers.append((r'Connection', r'Close')) + +req.respond(e, HGTYPE, body='0\n%s\n' % e) + +return '' + class sshserver(abstractserverproto): def __init__(self, ui, repo): self._ui = ui diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -369,18 +369,7 @@ if cmd in perms: self.check_perm(rctx, req, perms[cmd]) except ErrorResponse as inst: -# A client that sends unbundle without 100-continue will -# break if we respond early. -if (cmd == 'unbundle' and -(req.env.get('HTTP_EXPECT', - '').lower() != '100-continue') or -req.env.get('X-HgHttp2', '')): -req.drain() -else: -req.headers.append((r'Connection', r'Close')) -req.respond(inst, wireprotoserver.HGTYPE, -body='0\n%s\n' % inst) -return '' +return protohandler['handleerror'](inst) return protohandler['dispatch']() To: indygreg, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2064: wireprotoserver: document and improve the httplib workaround
This revision was automatically updated to reflect the committed changes. Closed by commit rHG6010fe1da619: wireprotoserver: document and improve the httplib workaround (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2064?vs=5261=5312 REVISION DETAIL https://phab.mercurial-scm.org/D2064 AFFECTED FILES mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -292,8 +292,9 @@ req.respond(HTTP_OK, HGTYPE, body=rsp) return [] elif isinstance(rsp, wireproto.pusherr): -# drain the incoming bundle +# This is the httplib workaround documented in _handlehttperror(). req.drain() + proto.restore() rsp = '0\n%s\n' % rsp.res req.respond(HTTP_OK, HGTYPE, body=rsp) @@ -306,16 +307,28 @@ def _handlehttperror(e, req, cmd): """Called when an ErrorResponse is raised during HTTP request processing.""" -# A client that sends unbundle without 100-continue will -# break if we respond early. -if (cmd == 'unbundle' and + +# Clients using Python's httplib are stateful: the HTTP client +# won't process an HTTP response until all request data is +# sent to the server. The intent of this code is to ensure +# we always read HTTP request data from the client, thus +# ensuring httplib transitions to a state that allows it to read +# the HTTP response. In other words, it helps prevent deadlocks +# on clients using httplib. + +if (req.env[r'REQUEST_METHOD'] == r'POST' and +# But not if Expect: 100-continue is being used. (req.env.get('HTTP_EXPECT', '').lower() != '100-continue') or +# Or the non-httplib HTTP library is being advertised by +# the client. req.env.get('X-HgHttp2', '')): req.drain() else: req.headers.append((r'Connection', r'Close')) +# TODO This response body assumes the failed command was +# "unbundle." That assumption is not always valid. req.respond(e, HGTYPE, body='0\n%s\n' % e) return '' To: indygreg, #hg-reviewers, durin42 Cc: durin42, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1996: wireproto: remove unnecessary exception trapping
This revision was automatically updated to reflect the committed changes. Closed by commit rHGae79cf6f9c82: wireproto: remove unnecessary exception trapping (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D1996?vs=5135=5304 REVISION DETAIL https://phab.mercurial-scm.org/D1996 AFFECTED FILES mercurial/wireproto.py CHANGE DETAILS diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -914,11 +914,8 @@ proto.redirect() -try: -r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), - encoding.tolocal(old), new) or False -except error.Abort: -r = False +r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), + encoding.tolocal(old), new) or False output = proto.restore() To: indygreg, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2019: wireprotoserver: move protocol parsing and dispatch out of hgweb
This revision was automatically updated to reflect the committed changes. Closed by commit rHGcdc93fe1da77: wireprotoserver: move protocol parsing and dispatch out of hgweb (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2019?vs=5259=5309 REVISION DETAIL https://phab.mercurial-scm.org/D2019 AFFECTED FILES mercurial/hgweb/hgweb_mod.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -208,9 +208,43 @@ def iscmd(cmd): return cmd in wireproto.commands -def callhttp(repo, req, cmd): +def parsehttprequest(repo, req, query): +"""Parse the HTTP request for a wire protocol request. + +If the current request appears to be a wire protocol request, this +function returns a dict with details about that request, including +an ``abstractprotocolserver`` instance suitable for handling the +request. Otherwise, ``None`` is returned. + +``req`` is a ``wsgirequest`` instance. +""" +# HTTP version 1 wire protocol requests are denoted by a "cmd" query +# string parameter. If it isn't present, this isn't a wire protocol +# request. +if r'cmd' not in req.form: +return None + +cmd = pycompat.sysbytes(req.form[r'cmd'][0]) + +# The "cmd" request parameter is used by both the wire protocol and hgweb. +# While not all wire protocol commands are available for all transports, +# if we see a "cmd" value that resembles a known wire protocol command, we +# route it to a protocol handler. This is better than routing possible +# wire protocol requests to hgweb because it prevents hgweb from using +# known wire protocol commands and it is less confusing for machine +# clients. +if cmd not in wireproto.commands: +return None + proto = webproto(req, repo.ui) +return { +'cmd': cmd, +'proto': proto, +'dispatch': lambda: _callhttp(repo, req, proto, cmd), +} + +def _callhttp(repo, req, proto, cmd): def genversion2(gen, engine, engineopts): # application/mercurial-0.2 always sends a payload header # identifying the compression engine. diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -357,26 +357,18 @@ query = req.env[r'QUERY_STRING'].partition(r'&')[0] query = query.partition(r';')[0] -# The ``cmd`` request parameter is used by both the wire protocol -# and hgweb. We route all known wire protocol commands to the -# wire protocol handler, even if the command isn't available for -# this transport. That's better for machine clients in the case -# of an errant request to an unavailable protocol command. And it -# prevents hgweb from accidentally using ``cmd`` values used by -# the wire protocol. +# Route it to a wire protocol handler if it looks like a wire protocol +# request. +protohandler = wireprotoserver.parsehttprequest(rctx.repo, req, query) -# process this if it's a protocol request -# protocol bits don't need to create any URLs -# and the clients always use the old URL structure - -cmd = pycompat.sysbytes(req.form.get(r'cmd', [r''])[0]) -if wireprotoserver.iscmd(cmd): +if protohandler: +cmd = protohandler['cmd'] try: if query: raise ErrorResponse(HTTP_NOT_FOUND) if cmd in perms: self.check_perm(rctx, req, perms[cmd]) -return wireprotoserver.callhttp(rctx.repo, req, cmd) +return protohandler['dispatch']() except ErrorResponse as inst: # A client that sends unbundle without 100-continue will # break if we respond early. @@ -425,6 +417,8 @@ if fn.endswith(ext): req.form['node'] = [fn[:-len(ext)]] req.form['type'] = [type_] +else: +cmd = pycompat.sysbytes(req.form.get(r'cmd', [r''])[0]) # process the web interface request To: indygreg, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2061: sshpeer: initial definition and implementation of new SSH protocol
This revision was automatically updated to reflect the committed changes. Closed by commit rHG48a3a9283f09: sshpeer: initial definition and implementation of new SSH protocol (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2061?vs=5255=5301 REVISION DETAIL https://phab.mercurial-scm.org/D2061 AFFECTED FILES mercurial/configitems.py mercurial/help/internals/wireprotocol.txt mercurial/sshpeer.py mercurial/wireprotoserver.py tests/sshprotoext.py tests/test-ssh-proto.t CHANGE DETAILS diff --git a/tests/test-ssh-proto.t b/tests/test-ssh-proto.t --- a/tests/test-ssh-proto.t +++ b/tests/test-ssh-proto.t @@ -388,3 +388,107 @@ 0 0 0 + +Send an upgrade request to a server that doesn't support that command + + $ hg -R server serve --stdio << EOF + > upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=irrelevant1%2Cirrelevant2 + > hello + > between + > pairs 81 + > - + > EOF + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + + $ hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + remote: 0 + remote: 384 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + remote: 1 + url: ssh://user@dummy/server + local: no + pushable: yes + +Send an upgrade request to a server that supports upgrade + + $ SSHSERVERMODE=upgradev2 hg -R server serve --stdio << EOF + > upgrade this-is-some-token proto=exp-ssh-v2-0001 + > hello + > between + > pairs 81 + > - + > EOF + upgraded this-is-some-token exp-ssh-v2-0001 + 383 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + + $ SSHSERVERMODE=upgradev2 hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + protocol upgraded to exp-ssh-v2-0001 + url: ssh://user@dummy/server + local: no + pushable: yes + +Verify the peer has capabilities + + $ SSHSERVERMODE=upgradev2 hg --config experimental.sshpeer.advertise-v2=true --debug debugcapabilities ssh://user@dummy/server + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + protocol upgraded to exp-ssh-v2-0001 + Main capabilities: +batch +branchmap +$USUAL_BUNDLE2_CAPS_SERVER$ +changegroupsubset +getbundle +known +lookup +pushkey +streamreqs=generaldelta,revlogv1 +unbundle=HG10GZ,HG10BZ,HG10UN +unbundlehash + Bundle2 capabilities: +HG20 +bookmarks +changegroup + 01 + 02 +digests + md5 + sha1 + sha512 +error + abort + unsupportedcontent + pushraced + pushkey +hgtagsfnodes +listkeys +phases + heads +pushkey +remote-changegroup + http + https diff --git a/tests/sshprotoext.py b/tests/sshprotoext.py --- a/tests/sshprotoext.py +++ b/tests/sshprotoext.py @@ -53,6 +53,35 @@ super(prehelloserver, self).serve_forever() +class upgradev2server(wireprotoserver.sshserver): +"""Tests behavior for clients that issue upgrade to version 2.""" +def serve_forever(self): +name = wireprotoserver.SSHV2 +l = self._fin.readline() +assert l.startswith(b'upgrade ') +token, caps = l[:-1].split(b' ')[1:] +assert caps == b'proto=%s' % name + +# Filter hello and between requests. +l = self._fin.readline() +assert l == b'hello\n' +l = self._fin.readline() +assert l == b'between\n' +l = self._fin.readline() +assert l == 'pairs 81\n' +self._fin.read(81) + +# Send the upgrade response. +
D2020: hgweb: move call to protocol handler outside of try..except
This revision was automatically updated to reflect the committed changes. Closed by commit rHGe69e65b2b4a9: hgweb: move call to protocol handler outside of try..except (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2020?vs=5174=5310 REVISION DETAIL https://phab.mercurial-scm.org/D2020 AFFECTED FILES mercurial/hgweb/hgweb_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -368,7 +368,6 @@ raise ErrorResponse(HTTP_NOT_FOUND) if cmd in perms: self.check_perm(rctx, req, perms[cmd]) -return protohandler['dispatch']() except ErrorResponse as inst: # A client that sends unbundle without 100-continue will # break if we respond early. @@ -383,6 +382,8 @@ body='0\n%s\n' % inst) return '' +return protohandler['dispatch']() + # translate user-visible url structure to internal structure args = query.split('/', 2) To: indygreg, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1998: wireproto: define and use types for wire protocol commands
This revision was automatically updated to reflect the committed changes. Closed by commit rHGef683a0fd21f: wireproto: define and use types for wire protocol commands (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D1998?vs=5137=5305 REVISION DETAIL https://phab.mercurial-scm.org/D1998 AFFECTED FILES mercurial/wireproto.py CHANGE DETAILS diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -634,8 +634,64 @@ return compengines -# list of commands -commands = {} +class commandentry(object): +"""Represents a declared wire protocol command.""" +def __init__(self, func, args=''): +self.func = func +self.args = args + +def _merge(self, func, args): +"""Merge this instance with an incoming 2-tuple. + +This is called when a caller using the old 2-tuple API attempts +to replace an instance. The incoming values are merged with +data not captured by the 2-tuple and a new instance containing +the union of the two objects is returned. +""" +return commandentry(func, args) + +# Old code treats instances as 2-tuples. So expose that interface. +def __iter__(self): +yield self.func +yield self.args + +def __getitem__(self, i): +if i == 0: +return self.func +elif i == 1: +return self.args +else: +raise IndexError('can only access elements 0 and 1') + +class commanddict(dict): +"""Container for registered wire protocol commands. + +It behaves like a dict. But __setitem__ is overwritten to allow silent +coercion of values from 2-tuples for API compatibility. +""" +def __setitem__(self, k, v): +if isinstance(v, commandentry): +pass +# Cast 2-tuples to commandentry instances. +elif isinstance(v, tuple): +if len(v) != 2: +raise ValueError('command tuples must have exactly 2 elements') + +# It is common for extensions to wrap wire protocol commands via +# e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers +# doing this aren't aware of the new API that uses objects to store +# command entries, we automatically merge old state with new. +if k in self: +v = self[k]._merge(v[0], v[1]) +else: +v = commandentry(v[0], v[1]) +else: +raise ValueError('command entries must be commandentry instances ' + 'or 2-tuples') + +return super(commanddict, self).__setitem__(k, v) + +commands = commanddict() def wireprotocommand(name, args=''): """Decorator to declare a wire protocol command. @@ -646,7 +702,7 @@ accepts. ``*`` is a special value that says to accept all arguments. """ def register(func): -commands[name] = (func, args) +commands[name] = commandentry(func, args) return func return register To: indygreg, #hg-reviewers, durin42 Cc: durin42, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1997: wireproto: improve docstring for @wireprotocommand
This revision was automatically updated to reflect the committed changes. Closed by commit rHGb4976912a6ef: wireproto: improve docstring for @wireprotocommand (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D1997?vs=5136=5306 REVISION DETAIL https://phab.mercurial-scm.org/D1997 AFFECTED FILES mercurial/wireproto.py CHANGE DETAILS diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -638,7 +638,13 @@ commands = {} def wireprotocommand(name, args=''): -"""decorator for wire protocol command""" +"""Decorator to declare a wire protocol command. + +``name`` is the name of the wire protocol command being provided. + +``args`` is a space-delimited list of named arguments that the command +accepts. ``*`` is a special value that says to accept all arguments. +""" def register(func): commands[name] = (func, args) return func To: indygreg, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2062: sshpeer: rename sshpeer class to sshv1peer (API)
This revision was automatically updated to reflect the committed changes. Closed by commit rHG625038cb4b1d: sshpeer: rename sshpeer class to sshv1peer (API) (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2062?vs=5256=5302 REVISION DETAIL https://phab.mercurial-scm.org/D2062 AFFECTED FILES hgext/largefiles/uisetup.py mercurial/sshpeer.py tests/test-check-interfaces.py CHANGE DETAILS diff --git a/tests/test-check-interfaces.py b/tests/test-check-interfaces.py --- a/tests/test-check-interfaces.py +++ b/tests/test-check-interfaces.py @@ -65,8 +65,8 @@ checkobject(badpeer()) checkobject(httppeer.httppeer(ui, 'http://localhost')) checkobject(localrepo.localpeer(dummyrepo())) -checkobject(sshpeer.sshpeer(ui, 'ssh://localhost/foo', None, None, None, - None, None)) +checkobject(sshpeer.sshv1peer(ui, 'ssh://localhost/foo', None, None, None, + None, None)) checkobject(bundlerepo.bundlepeer(dummyrepo())) checkobject(statichttprepo.statichttppeer(dummyrepo())) checkobject(unionrepo.unionpeer(dummyrepo())) diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py --- a/mercurial/sshpeer.py +++ b/mercurial/sshpeer.py @@ -328,7 +328,7 @@ return caps -class sshpeer(wireproto.wirepeer): +class sshv1peer(wireproto.wirepeer): def __init__(self, ui, url, proc, stdin, stdout, stderr, caps): """Create a peer from an existing SSH connection. @@ -537,4 +537,4 @@ _cleanuppipes(ui, stdout, stdin, stderr) raise -return sshpeer(ui, path, proc, stdin, stdout, stderr, caps) +return sshv1peer(ui, path, proc, stdin, stdout, stderr, caps) diff --git a/hgext/largefiles/uisetup.py b/hgext/largefiles/uisetup.py --- a/hgext/largefiles/uisetup.py +++ b/hgext/largefiles/uisetup.py @@ -185,9 +185,9 @@ # can't do this in reposetup because it needs to have happened before # wirerepo.__init__ is called -proto.ssholdcallstream = sshpeer.sshpeer._callstream +proto.ssholdcallstream = sshpeer.sshv1peer._callstream proto.httpoldcallstream = httppeer.httppeer._callstream -sshpeer.sshpeer._callstream = proto.sshrepocallstream +sshpeer.sshv1peer._callstream = proto.sshrepocallstream httppeer.httppeer._callstream = proto.httprepocallstream # override some extensions' stuff as well To: indygreg, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2060: internals: refactor wire protocol documentation
This revision was automatically updated to reflect the committed changes. Closed by commit rHG40d94ea51402: internals: refactor wire protocol documentation (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2060?vs=5254=5300 REVISION DETAIL https://phab.mercurial-scm.org/D2060 AFFECTED FILES mercurial/help/internals/wireprotocol.txt CHANGE DETAILS diff --git a/mercurial/help/internals/wireprotocol.txt b/mercurial/help/internals/wireprotocol.txt --- a/mercurial/help/internals/wireprotocol.txt +++ b/mercurial/help/internals/wireprotocol.txt @@ -10,11 +10,43 @@ The protocol is synchronous and does not support multiplexing (concurrent commands). -Transport Protocols -=== +Handshake += + +It is required or common for clients to perform a *handshake* when connecting +to a server. The handshake serves the following purposes: + +* Negotiating protocol/transport level options +* Allows the client to learn about server capabilities to influence + future requests +* Ensures the underlying transport channel is in a *clean* state -HTTP Transport --- +An important goal of the handshake is to allow clients to use more modern +wire protocol features. By default, clients must assume they are talking +to an old version of Mercurial server (possibly even the very first +implementation). So, clients should not attempt to call or utilize modern +wire protocol features until they have confirmation that the server +supports them. The handshake implementation is designed to allow both +ends to utilize the latest set of features and capabilities with as +few round trips as possible. + +The handshake mechanism varies by transport and protocol and is documented +in the sections below. + +HTTP Protocol += + +Handshake +- + +The client sends a ``capabilities`` command request (``?cmd=capabilities``) +as soon as HTTP requests may be issued. + +The server responds with a capabilities string, which the client parses to +learn about the server's abilities. + +HTTP Version 1 Transport + Commands are issued as HTTP/1.0 or HTTP/1.1 requests. Commands are sent to the base URL of the repository with the command name sent in @@ -112,11 +144,86 @@ ``application/mercurial-0.*`` media type and the HTTP response is typically using *chunked transfer* (``Transfer-Encoding: chunked``). -SSH Transport -= +SSH Protocol + + +Handshake +- + +For all clients, the handshake consists of the client sending 1 or more +commands to the server using version 1 of the transport. Servers respond +to commands they know how to respond to and send an empty response (``0\n``) +for unknown commands (per standard behavior of version 1 of the transport). +Clients then typically look for a response to the newest sent command to +determine which transport version to use and what the available features for +the connection and server are. + +Preceding any response from client-issued commands, the server may print +non-protocol output. It is common for SSH servers to print banners, message +of the day announcements, etc when clients connect. It is assumed that any +such *banner* output will precede any Mercurial server output. So clients +must be prepared to handle server output on initial connect that isn't +in response to any client-issued command and doesn't conform to Mercurial's +wire protocol. This *banner* output should only be on stdout. However, +some servers may send output on stderr. + +Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument +having the value +``-``. + +The ``between`` command has been supported since the original Mercurial +SSH server. Requesting the empty range will return a ``\n`` string response, +which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline +followed by the value, which happens to be a newline). + +For pre 0.9.1 clients and all servers, the exchange looks like:: + + c: between\n + c: pairs 81\n + c: - + s: 1\n + s: \n -The SSH transport is a custom text-based protocol suitable for use over any -bi-directional stream transport. It is most commonly used with SSH. +0.9.1+ clients send a ``hello`` command (with no arguments) before the +``between`` command. The response to this command allows clients to +discover server capabilities and settings. + +An example exchange between 0.9.1+ clients and a ``hello`` aware server looks +like:: + + c: hello\n + c: between\n + c: pairs 81\n + c: - + s: 324\n + s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n + s: 1\n + s: \n + +And a similar scenario
D2063: sshpeer: implement peer for version 2 of wire protocol
This revision was automatically updated to reflect the committed changes. Closed by commit rHG59e4a7781a36: sshpeer: implement peer for version 2 of wire protocol (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2063?vs=5257=5303 REVISION DETAIL https://phab.mercurial-scm.org/D2063 AFFECTED FILES mercurial/sshpeer.py tests/test-check-interfaces.py CHANGE DETAILS diff --git a/tests/test-check-interfaces.py b/tests/test-check-interfaces.py --- a/tests/test-check-interfaces.py +++ b/tests/test-check-interfaces.py @@ -67,6 +67,8 @@ checkobject(localrepo.localpeer(dummyrepo())) checkobject(sshpeer.sshv1peer(ui, 'ssh://localhost/foo', None, None, None, None, None)) +checkobject(sshpeer.sshv2peer(ui, 'ssh://localhost/foo', None, None, None, + None, None)) checkobject(bundlerepo.bundlepeer(dummyrepo())) checkobject(statichttprepo.statichttppeer(dummyrepo())) checkobject(unionrepo.unionpeer(dummyrepo())) diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py --- a/mercurial/sshpeer.py +++ b/mercurial/sshpeer.py @@ -326,7 +326,7 @@ if not caps: badresponse() -return caps +return protoname, caps class sshv1peer(wireproto.wirepeer): def __init__(self, ui, url, proc, stdin, stdout, stderr, caps): @@ -497,6 +497,12 @@ self._pipeo.flush() self._readerr() +class sshv2peer(sshv1peer): +"""A peer that speakers version 2 of the transport protocol.""" +# Currently version 2 is identical to version 1 post handshake. +# And handshake is performed before the peer is instantiated. So +# we need no custom code. + def instance(ui, path, create): """Create an SSH peer. @@ -532,9 +538,16 @@ remotepath, sshenv) try: -caps = _performhandshake(ui, stdin, stdout, stderr) +protoname, caps = _performhandshake(ui, stdin, stdout, stderr) except Exception: _cleanuppipes(ui, stdout, stdin, stderr) raise -return sshv1peer(ui, path, proc, stdin, stdout, stderr, caps) +if protoname == wireprotoserver.SSHV1: +return sshv1peer(ui, path, proc, stdin, stdout, stderr, caps) +elif protoname == wireprotoserver.SSHV2: +return sshv2peer(ui, path, proc, stdin, stdout, stderr, caps) +else: +_cleanuppipes(ui, stdout, stdin, stderr) +raise error.RepoError(_('unknown version of SSH protocol: %s') % + protoname) To: indygreg, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2069: revlog: resolve lfs rawtext to vanilla rawtext before applying delta
indygreg requested changes to this revision. indygreg added a comment. This revision now requires changes to proceed. I'd like to see the next versions of hte previous 2 patches before looking at this because this patch will likely get some minor rework as well. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2069 To: quark, indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2068: revlog: do not use delta for lfs revisions
indygreg requested changes to this revision. indygreg added inline comments. This revision now requires changes to proceed. INLINE COMMENTS > revlog.py:408 > +# do not use flags != 0 (ex. LFS) revision as delta base > +if revlog.flags(candidaterev) != REVIDX_DEFAULT_FLAGS: > +continue Same comment as previous review: this leaves a foot gun if we ever introduce revision flags that aren't related to content presence. We need something to help prevent this footgun. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2068 To: quark, indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2067: changegroup: do not delta lfs revisions
indygreg requested changes to this revision. indygreg added a comment. This revision now requires changes to proceed. This looks mostly good. I would like a change to address a future footgun though. I would also appreciate someone familiar with censor and narrow to weigh in on the implications of disabling delta generation for revisions that have the censor and ellipsis flags set. I'm pretty sure narrow will cope since it reimplements changegroup generation. Not sure how censor will react. (But I know we already have random code for detecting censored nodes during changegroup generation.) INLINE COMMENTS > revlog.py:719 > +# disable delta if either rev uses non-default flag (ex. LFS) > +if self.flags(baserev) or self.flags(rev): > +return False This logic assumes that revision flags will only ever be used to influence the presence of content. That is true today because our flags are for `REVIDX_ISCENSORED`, `REVIDX_ELLIPSIS`, and `REVIDX_EXTSTORED`. But if we ever introduced a new revision flag for e.g. compression strategy, then testing for non-empty revision flags would be wrong. I think we want to capture the bitmask of revision flags influencing delta generation explicitly. Or we want a big comment by the revision flags constants at the top of the file telling people to audit `candelta()` when adding new revision flags. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2067 To: quark, indygreg, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1998: wireproto: define and use types for wire protocol commands
durin42 accepted this revision. durin42 added a comment. This revision is now accepted and ready to land. I'm sold. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D1998 To: indygreg, #hg-reviewers, durin42 Cc: durin42, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1998: wireproto: define and use types for wire protocol commands
indygreg added a comment. In https://phab.mercurial-scm.org/D1998#34740, @durin42 wrote: > I'm curious what registrations you need that don't fit in 2-tuples. Can I see a sample of where that's going? I'll be adding additional attributes to `@wireprotocommand` in future series. One thing I know I'll add is a mechanism to limit which wire protocol versions/transports to expose a command to. This will prevent legacy commands from being exposed to modern protocols and vice versa. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D1998 To: indygreg, #hg-reviewers Cc: durin42, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2065: wireprotoserver: rename abstractserverproto and improve docstring
indygreg updated this revision to Diff 5299. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2065?vs=5262=5299 REVISION DETAIL https://phab.mercurial-scm.org/D2065 AFFECTED FILES mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -38,10 +38,13 @@ # to reflect BC breakages. SSHV2 = 'exp-ssh-v2-0001' -class abstractserverproto(object): -"""abstract class that summarizes the protocol API +class baseprotocolhandler(object): +"""Abstract base class for wire protocol handlers. -Used as reference and documentation. +A wire protocol handler serves as an interface between protocol command +handlers and the wire protocol transport layer. Protocol handlers provide +methods to read command arguments, redirect stdio for the duration of +the request, handle response types, etc. """ __metaclass__ = abc.ABCMeta @@ -104,7 +107,7 @@ return ''.join(chunks) -class webproto(abstractserverproto): +class webproto(baseprotocolhandler): def __init__(self, req, ui): self._req = req self._ui = ui @@ -333,7 +336,7 @@ return '' -class sshserver(abstractserverproto): +class sshserver(baseprotocolhandler): def __init__(self, ui, repo): self._ui = ui self._repo = repo To: indygreg, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2048: utils: copy util.py as utils/dateutil.py
durin42 requested changes to this revision. durin42 added a comment. This revision now requires changes to proceed. Can we do all of https://phab.mercurial-scm.org/D2048::https://phab.mercurial-scm.org/D2056 as a single change? What you've done means we have no useful blame information on everything done in https://phab.mercurial-scm.org/D2049::D0256... REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2048 To: lothiraldan, #hg-reviewers, durin42 Cc: durin42, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1998: wireproto: define and use types for wire protocol commands
durin42 added a comment. I'm curious what registrations you need that don't fit in 2-tuples. Can I see a sample of where that's going? REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D1998 To: indygreg, #hg-reviewers Cc: durin42, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2064: wireprotoserver: document and improve the httplib workaround
durin42 accepted this revision. durin42 added a comment. This revision is now accepted and ready to land. I'm so sorry for this technical debt, even if it's httplib's fault. :/ REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2064 To: indygreg, #hg-reviewers, durin42 Cc: durin42, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2061: sshpeer: initial definition and implementation of new SSH protocol
durin42 added inline comments. INLINE COMMENTS > joerg.sonnenberger wrote in test-ssh-proto.t:396 > I'm a bit concerned about the order here. I would prefer to stay with the > spirit of the original SSH protocol and go with the following order instead: > > - client sends hello to the server > - server sends its capabilities, including the supported "modern" protocol > versions > - client upgrades to the "best" version it supports and sends any additional > wire capabilities it has > > I don't think this changes the number of round-trips, but it slightly affects > the number of commands that are "unversioned". I don't feel super-strongly. I think my bias is to let the upgrade happen prior to the hello so that we can have custom servers in the future not have to support the bad old framing. I'll land the patch as is, but we can continue discussing it a bit here or on the list if I haven't convinced you. :) REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2061 To: indygreg, #hg-reviewers Cc: durin42, joerg.sonnenberger, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2079: color: honor NO_COLOR
quark added a comment. You might want to let run-tests.py drop NO_COLOR for tests. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2079 To: indygreg, #hg-reviewers, lothiraldan Cc: quark, lothiraldan, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2079: color: honor NO_COLOR
lothiraldan accepted this revision. lothiraldan added a comment. Hurrah for standards! REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2079 To: indygreg, #hg-reviewers, lothiraldan Cc: lothiraldan, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[Bug 5783] New: ValueError (too many values to unpack) in mercurial/obsolete.py
https://bz.mercurial-scm.org/show_bug.cgi?id=5783 Bug ID: 5783 Summary: ValueError (too many values to unpack) in mercurial/obsolete.py Product: Mercurial Version: stable branch Hardware: PC OS: Linux Status: UNCONFIRMED Severity: bug Priority: wish Component: evolution Assignee: bugzi...@mercurial-scm.org Reporter: david.dou...@logilab.fr CC: mercurial-devel@mercurial-scm.org, pierre-yves.da...@ens-lyon.org In a repository (in which we use obsolete and evolve and topics), we've been hit by the traceback : [...] File "/home/david/.virtualenvs/hg/local/lib/python2.7/site-packages/mercurial/obsolete.py", line 296, in _fm0decodemeta key, value = l.split(':') ValueError: too many values to unpack In my context, the variable 'l' contains 2 ':' chars. The content of the variabel 'data' in this function is: 'date:1518013344.878793 -3600\x00ef1:0\x00note:6c95ca::a1e17f\x00p1:ed7673f73387b36521da58a87f08e02e4a795ded\x00user:Denis Laxalde' I mage a simple hotfix replacing this line by: key, value = l.split(':', 1) which seems to work for now. Note: the problem occurs the same on hg 4.3 (deb package on a jessie machine) and hg 4.5 (from pip). David -- You are receiving this mail because: You are on the CC list for the bug. ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 6 of 6] log: pack filematcher and hunksfilter into changesetdiffer object
Yuya Nishihara a écrit : > # HG changeset patch > # User Yuya Nishihara> # Date 1516517658 -32400 > # Sun Jan 21 15:54:18 2018 +0900 > # Node ID 5f9dcb5d72da427abbfa2c304bdbe4dd555e0c7d > # Parent f95d0d1e012a512550de945350e08f3dc7db090f > log: pack filematcher and hunksfilter into changesetdiffer object > > This is just a way of getting rid of clumsy makefilematcher/makehunksfilter > arguments. There might be a better abstraction, but I don't think this is bad. Alternatively we could have a makediffer(makefilematcher=None, makehunksfilter=None) factory function returning the showdiff function with capture context; that would avoid setting _make in client code. Not sure this is better and I agree that your proposal is "not bad". Nice cleanup overall. > This makes filematcher and hunksfilter available by default, but that should > be fine. > > diff --git a/mercurial/commands.py b/mercurial/commands.py > --- a/mercurial/commands.py > +++ b/mercurial/commands.py > @@ -3419,17 +3419,15 @@ def log(ui, repo, *pats, **opts): > ) > > repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn') > -revs, filematcher = logcmdutil.getrevs(repo, pats, opts) > -hunksfilter = None > +revs, differ = logcmdutil.getrevs(repo, pats, opts) > > if opts.get('graph'): > if linerange: > raise error.Abort(_('graph not supported with line range > patterns')) > -return logcmdutil.graphlog(ui, repo, revs, filematcher, opts) > +return logcmdutil.graphlog(ui, repo, revs, differ, opts) > > if linerange: > -revs, filematcher, hunksfilter = logcmdutil.getlinerangerevs( > -repo, revs, opts) > +revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts) > > getrenamed = None > if opts.get('copies'): > @@ -3439,9 +3437,7 @@ def log(ui, repo, *pats, **opts): > getrenamed = templatekw.getrenamedfn(repo, endrev=endrev) > > ui.pager('log') > -displayer = logcmdutil.changesetdisplayer(ui, repo, opts, > - makefilematcher=filematcher, > - makehunksfilter=hunksfilter, > +displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ, >buffered=True) > for rev in revs: > ctx = repo[rev] > diff --git a/mercurial/logcmdutil.py b/mercurial/logcmdutil.py > --- a/mercurial/logcmdutil.py > +++ b/mercurial/logcmdutil.py > @@ -109,6 +109,23 @@ def diffordiffstat(ui, repo, diffopts, n > sub.diff(ui, diffopts, tempnode2, submatch, changes=changes, > stat=stat, fp=fp, prefix=prefix) > > +class changesetdiffer(object): > +"""Generate diff of changeset with pre-configured filtering functions""" > + > +def _makefilematcher(self, ctx): > +return scmutil.matchall(ctx.repo()) > + > +def _makehunksfilter(self, ctx): > +return None > + > +def showdiff(self, ui, ctx, diffopts, stat=False): > +repo = ctx.repo() > +node = ctx.node() > +prev = ctx.p1().node() > +diffordiffstat(ui, repo, diffopts, prev, node, > + match=self._makefilematcher(ctx), stat=stat, > + hunksfilterfn=self._makehunksfilter(ctx)) > + > def changesetlabels(ctx): > labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()] > if ctx.obsolete(): > @@ -122,13 +139,11 @@ def changesetlabels(ctx): > class changesetprinter(object): > '''show changeset information when templating not requested.''' > > -def __init__(self, ui, repo, makefilematcher=None, makehunksfilter=None, > - diffopts=None, buffered=False): > +def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False): > self.ui = ui > self.repo = repo > self.buffered = buffered > -self._makefilematcher = makefilematcher or (lambda ctx: None) > -self._makehunksfilter = makehunksfilter or (lambda ctx: None) > +self._differ = differ or changesetdiffer() > self.diffopts = diffopts or {} > self.header = {} > self.hunk = {} > @@ -267,35 +282,23 @@ class changesetprinter(object): > ''' > > def _showpatch(self, ctx): > -matchfn = self._makefilematcher(ctx) > -hunksfilterfn = self._makehunksfilter(ctx) > -if not matchfn: > -return > stat = self.diffopts.get('stat') > diff = self.diffopts.get('patch') > diffopts = patch.diffallopts(self.ui, self.diffopts) > -node = ctx.node() > -prev = ctx.p1().node() > if stat: > -diffordiffstat(self.ui, self.repo, diffopts, prev, node, > - match=matchfn, stat=True, > - hunksfilterfn=hunksfilterfn) > +self._differ.showdiff(self.ui, ctx, diffopts,
D2079: color: honor NO_COLOR
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY The http://no-color.org/ initiative is trying to get programs that emit color by default to honor a NO_COLOR environment variable to disable color. I think that's a good idea. So this commit implements support for NO_COLOR. I'm not sure if the precedence of settings is proper here. Right now, NO_COLOR overrides config settings set by hgrc or --config. But it doesn't override --color. I can see an argument for honoring --config as well. Same for hgrc (since color is enabled by default these days). But the existing logic/precedence is unclear to me. So I went with an easy implementation. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2079 AFFECTED FILES mercurial/color.py tests/test-status-color.t CHANGE DETAILS diff --git a/tests/test-status-color.t b/tests/test-status-color.t --- a/tests/test-status-color.t +++ b/tests/test-status-color.t @@ -46,6 +46,42 @@ [status.unknown|? ][status.unknown|b/in_b] (glob) [status.unknown|? ][status.unknown|in_root] +NO_COLOR disables color + + $ NO_COLOR=1 hg status + ? a/1/in_a_1 + ? a/in_a + ? b/1/in_b_1 + ? b/2/in_b_2 + ? b/in_b + ? in_root + + $ NO_COLOR=0 hg status + ? a/1/in_a_1 + ? a/in_a + ? b/1/in_b_1 + ? b/2/in_b_2 + ? b/in_b + ? in_root + + $ NO_COLOR= hg status + ? a/1/in_a_1 + ? a/in_a + ? b/1/in_b_1 + ? b/2/in_b_2 + ? b/in_b + ? in_root + +NO_COLOR is overridden by --color + + $ NO_COLOR=1 hg --color=always status + \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc) + \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc) + \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc) + \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc) + \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc) + \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc) + hg status with template $ hg status -T "{label('red', path)}\n" --color=debug [red|a/1/in_a_1] diff --git a/mercurial/color.py b/mercurial/color.py --- a/mercurial/color.py +++ b/mercurial/color.py @@ -198,6 +198,13 @@ if config == 'debug': return 'debug' +# The http://no-color.org/ initiative wants to standardize on an environment +# variable to disable color. The value of this variable doesn't matter. +if 'NO_COLOR' in encoding.environ: +# Allow --color CLI argument to override NO_COLOR +if ui.configsource('ui', 'color') != '--color': +return None + auto = (config == 'auto') always = False if not auto and util.parsebool(config): To: indygreg, #hg-reviewers Cc: mercurial-devel, spectral ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1957: streamclonebundle: make sure we accept new stream clone bundle spec
lothiraldan added a subscriber: indygreg. lothiraldan added a comment. @indygreg Could you take a look at the series before it gets too distant to merge cleanly? REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D1957 To: lothiraldan, #hg-reviewers Cc: indygreg, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2061: sshpeer: initial definition and implementation of new SSH protocol
joerg.sonnenberger added inline comments. INLINE COMMENTS > test-ssh-proto.t:396 > + > upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a > proto=irrelevant1%2Cirrelevant2 > + > hello > + > between I'm a bit concerned about the order here. I would prefer to stay with the spirit of the original SSH protocol and go with the following order instead: - client sends hello to the server - server sends its capabilities, including the supported "modern" protocol versions - client upgrades to the "best" version it supports and sends any additional wire capabilities it has I don't think this changes the number of round-trips, but it slightly affects the number of commands that are "unversioned". REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2061 To: indygreg, #hg-reviewers Cc: joerg.sonnenberger, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 07 of 13] revlog: move index file opening in a method
On Tue, 06 Feb 2018 14:21:28 +0100, Boris Feld wrote: > # HG changeset patch > # User Boris Feld> # Date 1517847733 -3600 > # Mon Feb 05 17:22:13 2018 +0100 > # Node ID d238ec45ba25f7c1ea9ec22aa11a4ec699c72740 > # Parent b192ee27b376523292ec1cf52c49c9382a870658 > # EXP-Topic revlog-fp > # Available At https://bitbucket.org/octobus/mercurial-devel/ > # hg pull https://bitbucket.org/octobus/mercurial-devel/ -r > d238ec45ba25 > revlog: move index file opening in a method Queud the series, thanks. > @@ -682,6 +682,15 @@ class revlog(object): > def _compressor(self): > return util.compengines[self._compengine].revlogcompressor() > > +def _indexfp(self, mode='r'): > +"""file object for the revlog's index file""" > +args = {'mode': mode} > +if mode != 'r': > +args['checkambig'] = self._checkambig > +if mode == 'w': > +args['atomictemp'] = True > +return self.opener(self.indexfile, **args) Added r'' in flight to get around py3 transformer. > -fp = self.opener(self.indexfile, 'w', atomictemp=True, > - checkambig=self._checkambig) > +fp = self._indexfp('w') I don't think it's good idea to make 'w' imply atomictemp=True because here the atomic-temp semantics is really important, but the series looks good as a whole. ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2022: ui: improve ui.write performance when not coloring on Windows
This revision was automatically updated to reflect the committed changes. Closed by commit rHG0ff41ced4c12: diff: improve ui.write performance when not coloring on Windows (authored by joerg.sonnenberger, committed by ). CHANGED PRIOR TO COMMIT https://phab.mercurial-scm.org/D2022?vs=5293=5297#toc REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2022?vs=5293=5297 REVISION DETAIL https://phab.mercurial-scm.org/D2022 AFFECTED FILES mercurial/logcmdutil.py mercurial/ui.py CHANGE DETAILS diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -870,6 +870,17 @@ return "".join(self._buffers.pop()) +def canwritewithoutlabels(self): +'''check if write skips the label''' +if self._buffers and not self._bufferapplylabels: +return True +return self._colormode is None + +def canbatchlabeledwrites(self): +'''check if write calls with labels are batchable''' +# Windows color printing is special, see ``write``. +return self._colormode != 'win32' + def write(self, *args, **opts): '''write args to output diff --git a/mercurial/logcmdutil.py b/mercurial/logcmdutil.py --- a/mercurial/logcmdutil.py +++ b/mercurial/logcmdutil.py @@ -79,18 +79,31 @@ width = 80 if not ui.plain(): width = ui.termwidth() -chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts, -prefix=prefix, relroot=relroot, -hunksfilterfn=hunksfilterfn) -for chunk, label in patch.diffstatui(util.iterlines(chunks), - width=width): -write(chunk, label=label) + +chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts, +prefix=prefix, relroot=relroot, +hunksfilterfn=hunksfilterfn) + +if fp is not None or ui.canwritewithoutlabels(): +if stat: +chunks = patch.diffstat(util.iterlines(chunks), width=width) +for chunk in util.filechunkiter(util.chunkbuffer(chunks)): +write(chunk) else: -for chunk, label in patch.diffui(repo, node1, node2, match, - changes, opts=diffopts, prefix=prefix, - relroot=relroot, - hunksfilterfn=hunksfilterfn): -write(chunk, label=label) +if stat: +chunks = patch.diffstatui(util.iterlines(chunks), width=width) +else: +chunks = patch.difflabel(lambda chunks, **kwargs: chunks, chunks, + opts=diffopts) +if ui.canbatchlabeledwrites(): +def gen(): +for chunk, label in chunks: +yield ui.label(chunk, label=label) +for chunk in util.filechunkiter(util.chunkbuffer(gen())): +write(chunk) +else: +for chunk, label in chunks: +write(chunk, label=label) if listsubrepos: ctx1 = repo[node1] To: joerg.sonnenberger, #hg-reviewers, yuja Cc: yuja, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2058: chg: enable clang-format on all .c and .h files
This revision was automatically updated to reflect the committed changes. Closed by commit rHG9724f54923ec: chg: enable clang-format on all .c and .h files (authored by durin42, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2058?vs=5239=5295 REVISION DETAIL https://phab.mercurial-scm.org/D2058 AFFECTED FILES contrib/chg/chg.c contrib/chg/hgclient.c contrib/chg/hgclient.h contrib/chg/procutil.c contrib/chg/util.c contrib/chg/util.h contrib/clang-format-blacklist CHANGE DETAILS diff --git a/contrib/clang-format-blacklist b/contrib/clang-format-blacklist --- a/contrib/clang-format-blacklist +++ b/contrib/clang-format-blacklist @@ -1,12 +1,5 @@ # Files that just need to be migrated to the formatter. # Do not add new files here! -contrib/chg/chg.c -contrib/chg/hgclient.c -contrib/chg/hgclient.h -contrib/chg/procutil.c -contrib/chg/procutil.h -contrib/chg/util.c -contrib/chg/util.h contrib/hgsh/hgsh.c mercurial/cext/base85.c mercurial/cext/bdiff.c diff --git a/contrib/chg/util.h b/contrib/chg/util.h --- a/contrib/chg/util.h +++ b/contrib/chg/util.h @@ -32,4 +32,4 @@ int runshellcmd(const char *cmd, const char *envp[], const char *cwd); -#endif /* UTIL_H_ */ +#endif /* UTIL_H_ */ diff --git a/contrib/chg/util.c b/contrib/chg/util.c --- a/contrib/chg/util.c +++ b/contrib/chg/util.c @@ -62,7 +62,8 @@ static int debugmsgenabled = 0; static double debugstart = 0; -static double now() { +static double now() +{ struct timeval t; gettimeofday(, NULL); return t.tv_usec / 1e6 + t.tv_sec; diff --git a/contrib/chg/procutil.c b/contrib/chg/procutil.c --- a/contrib/chg/procutil.c +++ b/contrib/chg/procutil.c @@ -54,7 +54,7 @@ goto error; forwardsignal(sig); - if (raise(sig) < 0) /* resend to self */ + if (raise(sig) < 0) /* resend to self */ goto error; if (sigaction(sig, , ) < 0) goto error; @@ -205,8 +205,8 @@ close(pipefds[0]); close(pipefds[1]); - int r = execle("/bin/sh", "/bin/sh", "-c", pagercmd, NULL, - envp); + int r = + execle("/bin/sh", "/bin/sh", "-c", pagercmd, NULL, envp); if (r < 0) { abortmsgerrno("cannot start pager '%s'", pagercmd); } diff --git a/contrib/chg/hgclient.h b/contrib/chg/hgclient.h --- a/contrib/chg/hgclient.h +++ b/contrib/chg/hgclient.h @@ -22,9 +22,9 @@ pid_t hgc_peerpid(const hgclient_t *hgc); const char **hgc_validate(hgclient_t *hgc, const char *const args[], - size_t argsize); + size_t argsize); int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize); void hgc_attachio(hgclient_t *hgc); void hgc_setenv(hgclient_t *hgc, const char *const envp[]); -#endif /* HGCLIENT_H_ */ +#endif /* HGCLIENT_H_ */ diff --git a/contrib/chg/hgclient.c b/contrib/chg/hgclient.c --- a/contrib/chg/hgclient.c +++ b/contrib/chg/hgclient.c @@ -7,7 +7,7 @@ * GNU General Public License version 2 or any later version. */ -#include /* for ntohl(), htonl() */ +#include /* for ntohl(), htonl() */ #include #include #include @@ -26,33 +26,32 @@ #include "procutil.h" #include "util.h" -enum { - CAP_GETENCODING = 0x0001, - CAP_RUNCOMMAND = 0x0002, - /* cHg extension: */ - CAP_ATTACHIO = 0x0100, - CAP_CHDIR = 0x0200, - CAP_SETENV = 0x0800, - CAP_SETUMASK = 0x1000, - CAP_VALIDATE = 0x2000, - CAP_SETPROCNAME = 0x4000, +enum { CAP_GETENCODING = 0x0001, + CAP_RUNCOMMAND = 0x0002, + /* cHg extension: */ + CAP_ATTACHIO = 0x0100, + CAP_CHDIR = 0x0200, + CAP_SETENV = 0x0800, + CAP_SETUMASK = 0x1000, + CAP_VALIDATE = 0x2000, + CAP_SETPROCNAME = 0x4000, }; typedef struct { const char *name; unsigned int flag; } cappair_t; static const cappair_t captable[] = { - {"getencoding", CAP_GETENCODING}, - {"runcommand", CAP_RUNCOMMAND}, - {"attachio", CAP_ATTACHIO}, - {"chdir", CAP_CHDIR}, - {"setenv", CAP_SETENV}, - {"setumask", CAP_SETUMASK}, - {"validate", CAP_VALIDATE}, - {"setprocname", CAP_SETPROCNAME}, - {NULL, 0}, /* terminator */ +{"getencoding", CAP_GETENCODING}, +{"runcommand", CAP_RUNCOMMAND}, +{"attachio", CAP_ATTACHIO}, +{"chdir", CAP_CHDIR}, +{"setenv", CAP_SETENV}, +{"setumask", CAP_SETUMASK}, +{"validate", CAP_VALIDATE}, +{"setprocname", CAP_SETPROCNAME}, +{NULL, 0}, /* terminator */ }; typedef struct { @@ -88,8 +87,8 @@ if (newsize <= ctx->maxdatasize) return; - newsize = defaultdatasize - * ((newsize + defaultdatasize - 1) / defaultdatasize); + newsize = defaultdatasize * + ((newsize +
D2059: hgsh: enable clang-format
This revision was automatically updated to reflect the committed changes. Closed by commit rHG580f7b1b88c7: hgsh: enable clang-format (authored by durin42, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2059?vs=5240=5296 REVISION DETAIL https://phab.mercurial-scm.org/D2059 AFFECTED FILES contrib/clang-format-blacklist contrib/hgsh/hgsh.c CHANGE DETAILS diff --git a/contrib/hgsh/hgsh.c b/contrib/hgsh/hgsh.c --- a/contrib/hgsh/hgsh.c +++ b/contrib/hgsh/hgsh.c @@ -48,65 +48,65 @@ * have such machine, set to NULL. */ #ifndef HG_GATEWAY -#define HG_GATEWAY "gateway" +#define HG_GATEWAY "gateway" #endif /* * HG_HOST: hostname of mercurial server. if any machine is allowed, set to * NULL. */ #ifndef HG_HOST -#define HG_HOST "mercurial" +#define HG_HOST "mercurial" #endif /* * HG_USER: username to log in from HG_GATEWAY to HG_HOST. if gateway and * host username are same, set to NULL. */ #ifndef HG_USER -#define HG_USER "hg" +#define HG_USER "hg" #endif /* * HG_ROOT: root of tree full of mercurial repos. if you do not want to * validate location of repo when someone is try to access, set to NULL. */ #ifndef HG_ROOT -#define HG_ROOT "/home/hg/repos" +#define HG_ROOT "/home/hg/repos" #endif /* * HG: path to the mercurial executable to run. */ #ifndef HG -#define HG "/home/hg/bin/hg" +#define HG "/home/hg/bin/hg" #endif /* * HG_SHELL: shell to use for actions like "sudo" and "su" access to * mercurial user, and cron jobs. if you want to make these things * impossible, set to NULL. */ #ifndef HG_SHELL -#define HG_SHELLNULL +#define HG_SHELL NULL /* #define HG_SHELL"/bin/bash" */ #endif /* * HG_HELP: some way for users to get support if they have problem. if they * should not get helpful message, set to NULL. */ #ifndef HG_HELP -#define HG_HELP "please contact supp...@example.com for help." +#define HG_HELP "please contact supp...@example.com for help." #endif /* * SSH: path to ssh executable to run, if forwarding from HG_GATEWAY to * HG_HOST. if you want to use rsh instead (why?), you need to modify * arguments it is called with. see forward_through_gateway. */ #ifndef SSH -#define SSH "/usr/bin/ssh" +#define SSH "/usr/bin/ssh" #endif /* @@ -249,7 +249,6 @@ hg_serve, }; - /* * attempt to verify that a directory is really a hg repo, by testing * for the existence of a subdirectory. @@ -310,8 +309,7 @@ if (sscanf(argv[2], "hg init %as", ) == 1) { cmd = hg_init; - } - else if (sscanf(argv[2], "hg -R %as serve --stdio", ) == 1) { + } else if (sscanf(argv[2], "hg -R %as serve --stdio", ) == 1) { cmd = hg_serve; } else { goto badargs; diff --git a/contrib/clang-format-blacklist b/contrib/clang-format-blacklist --- a/contrib/clang-format-blacklist +++ b/contrib/clang-format-blacklist @@ -1,6 +1,5 @@ # Files that just need to be migrated to the formatter. # Do not add new files here! -contrib/hgsh/hgsh.c mercurial/cext/base85.c mercurial/cext/bdiff.c mercurial/cext/charencode.c To: durin42, #hg-reviewers, yuja Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2078: python3: whitelist an additional 11 tests
This revision was automatically updated to reflect the committed changes. Closed by commit rHGfd9f2a22ee83: python3: whitelist an additional 11 tests (authored by durin42, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2078?vs=5292=5294 REVISION DETAIL https://phab.mercurial-scm.org/D2078 AFFECTED FILES contrib/python3-whitelist CHANGE DETAILS diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist --- a/contrib/python3-whitelist +++ b/contrib/python3-whitelist @@ -3,6 +3,7 @@ test-addremove.t test-amend-subrepo.t test-ancestor.py +test-annotate.py test-automv.t test-backwards-remove.t test-bheads.t @@ -29,8 +30,10 @@ test-confused-revert.t test-contrib-check-code.t test-contrib-check-commit.t +test-copy-move-merge.t test-debugindexdot.t test-debugrename.t +test-diff-binary-file.t test-diff-change.t test-diff-copy-depth.t test-diff-hashes.t @@ -74,6 +77,7 @@ test-exchange-obsmarkers-case-D3.t test-exchange-obsmarkers-case-D4.t test-execute-bit.t +test-extra-filelog-entry.t test-filebranch.t test-flags.t test-generaldelta.t @@ -119,9 +123,11 @@ test-merge7.t test-merge8.t test-mq-qimport-fail-cleanup.t +test-mq-qsave.t test-obshistory.t test-obsolete-changeset-exchange.t test-obsolete-checkheads.t +test-obsolete-distributed.t test-parents.t test-permissions.t test-pull-branch.t @@ -154,6 +160,9 @@ test-push-checkheads-unpushed-D6.t test-push-checkheads-unpushed-D7.t test-push-warn.t +test-rebase-inmemory.t +test-rebase-issue-noparam-single-rev.t +test-rebase-transaction.t test-record.t test-rename-after-merge.t test-rename-dir-merge.t @@ -164,6 +173,7 @@ test-revlog-group-emptyiter.t test-revlog-mmapindex.t test-revlog-packentry.t +test-revset-dirstate-parents.t test-revset-outgoing.t test-run-tests.py test-show-stack.t @@ -176,6 +186,7 @@ test-status-terse.t test-strip-cross.t test-strip.t +test-unamend.t test-uncommit.t test-unified-test.t test-unrelated-pull.t To: durin42, pulkit, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[PATCH 2 of 6] logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)
# HG changeset patch # User Yuya Nishihara# Date 1516511272 -32400 # Sun Jan 21 14:07:52 2018 +0900 # Node ID fa4427fe64617b5822c383f8dfdf6704d16fe01f # Parent ca578149cc821eb207e2a844c6cf74e6b66f860c logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API) This merges self.matchfn and self.show(matchfn) into self._makefilematcher, and does the same for hunksfilter. Because changesetprinter seems to have too many optional arguments, makefilematcher() and makehunksfilter() will be packed into one object by later patch. diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -3450,7 +3450,10 @@ def log(ui, repo, *pats, **opts): getrenamed = templatekw.getrenamedfn(repo, endrev=endrev) ui.pager('log') -displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True) +displayer = logcmdutil.changesetdisplayer(ui, repo, opts, + makefilematcher=filematcher, + makehunksfilter=hunksfilter, + buffered=True) for rev in revs: ctx = repo[rev] copies = None @@ -3460,16 +3463,7 @@ def log(ui, repo, *pats, **opts): rename = getrenamed(fn, rev) if rename: copies.append((fn, rename[0])) -if filematcher: -revmatchfn = filematcher(ctx) -else: -revmatchfn = None -if hunksfilter: -revhunksfilter = hunksfilter(ctx) -else: -revhunksfilter = None -displayer.show(ctx, copies=copies, matchfn=revmatchfn, - hunksfilterfn=revhunksfilter) +displayer.show(ctx, copies=copies) displayer.flush(ctx) displayer.close() diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -886,7 +886,8 @@ def _incoming(displaychlist, subreporecu ui.status(_("no changes found\n")) return subreporecurse() ui.pager('incoming') -displayer = logcmdutil.changesetdisplayer(ui, other, opts, buffered) +displayer = logcmdutil.changesetdisplayer(ui, other, opts, + buffered=buffered) displaychlist(other, chlist, displayer) displayer.close() finally: diff --git a/mercurial/logcmdutil.py b/mercurial/logcmdutil.py --- a/mercurial/logcmdutil.py +++ b/mercurial/logcmdutil.py @@ -122,11 +122,13 @@ def changesetlabels(ctx): class changesetprinter(object): '''show changeset information when templating not requested.''' -def __init__(self, ui, repo, matchfn=None, diffopts=None, buffered=False): +def __init__(self, ui, repo, makefilematcher=None, makehunksfilter=None, + diffopts=None, buffered=False): self.ui = ui self.repo = repo self.buffered = buffered -self.matchfn = matchfn +self._makefilematcher = makefilematcher or (lambda ctx: None) +self._makehunksfilter = makehunksfilter or (lambda ctx: None) self.diffopts = diffopts or {} self.header = {} self.hunk = {} @@ -150,17 +152,16 @@ class changesetprinter(object): if self.footer: self.ui.write(self.footer) -def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None, - **props): +def show(self, ctx, copies=None, **props): props = pycompat.byteskwargs(props) if self.buffered: self.ui.pushbuffer(labeled=True) -self._show(ctx, copies, matchfn, hunksfilterfn, props) +self._show(ctx, copies, props) self.hunk[ctx.rev()] = self.ui.popbuffer() else: -self._show(ctx, copies, matchfn, hunksfilterfn, props) +self._show(ctx, copies, props) -def _show(self, ctx, copies, matchfn, hunksfilterfn, props): +def _show(self, ctx, copies, props): '''show a single changeset or file revision''' changenode = ctx.node() rev = ctx.rev() @@ -251,7 +252,7 @@ class changesetprinter(object): label='log.summary') self.ui.write("\n") -self._showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn) +self._showpatch(ctx) def _showobsfate(self, ctx): obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui) @@ -265,9 +266,9 @@ class changesetprinter(object): '''empty method used by extension as a hook point ''' -def _showpatch(self, ctx, matchfn, hunksfilterfn=None): -if not matchfn: -matchfn = self.matchfn +def _showpatch(self, ctx): +matchfn = self._makefilematcher(ctx) +hunksfilterfn = self._makehunksfilter(ctx) if matchfn: stat = self.diffopts.get('stat')
[PATCH 1 of 6] log: pass ctx to makefilematcher() and makehunksfilter() functions
# HG changeset patch # User Yuya Nishihara# Date 1516509631 -32400 # Sun Jan 21 13:40:31 2018 +0900 # Node ID ca578149cc821eb207e2a844c6cf74e6b66f860c # Parent b62c4154bb287fe0f4c15cdb0d2ef290069288df log: pass ctx to makefilematcher() and makehunksfilter() functions This isn't important, but seems more consisntent as changesetprinter.show() takes a ctx, not a revision number. diff --git a/hgext/largefiles/overrides.py b/hgext/largefiles/overrides.py --- a/hgext/largefiles/overrides.py +++ b/hgext/largefiles/overrides.py @@ -392,7 +392,7 @@ def overridelog(orig, ui, repo, *pats, * def overridemakefilematcher(repo, pats, opts, badfn=None): wctx = repo[None] match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn) -return lambda rev: match +return lambda ctx: match oldmatchandpats = installmatchandpatsfn(overridematchandpats) oldmakefilematcher = logcmdutil._makenofollowfilematcher diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -3461,11 +3461,11 @@ def log(ui, repo, *pats, **opts): if rename: copies.append((fn, rename[0])) if filematcher: -revmatchfn = filematcher(ctx.rev()) +revmatchfn = filematcher(ctx) else: revmatchfn = None if hunksfilter: -revhunksfilter = hunksfilter(rev) +revhunksfilter = hunksfilter(ctx) else: revhunksfilter = None displayer.show(ctx, copies=copies, matchfn=revmatchfn, diff --git a/mercurial/logcmdutil.py b/mercurial/logcmdutil.py --- a/mercurial/logcmdutil.py +++ b/mercurial/logcmdutil.py @@ -614,8 +614,8 @@ def _fileancestors(repo, revs, match, fo # revision, stored in "fcache". "fcache" is populated as a side effect # of the graph traversal. fcache = {} -def filematcher(rev): -return scmutil.matchfiles(repo, fcache.get(rev, [])) +def filematcher(ctx): +return scmutil.matchfiles(repo, fcache.get(ctx.rev(), [])) def revgen(): for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst): @@ -709,7 +709,7 @@ def _initialrevs(repo, opts): def getrevs(repo, pats, opts): """Return (revs, filematcher) where revs is a smartset -filematcher is a callable taking a revision number and returning a match +filematcher is a callable taking a changectx and returning a match objects filtering the files to be detailed when displaying the revision. """ follow = opts.get('follow') or opts.get('follow_first') @@ -729,7 +729,7 @@ def getrevs(repo, pats, opts): if filematcher is None: filematcher = _makenofollowfilematcher(repo, pats, opts) if filematcher is None: -def filematcher(rev): +def filematcher(ctx): return match expr = _makerevset(repo, match, pats, slowpath, opts) @@ -771,11 +771,11 @@ def getlinerangerevs(repo, userrevs, opt "revs" are revisions obtained by processing "line-range" log options and walking block ancestors of each specified file/line-range. -"filematcher(rev) -> match" is a factory function returning a match object +"filematcher(ctx) -> match" is a factory function returning a match object for a given revision for file patterns specified in --line-range option. If neither --stat nor --patch options are passed, "filematcher" is None. -"hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function +"hunksfilter(ctx) -> filterfn(fctx, hunks)" is a factory function returning a hunks filtering function. If neither --stat nor --patch options are passed, "filterhunks" is None. """ @@ -803,8 +803,8 @@ def getlinerangerevs(repo, userrevs, opt def nofilterhunksfn(fctx, hunks): return hunks -def hunksfilter(rev): -fctxlineranges = linerangesbyrev.get(rev) +def hunksfilter(ctx): +fctxlineranges = linerangesbyrev.get(ctx.rev()) if fctxlineranges is None: return nofilterhunksfn @@ -824,8 +824,8 @@ def getlinerangerevs(repo, userrevs, opt return filterfn -def filematcher(rev): -files = list(linerangesbyrev.get(rev, [])) +def filematcher(ctx): +files = list(linerangesbyrev.get(ctx.rev(), [])) return scmutil.matchfiles(repo, files) revs = sorted(linerangesbyrev, reverse=True) @@ -886,7 +886,7 @@ def displaygraph(ui, repo, dag, displaye copies.append((fn, rename[0])) revmatchfn = None if filematcher is not None: -revmatchfn = filematcher(ctx.rev()) +revmatchfn = filematcher(ctx) edges = edgefn(type, char, state, rev, parents) firstedge = next(edges) width = firstedge[2] ___
[PATCH 5 of 6] log: drop dead code to concatenate --line-range patterns and pats
# HG changeset patch # User Yuya Nishihara# Date 1516517424 -32400 # Sun Jan 21 15:50:24 2018 +0900 # Node ID f95d0d1e012a512550de945350e08f3dc7db090f # Parent 03f30f86c95a3847be7bb7b89a8cc22d9abee524 log: drop dead code to concatenate --line-range patterns and pats It's disabled since 2e45bbd3db7b, and the current implementation is unlikely to be reused. diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -3428,20 +3428,9 @@ def log(ui, repo, *pats, **opts): return logcmdutil.graphlog(ui, repo, revs, filematcher, opts) if linerange: -revs, lrfilematcher, hunksfilter = logcmdutil.getlinerangerevs( +revs, filematcher, hunksfilter = logcmdutil.getlinerangerevs( repo, revs, opts) -if filematcher is not None: -basefilematcher = filematcher - -def filematcher(rev): -files = (basefilematcher(rev).files() - + lrfilematcher(rev).files()) -return scmutil.matchfiles(repo, files) - -elif filematcher is None: -filematcher = lrfilematcher - getrenamed = None if opts.get('copies'): endrev = None ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[PATCH 6 of 6] log: pack filematcher and hunksfilter into changesetdiffer object
# HG changeset patch # User Yuya Nishihara# Date 1516517658 -32400 # Sun Jan 21 15:54:18 2018 +0900 # Node ID 5f9dcb5d72da427abbfa2c304bdbe4dd555e0c7d # Parent f95d0d1e012a512550de945350e08f3dc7db090f log: pack filematcher and hunksfilter into changesetdiffer object This is just a way of getting rid of clumsy makefilematcher/makehunksfilter arguments. There might be a better abstraction, but I don't think this is bad. This makes filematcher and hunksfilter available by default, but that should be fine. diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -3419,17 +3419,15 @@ def log(ui, repo, *pats, **opts): ) repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn') -revs, filematcher = logcmdutil.getrevs(repo, pats, opts) -hunksfilter = None +revs, differ = logcmdutil.getrevs(repo, pats, opts) if opts.get('graph'): if linerange: raise error.Abort(_('graph not supported with line range patterns')) -return logcmdutil.graphlog(ui, repo, revs, filematcher, opts) +return logcmdutil.graphlog(ui, repo, revs, differ, opts) if linerange: -revs, filematcher, hunksfilter = logcmdutil.getlinerangerevs( -repo, revs, opts) +revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts) getrenamed = None if opts.get('copies'): @@ -3439,9 +3437,7 @@ def log(ui, repo, *pats, **opts): getrenamed = templatekw.getrenamedfn(repo, endrev=endrev) ui.pager('log') -displayer = logcmdutil.changesetdisplayer(ui, repo, opts, - makefilematcher=filematcher, - makehunksfilter=hunksfilter, +displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ, buffered=True) for rev in revs: ctx = repo[rev] diff --git a/mercurial/logcmdutil.py b/mercurial/logcmdutil.py --- a/mercurial/logcmdutil.py +++ b/mercurial/logcmdutil.py @@ -109,6 +109,23 @@ def diffordiffstat(ui, repo, diffopts, n sub.diff(ui, diffopts, tempnode2, submatch, changes=changes, stat=stat, fp=fp, prefix=prefix) +class changesetdiffer(object): +"""Generate diff of changeset with pre-configured filtering functions""" + +def _makefilematcher(self, ctx): +return scmutil.matchall(ctx.repo()) + +def _makehunksfilter(self, ctx): +return None + +def showdiff(self, ui, ctx, diffopts, stat=False): +repo = ctx.repo() +node = ctx.node() +prev = ctx.p1().node() +diffordiffstat(ui, repo, diffopts, prev, node, + match=self._makefilematcher(ctx), stat=stat, + hunksfilterfn=self._makehunksfilter(ctx)) + def changesetlabels(ctx): labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()] if ctx.obsolete(): @@ -122,13 +139,11 @@ def changesetlabels(ctx): class changesetprinter(object): '''show changeset information when templating not requested.''' -def __init__(self, ui, repo, makefilematcher=None, makehunksfilter=None, - diffopts=None, buffered=False): +def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False): self.ui = ui self.repo = repo self.buffered = buffered -self._makefilematcher = makefilematcher or (lambda ctx: None) -self._makehunksfilter = makehunksfilter or (lambda ctx: None) +self._differ = differ or changesetdiffer() self.diffopts = diffopts or {} self.header = {} self.hunk = {} @@ -267,35 +282,23 @@ class changesetprinter(object): ''' def _showpatch(self, ctx): -matchfn = self._makefilematcher(ctx) -hunksfilterfn = self._makehunksfilter(ctx) -if not matchfn: -return stat = self.diffopts.get('stat') diff = self.diffopts.get('patch') diffopts = patch.diffallopts(self.ui, self.diffopts) -node = ctx.node() -prev = ctx.p1().node() if stat: -diffordiffstat(self.ui, self.repo, diffopts, prev, node, - match=matchfn, stat=True, - hunksfilterfn=hunksfilterfn) +self._differ.showdiff(self.ui, ctx, diffopts, stat=True) if stat and diff: self.ui.write("\n") if diff: -diffordiffstat(self.ui, self.repo, diffopts, prev, node, - match=matchfn, stat=False, - hunksfilterfn=hunksfilterfn) +self._differ.showdiff(self.ui, ctx, diffopts, stat=False) if stat or diff: self.ui.write("\n") class jsonchangeset(changesetprinter): '''format changeset information.''' -def __init__(self, ui, repo, makefilematcher=None,
[PATCH 3 of 6] logcmdutil: unindent diff generator of changesetprinter
# HG changeset patch # User Yuya Nishihara# Date 1516513024 -32400 # Sun Jan 21 14:37:04 2018 +0900 # Node ID 44e6702bf9cdef9d9df717b8632c1ba5a213b797 # Parent fa4427fe64617b5822c383f8dfdf6704d16fe01f logcmdutil: unindent diff generator of changesetprinter Prepares for the next few patches which will make matchfn and hunksfilterfn always available. diff --git a/mercurial/logcmdutil.py b/mercurial/logcmdutil.py --- a/mercurial/logcmdutil.py +++ b/mercurial/logcmdutil.py @@ -269,24 +269,25 @@ class changesetprinter(object): def _showpatch(self, ctx): matchfn = self._makefilematcher(ctx) hunksfilterfn = self._makehunksfilter(ctx) -if matchfn: -stat = self.diffopts.get('stat') -diff = self.diffopts.get('patch') -diffopts = patch.diffallopts(self.ui, self.diffopts) -node = ctx.node() -prev = ctx.p1().node() -if stat: -diffordiffstat(self.ui, self.repo, diffopts, prev, node, - match=matchfn, stat=True, - hunksfilterfn=hunksfilterfn) -if diff: -if stat: -self.ui.write("\n") -diffordiffstat(self.ui, self.repo, diffopts, prev, node, - match=matchfn, stat=False, - hunksfilterfn=hunksfilterfn) -if stat or diff: -self.ui.write("\n") +if not matchfn: +return +stat = self.diffopts.get('stat') +diff = self.diffopts.get('patch') +diffopts = patch.diffallopts(self.ui, self.diffopts) +node = ctx.node() +prev = ctx.p1().node() +if stat: +diffordiffstat(self.ui, self.repo, diffopts, prev, node, + match=matchfn, stat=True, + hunksfilterfn=hunksfilterfn) +if stat and diff: +self.ui.write("\n") +if diff: +diffordiffstat(self.ui, self.repo, diffopts, prev, node, + match=matchfn, stat=False, + hunksfilterfn=hunksfilterfn) +if stat or diff: +self.ui.write("\n") class jsonchangeset(changesetprinter): '''format changeset information.''' @@ -370,22 +371,21 @@ class jsonchangeset(changesetprinter): for k, v in copies)) matchfn = self._makefilematcher(ctx) -if matchfn: -stat = self.diffopts.get('stat') -diff = self.diffopts.get('patch') -diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True) -node, prev = ctx.node(), ctx.p1().node() -if stat: -self.ui.pushbuffer() -diffordiffstat(self.ui, self.repo, diffopts, prev, node, - match=matchfn, stat=True) -self.ui.write((',\n "diffstat": "%s"') - % j(self.ui.popbuffer())) -if diff: -self.ui.pushbuffer() -diffordiffstat(self.ui, self.repo, diffopts, prev, node, - match=matchfn, stat=False) -self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer())) +stat = self.diffopts.get('stat') +diff = self.diffopts.get('patch') +diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True) +node, prev = ctx.node(), ctx.p1().node() +if matchfn and stat: +self.ui.pushbuffer() +diffordiffstat(self.ui, self.repo, diffopts, prev, node, + match=matchfn, stat=True) +self.ui.write((',\n "diffstat": "%s"') + % j(self.ui.popbuffer())) +if matchfn and diff: +self.ui.pushbuffer() +diffordiffstat(self.ui, self.repo, diffopts, prev, node, + match=matchfn, stat=False) +self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer())) self.ui.write("\n }") ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[PATCH 4 of 6] logcmdutil: create hunksfilter and filematcher even if no diff option given
# HG changeset patch # User Yuya Nishihara# Date 1516516477 -32400 # Sun Jan 21 15:34:37 2018 +0900 # Node ID 03f30f86c95a3847be7bb7b89a8cc22d9abee524 # Parent 44e6702bf9cdef9d9df717b8632c1ba5a213b797 logcmdutil: create hunksfilter and filematcher even if no diff option given It's okay since 5fe6f946f111, "log: allow matchfn to be non-null even if both --patch/--stat are off." diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -3431,7 +3431,7 @@ def log(ui, repo, *pats, **opts): revs, lrfilematcher, hunksfilter = logcmdutil.getlinerangerevs( repo, revs, opts) -if filematcher is not None and lrfilematcher is not None: +if filematcher is not None: basefilematcher = filematcher def filematcher(rev): diff --git a/mercurial/logcmdutil.py b/mercurial/logcmdutil.py --- a/mercurial/logcmdutil.py +++ b/mercurial/logcmdutil.py @@ -779,11 +779,9 @@ def getlinerangerevs(repo, userrevs, opt "filematcher(ctx) -> match" is a factory function returning a match object for a given revision for file patterns specified in --line-range option. -If neither --stat nor --patch options are passed, "filematcher" is None. "hunksfilter(ctx) -> filterfn(fctx, hunks)" is a factory function returning a hunks filtering function. -If neither --stat nor --patch options are passed, "filterhunks" is None. """ wctx = repo[None] @@ -802,37 +800,33 @@ def getlinerangerevs(repo, userrevs, opt rev, {}).setdefault( fctx.path(), []).append(linerange) -filematcher = None -hunksfilter = None -if opts.get('patch') or opts.get('stat'): +def nofilterhunksfn(fctx, hunks): +return hunks -def nofilterhunksfn(fctx, hunks): -return hunks - -def hunksfilter(ctx): -fctxlineranges = linerangesbyrev.get(ctx.rev()) -if fctxlineranges is None: -return nofilterhunksfn +def hunksfilter(ctx): +fctxlineranges = linerangesbyrev.get(ctx.rev()) +if fctxlineranges is None: +return nofilterhunksfn -def filterfn(fctx, hunks): -lineranges = fctxlineranges.get(fctx.path()) -if lineranges is not None: -for hr, lines in hunks: -if hr is None: # binary -yield hr, lines -continue -if any(mdiff.hunkinrange(hr[2:], lr) - for lr in lineranges): -yield hr, lines -else: -for hunk in hunks: -yield hunk +def filterfn(fctx, hunks): +lineranges = fctxlineranges.get(fctx.path()) +if lineranges is not None: +for hr, lines in hunks: +if hr is None: # binary +yield hr, lines +continue +if any(mdiff.hunkinrange(hr[2:], lr) + for lr in lineranges): +yield hr, lines +else: +for hunk in hunks: +yield hunk -return filterfn +return filterfn -def filematcher(ctx): -files = list(linerangesbyrev.get(ctx.rev(), [])) -return scmutil.matchfiles(repo, files) +def filematcher(ctx): +files = list(linerangesbyrev.get(ctx.rev(), [])) +return scmutil.matchfiles(repo, files) revs = sorted(linerangesbyrev, reverse=True) ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2022: ui: improve ui.write performance when not coloring on Windows
yuja accepted this revision. yuja added a comment. This revision is now accepted and ready to land. Dropped `**opts` and queued, thanks. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2022 To: joerg.sonnenberger, #hg-reviewers, yuja Cc: yuja, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2022: ui: improve ui.write performance when not coloring on Windows
joerg.sonnenberger updated this revision to Diff 5293. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2022?vs=5181=5293 REVISION DETAIL https://phab.mercurial-scm.org/D2022 AFFECTED FILES mercurial/logcmdutil.py mercurial/ui.py CHANGE DETAILS diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -870,6 +870,17 @@ return "".join(self._buffers.pop()) +def canwritewithoutlabels(self, **opts): +'''check if write skips the label''' +if self._buffers and not self._bufferapplylabels: +return True +return self._colormode is None + +def canbatchlabeledwrites(self, **opts): +'''check if write calls with labels are batchable''' +# Windows color printing is special, see ``write``. +return self._colormode != 'win32' + def write(self, *args, **opts): '''write args to output diff --git a/mercurial/logcmdutil.py b/mercurial/logcmdutil.py --- a/mercurial/logcmdutil.py +++ b/mercurial/logcmdutil.py @@ -79,18 +79,31 @@ width = 80 if not ui.plain(): width = ui.termwidth() -chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts, -prefix=prefix, relroot=relroot, -hunksfilterfn=hunksfilterfn) -for chunk, label in patch.diffstatui(util.iterlines(chunks), - width=width): -write(chunk, label=label) + +chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts, +prefix=prefix, relroot=relroot, +hunksfilterfn=hunksfilterfn) + +if fp is not None or ui.canwritewithoutlabels(): +if stat: +chunks = patch.diffstat(util.iterlines(chunks), width=width) +for chunk in util.filechunkiter(util.chunkbuffer(chunks)): +write(chunk) else: -for chunk, label in patch.diffui(repo, node1, node2, match, - changes, opts=diffopts, prefix=prefix, - relroot=relroot, - hunksfilterfn=hunksfilterfn): -write(chunk, label=label) +if stat: +chunks = patch.diffstatui(util.iterlines(chunks), width=width) +else: +chunks = patch.difflabel(lambda chunks, **kwargs: chunks, chunks, + opts=diffopts) +if ui.canbatchlabeledwrites(): +def gen(): +for chunk, label in chunks: +yield ui.label(chunk, label=label) +for chunk in util.filechunkiter(util.chunkbuffer(gen())): +write(chunk) +else: +for chunk, label in chunks: +write(chunk, label=label) if listsubrepos: ctx1 = repo[node1] To: joerg.sonnenberger, #hg-reviewers, yuja Cc: yuja, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
mercurial@35950: 30 new changesets (1 on stable)
30 new changesets (1 on stable) in mercurial: https://www.mercurial-scm.org/repo/hg/rev/47e737d27e01 changeset: 35921:47e737d27e01 user:Matt Harbisondate:Sat Jan 27 14:53:16 2018 -0500 summary: lfs: factor out a method for extracting the pointer of a single file https://www.mercurial-scm.org/repo/hg/rev/0b79f99fd7b0 changeset: 35922:0b79f99fd7b0 user:Matt Harbison date:Sat Feb 03 21:26:12 2018 -0500 summary: lfs: prefetch lfs blobs when applying merge updates https://www.mercurial-scm.org/repo/hg/rev/efbd04238029 changeset: 35923:efbd04238029 user:Matt Harbison date:Sun Feb 04 14:14:28 2018 -0500 summary: cmdutil: convert _revertprefetch() to a generic stored file hook (API) https://www.mercurial-scm.org/repo/hg/rev/d857cad588e4 changeset: 35924:d857cad588e4 user:Matt Harbison date:Sun Feb 04 00:33:28 2018 -0500 summary: lfs: prefetch lfs blobs during revert https://www.mercurial-scm.org/repo/hg/rev/533f04d4cb6d changeset: 35925:533f04d4cb6d user:Matt Harbison date:Sun Feb 04 14:31:32 2018 -0500 summary: archive: call the storage prefetch hook https://www.mercurial-scm.org/repo/hg/rev/264b90a060b7 changeset: 35926:264b90a060b7 user:Matt Harbison date:Sun Feb 04 15:26:49 2018 -0500 summary: cat: call the storage prefetch hook https://www.mercurial-scm.org/repo/hg/rev/9b413478f261 changeset: 35927:9b413478f261 user:Matt Harbison date:Sun Feb 04 16:17:43 2018 -0500 summary: lfs: deduplicate oids in the transfer https://www.mercurial-scm.org/repo/hg/rev/b0d2885c5945 changeset: 35928:b0d2885c5945 user:Gregory Szorc date:Sun Feb 04 12:47:37 2018 -0800 summary: sshpeer: make "instance" a function https://www.mercurial-scm.org/repo/hg/rev/5f029d03cf71 changeset: 35929:5f029d03cf71 user:Gregory Szorc date:Sat Feb 03 12:01:01 2018 -0800 summary: debugcommands: introduce debugpeer command https://www.mercurial-scm.org/repo/hg/rev/83d67257ba90 changeset: 35930:83d67257ba90 user:Gregory Szorc date:Sun Feb 04 14:02:41 2018 -0800 summary: tests: add low-level SSH protocol tests https://www.mercurial-scm.org/repo/hg/rev/b202d360d2a4 changeset: 35931:b202d360d2a4 user:Gregory Szorc date:Sun Feb 04 12:55:18 2018 -0800 summary: sshpeer: move URL validation out of sshpeer.__init__ https://www.mercurial-scm.org/repo/hg/rev/31449baf0936 changeset: 35932:31449baf0936 user:Gregory Szorc date:Sun Feb 04 19:23:40 2018 -0800 summary: sshpeer: move ssh command and repo creation logic out of __init__ https://www.mercurial-scm.org/repo/hg/rev/805edf16e8e0 changeset: 35933:805edf16e8e0 user:Gregory Szorc date:Sun Feb 04 11:37:19 2018 -0800 summary: sshpeer: extract pipe cleanup logic to own function https://www.mercurial-scm.org/repo/hg/rev/94ba29934f00 changeset: 35934:94ba29934f00 user:Gregory Szorc date:Sun Feb 04 11:40:13 2018 -0800 summary: sshpeer: remove frivolous call to _cleanup() https://www.mercurial-scm.org/repo/hg/rev/00b9e26d727b changeset: 35935:00b9e26d727b user:Gregory Szorc date:Mon Feb 05 14:05:59 2018 -0800 summary: sshpeer: establish SSH connection before class instantiation https://www.mercurial-scm.org/repo/hg/rev/f8f034344b39 changeset: 35936:f8f034344b39 user:Gregory Szorc date:Mon Feb 05 14:17:24 2018 -0800 summary: sshpeer: clean up API for sshpeer.__init__ (API) https://www.mercurial-scm.org/repo/hg/rev/a9cffd14aa04 changeset: 35937:a9cffd14aa04 user:Gregory Szorc date:Sun Feb 04 14:10:56 2018 -0800 summary: sshpeer: inline I/O into _validaterepo() https://www.mercurial-scm.org/repo/hg/rev/80a2b8ae42a1 changeset: 35938:80a2b8ae42a1 user:Gregory Szorc date:Mon Feb 05 09:14:32 2018 -0800 summary: sshpeer: move handshake outside of sshpeer https://www.mercurial-scm.org/repo/hg/rev/a622a927fe03 changeset: 35939:a622a927fe03 user:Gregory Szorc date:Sun Feb 04 14:44:04 2018 -0800 summary: sshpeer: document the handshake mechanism https://www.mercurial-scm.org/repo/hg/rev/556218e08e25 changeset: 35940:556218e08e25 user:Gregory Szorc date:Sun Feb 04 14:58:32 2018 -0800 summary: sshpeer: remove support for connecting to <0.9.1 servers (BC)
Re: [PATCH 2 of 2] ui: add explicit path to write prompt text bypassing buffers
> On Feb 5, 2018, at 13:11, Yuya Nishiharawrote: > > # HG changeset patch > # User Yuya Nishihara > # Date 1517831331 -32400 > # Mon Feb 05 20:48:51 2018 +0900 > # Node ID 59869758acd7b38d9de045d5a72f5196cc80f047 > # Parent 9be8a0f8d48502734066a66e3d5b9b22e460ae70 > ui: add explicit path to write prompt text bypassing buffers queued, thanks ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 3 of 3] logcmdutil: mark changesetprinter.showpatch() as private
> On Feb 4, 2018, at 12:18, Yuya Nishiharawrote: > > # HG changeset patch > # User Yuya Nishihara > # Date 1516510856 -32400 > # Sun Jan 21 14:00:56 2018 +0900 > # Node ID 4d7182357056c2672716d7caf849231d7b25691a > # Parent f1a8a49af81a97618a4b1eb7e78c7372db776cdc > logcmdutil: mark changesetprinter.showpatch() as private queued, thanks ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 2 of 2] mdiff: use str.startswith/endswith() instead of slicing
> On Feb 4, 2018, at 12:17, Yuya Nishiharawrote: > > # HG changeset patch > # User Yuya Nishihara > # Date 1517707994 -32400 > # Sun Feb 04 10:33:14 2018 +0900 > # Node ID 16b4cc4f0cd6f72f5b7be575a7498ed0017ccea5 > # Parent d41b22b06360ceee3a9e6e66df5f57f267318314 > mdiff: use str.startswith/endswith() instead of slicing I shoulda noticed that. Queued, thanks! ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH STABLE] fileset: don't abort when running copied() on a revision with a removed file
> On Feb 7, 2018, at 05:58, Matt Harbisonwrote: > > # HG changeset patch > # User Matt Harbison > # Date 1517979217 18000 > # Tue Feb 06 23:53:37 2018 -0500 > # Branch stable > # Node ID 7b2b82f891bf6355ed87c06ed9198bfcd033fe7d > # Parent 1d60ad093792706e1dc7a52b20942593f2c19655 > fileset: don't abort when running copied() on a revision with a removed file queued, thanks ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2078: python3: whitelist an additional 11 tests
durin42 created this revision. Herald added a reviewer: pulkit. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY I think these are mostly the result of Pulkit's recent work. Thanks! REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2078 AFFECTED FILES contrib/python3-whitelist CHANGE DETAILS diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist --- a/contrib/python3-whitelist +++ b/contrib/python3-whitelist @@ -3,6 +3,7 @@ test-addremove.t test-amend-subrepo.t test-ancestor.py +test-annotate.py test-automv.t test-backwards-remove.t test-bheads.t @@ -29,8 +30,10 @@ test-confused-revert.t test-contrib-check-code.t test-contrib-check-commit.t +test-copy-move-merge.t test-debugindexdot.t test-debugrename.t +test-diff-binary-file.t test-diff-change.t test-diff-copy-depth.t test-diff-hashes.t @@ -74,6 +77,7 @@ test-exchange-obsmarkers-case-D3.t test-exchange-obsmarkers-case-D4.t test-execute-bit.t +test-extra-filelog-entry.t test-filebranch.t test-flags.t test-generaldelta.t @@ -119,9 +123,11 @@ test-merge7.t test-merge8.t test-mq-qimport-fail-cleanup.t +test-mq-qsave.t test-obshistory.t test-obsolete-changeset-exchange.t test-obsolete-checkheads.t +test-obsolete-distributed.t test-parents.t test-permissions.t test-pull-branch.t @@ -154,6 +160,9 @@ test-push-checkheads-unpushed-D6.t test-push-checkheads-unpushed-D7.t test-push-warn.t +test-rebase-inmemory.t +test-rebase-issue-noparam-single-rev.t +test-rebase-transaction.t test-record.t test-rename-after-merge.t test-rename-dir-merge.t @@ -164,6 +173,7 @@ test-revlog-group-emptyiter.t test-revlog-mmapindex.t test-revlog-packentry.t +test-revset-dirstate-parents.t test-revset-outgoing.t test-run-tests.py test-show-stack.t @@ -176,6 +186,7 @@ test-status-terse.t test-strip-cross.t test-strip.t +test-unamend.t test-uncommit.t test-unified-test.t test-unrelated-pull.t To: durin42, pulkit, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2076: py3: use bytes instead of str
This revision was automatically updated to reflect the committed changes. Closed by commit rHGf87641bf4d23: py3: use bytes instead of str (authored by pulkit, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2076?vs=5282=5290 REVISION DETAIL https://phab.mercurial-scm.org/D2076 AFFECTED FILES hgext/mq.py CHANGE DETAILS diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -1272,7 +1272,7 @@ if msg == defaultmsg.strip(): msg = '' ph.setmessage(msg) -p.write(str(ph)) +p.write(bytes(ph)) if commitfiles: parent = self.qparents(repo, n) if inclsubs: @@ -1853,7 +1853,7 @@ self.putsubstate2changes(substatestate, c) chunks = patchmod.diff(repo, patchparent, changes=c, opts=diffopts) -comments = str(ph) +comments = bytes(ph) if comments: patchf.write(comments) for chunk in chunks: To: pulkit, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel