D2768: hgweb: use a capped reader for WSGI input stream
This revision was automatically updated to reflect the committed changes. Closed by commit rHG290fc4c3d1e0: hgweb: use a capped reader for WSGI input stream (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2768?vs=6830=6905 REVISION DETAIL https://phab.mercurial-scm.org/D2768 AFFECTED FILES mercurial/hgweb/request.py CHANGE DETAILS diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -234,6 +234,14 @@ raise RuntimeError("Unknown and unsupported WSGI version %d.%d" % version) self.inp = wsgienv[r'wsgi.input'] + +if r'HTTP_CONTENT_LENGTH' in wsgienv: +self.inp = util.cappedreader(self.inp, + int(wsgienv[r'HTTP_CONTENT_LENGTH'])) +elif r'CONTENT_LENGTH' in wsgienv: +self.inp = util.cappedreader(self.inp, + int(wsgienv[r'CONTENT_LENGTH'])) + self.err = wsgienv[r'wsgi.errors'] self.threaded = wsgienv[r'wsgi.multithread'] self.multiprocess = wsgienv[r'wsgi.multiprocess'] 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
D2771: hgweb: expose input stream on parsed WSGI request object
This revision was automatically updated to reflect the committed changes. Closed by commit rHGda4e2f87167d: hgweb: expose input stream on parsed WSGI request object (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2771?vs=6833=6909 REVISION DETAIL https://phab.mercurial-scm.org/D2771 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py mercurial/hgweb/request.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -83,7 +83,7 @@ postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0)) if postlen: args.update(urlreq.parseqs( -self._wsgireq.inp.read(postlen), keep_blank_values=True)) +self._req.bodyfh.read(postlen), keep_blank_values=True)) return args argvalue = decodevaluefromheaders(self._req, b'X-HgArg') @@ -97,7 +97,7 @@ # If httppostargs is used, we need to read Content-Length # minus the amount that was consumed by args. length -= int(self._req.headers.get(b'X-HgArgs-Post', 0)) -for s in util.filechunkiter(self._wsgireq.inp, limit=length): +for s in util.filechunkiter(self._req.bodyfh, limit=length): fp.write(s) @contextlib.contextmanager diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -61,7 +61,10 @@ @attr.s(frozen=True) class parsedrequest(object): -"""Represents a parsed WSGI request / static HTTP request parameters.""" +"""Represents a parsed WSGI request. + +Contains both parsed parameters as well as a handle on the input stream. +""" # Request method. method = attr.ib() @@ -91,8 +94,10 @@ # wsgiref.headers.Headers instance. Operates like a dict with case # insensitive keys. headers = attr.ib() +# Request body input stream. +bodyfh = attr.ib() -def parserequestfromenv(env): +def parserequestfromenv(env, bodyfh): """Parse URL components from environment variables. WSGI defines request attributes via environment variables. This function @@ -209,6 +214,12 @@ if 'CONTENT_LENGTH' in env and 'HTTP_CONTENT_LENGTH' not in env: headers['Content-Length'] = env['CONTENT_LENGTH'] +# TODO do this once we remove wsgirequest.inp, otherwise we could have +# multiple readers from the underlying input stream. +#bodyfh = env['wsgi.input'] +#if 'Content-Length' in headers: +#bodyfh = util.cappedreader(bodyfh, int(headers['Content-Length'])) + return parsedrequest(method=env['REQUEST_METHOD'], url=fullurl, baseurl=baseurl, advertisedurl=advertisedfullurl, @@ -219,7 +230,8 @@ querystring=querystring, querystringlist=querystringlist, querystringdict=querystringdict, - headers=headers) + headers=headers, + bodyfh=bodyfh) class wsgirequest(object): """Higher-level API for a WSGI request. @@ -233,28 +245,27 @@ if (version < (1, 0)) or (version >= (2, 0)): raise RuntimeError("Unknown and unsupported WSGI version %d.%d" % version) -self.inp = wsgienv[r'wsgi.input'] + +inp = wsgienv[r'wsgi.input'] if r'HTTP_CONTENT_LENGTH' in wsgienv: -self.inp = util.cappedreader(self.inp, - int(wsgienv[r'HTTP_CONTENT_LENGTH'])) +inp = util.cappedreader(inp, int(wsgienv[r'HTTP_CONTENT_LENGTH'])) elif r'CONTENT_LENGTH' in wsgienv: -self.inp = util.cappedreader(self.inp, - int(wsgienv[r'CONTENT_LENGTH'])) +inp = util.cappedreader(inp, int(wsgienv[r'CONTENT_LENGTH'])) self.err = wsgienv[r'wsgi.errors'] self.threaded = wsgienv[r'wsgi.multithread'] self.multiprocess = wsgienv[r'wsgi.multiprocess'] self.run_once = wsgienv[r'wsgi.run_once'] self.env = wsgienv -self.form = normalize(cgi.parse(self.inp, +self.form = normalize(cgi.parse(inp, self.env, keep_blank_values=1)) self._start_response = start_response self.server_write = None self.headers = [] -self.req = parserequestfromenv(wsgienv) +self.req = parserequestfromenv(wsgienv, inp) def respond(self, status, type, filename=None, body=None): if not isinstance(type, str): @@ -315,7 +326,7 @@ # input stream doesn't overrun the actual request. So there's # no guarantee that
D2767: hgweb: document continuereader
This revision was automatically updated to reflect the committed changes. Closed by commit rHG7066617187c1: hgweb: document continuereader (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2767?vs=6811=6906 REVISION DETAIL https://phab.mercurial-scm.org/D2767 AFFECTED FILES mercurial/hgweb/common.py CHANGE DETAILS diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py --- a/mercurial/hgweb/common.py +++ b/mercurial/hgweb/common.py @@ -101,6 +101,13 @@ self.headers = headers class continuereader(object): +"""File object wrapper to handle HTTP 100-continue. + +This is used by servers so they automatically handle Expect: 100-continue +request headers. On first read of the request body, the 100 Continue +response is sent. This should trigger the client into actually sending +the request body. +""" def __init__(self, f, write): self.f = f self._write = write 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
D2770: hgweb: make parsedrequest part of wsgirequest
This revision was automatically updated to reflect the committed changes. Closed by commit rHG1f7d9024674c: hgweb: make parsedrequest part of wsgirequest (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2770?vs=6832=6908 REVISION DETAIL https://phab.mercurial-scm.org/D2770 AFFECTED FILES mercurial/hgweb/hgweb_mod.py mercurial/hgweb/hgwebdir_mod.py mercurial/hgweb/request.py CHANGE DETAILS diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -254,6 +254,8 @@ self.server_write = None self.headers = [] +self.req = parserequestfromenv(wsgienv) + def respond(self, status, type, filename=None, body=None): if not isinstance(type, str): type = pycompat.sysstr(type) diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -287,6 +287,11 @@ real = repos.get(virtualrepo) if real: wsgireq.env['REPO_NAME'] = virtualrepo +# We have to re-parse because of updated environment +# variable. +# TODO this is kind of hacky and we should have a better +# way of doing this than with REPO_NAME side-effects. +wsgireq.req = requestmod.parserequestfromenv(wsgireq.env) try: # ensure caller gets private copy of ui repo = hg.repository(self.ui.copy(), real) 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 @@ -304,7 +304,7 @@ yield r def _runwsgi(self, wsgireq, repo): -req = requestmod.parserequestfromenv(wsgireq.env) +req = wsgireq.req rctx = requestcontext(self, repo) # This state is global across all threads. 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
D2774: hgweb: remove support for POST form data (BC)
This revision was automatically updated to reflect the committed changes. Closed by commit rHG01f6bba64424: hgweb: remove support for POST form data (BC) (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2774?vs=6834=6910 REVISION DETAIL https://phab.mercurial-scm.org/D2774 AFFECTED FILES mercurial/hgweb/request.py CHANGE DETAILS diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -8,7 +8,6 @@ from __future__ import absolute_import -import cgi import errno import socket import wsgiref.headers as wsgiheaders @@ -258,15 +257,12 @@ self.multiprocess = wsgienv[r'wsgi.multiprocess'] self.run_once = wsgienv[r'wsgi.run_once'] self.env = wsgienv -self.form = normalize(cgi.parse(inp, -self.env, -keep_blank_values=1)) +self.req = parserequestfromenv(wsgienv, inp) +self.form = normalize(self.req.querystringdict) self._start_response = start_response self.server_write = None self.headers = [] -self.req = parserequestfromenv(wsgienv, inp) - def respond(self, status, type, filename=None, body=None): if not isinstance(type, str): type = pycompat.sysstr(type) 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
D2747: hgweb: remove unused methods on wsgirequest
This revision was automatically updated to reflect the committed changes. Closed by commit rHGe85574176467: hgweb: remove unused methods on wsgirequest (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2747?vs=6827=6902 REVISION DETAIL https://phab.mercurial-scm.org/D2747 AFFECTED FILES mercurial/hgweb/request.py CHANGE DETAILS diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -306,16 +306,9 @@ if inst[0] != errno.ECONNRESET: raise -def writelines(self, lines): -for line in lines: -self.write(line) - def flush(self): return None -def close(self): -return None - def wsgiapplication(app_maker): '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir() can and should now be used as a WSGI application.''' 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
D2746: wireprotoserver: remove unused argument from _handlehttperror()
This revision was automatically updated to reflect the committed changes. Closed by commit rHG0bc771bba220: wireprotoserver: remove unused argument from _handlehttperror() (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2746?vs=6826=6901 REVISION DETAIL https://phab.mercurial-scm.org/D2746 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 @@ -192,7 +192,7 @@ if req.dispatchpath: res = _handlehttperror( hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq, -req, cmd) +req) return True, res @@ -206,7 +206,7 @@ try: res = _callhttp(repo, wsgireq, req, proto, cmd) except hgwebcommon.ErrorResponse as e: -res = _handlehttperror(e, wsgireq, req, cmd) +res = _handlehttperror(e, wsgireq, req) return True, res @@ -313,7 +313,7 @@ return [] raise error.ProgrammingError('hgweb.protocol internal failure', rsp) -def _handlehttperror(e, wsgireq, req, cmd): +def _handlehttperror(e, wsgireq, req): """Called when an ErrorResponse is raised during HTTP request processing.""" # Clients using Python's httplib are stateful: the HTTP client 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
D2744: hgweb: handle CONTENT_LENGTH
This revision was automatically updated to reflect the committed changes. Closed by commit rHGed0456fde625: hgweb: handle CONTENT_LENGTH (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2744?vs=6824=6899 REVISION DETAIL https://phab.mercurial-scm.org/D2744 AFFECTED FILES mercurial/hgweb/request.py mercurial/wireprotoserver.py CHANGE DETAILS diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -91,10 +91,9 @@ return args def forwardpayload(self, fp): -if b'Content-Length' in self._req.headers: -length = int(self._req.headers[b'Content-Length']) -else: -length = int(self._wsgireq.env[r'CONTENT_LENGTH']) +# Existing clients *always* send Content-Length. +length = int(self._req.headers[b'Content-Length']) + # If httppostargs is used, we need to read Content-Length # minus the amount that was consumed by args. length -= int(self._req.headers.get(b'X-HgArgs-Post', 0)) diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -200,6 +200,13 @@ headers = wsgiheaders.Headers(headers) +# This is kind of a lie because the HTTP header wasn't explicitly +# sent. But for all intents and purposes it should be OK to lie about +# this, since a consumer will either either value to determine how many +# bytes are available to read. +if 'CONTENT_LENGTH' in env and 'HTTP_CONTENT_LENGTH' not in env: +headers['Content-Length'] = env['CONTENT_LENGTH'] + return parsedrequest(url=fullurl, baseurl=baseurl, advertisedurl=advertisedfullurl, advertisedbaseurl=advertisedbaseurl, To: indygreg, #hg-reviewers, durin42 Cc: mharbison72, durin42, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2743: wireprotoserver: access headers through parsed request
This revision was automatically updated to reflect the committed changes. Closed by commit rHG14f70c44af6c: wireprotoserver: access headers through parsed request (authored by indygreg, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2743?vs=6749=6898 REVISION DETAIL https://phab.mercurial-scm.org/D2743 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 @@ -36,26 +36,26 @@ SSHV1 = wireprototypes.SSHV1 SSHV2 = wireprototypes.SSHV2 -def decodevaluefromheaders(wsgireq, headerprefix): +def decodevaluefromheaders(req, headerprefix): """Decode a long value from multiple HTTP request headers. Returns the value as a bytes, not a str. """ chunks = [] i = 1 -prefix = headerprefix.upper().replace(r'-', r'_') while True: -v = wsgireq.env.get(r'HTTP_%s_%d' % (prefix, i)) +v = req.headers.get(b'%s-%d' % (headerprefix, i)) if v is None: break chunks.append(pycompat.bytesurl(v)) i += 1 return ''.join(chunks) class httpv1protocolhandler(wireprototypes.baseprotocolhandler): -def __init__(self, wsgireq, ui, checkperm): +def __init__(self, wsgireq, req, ui, checkperm): self._wsgireq = wsgireq +self._req = req self._ui = ui self._checkperm = checkperm @@ -80,24 +80,24 @@ def _args(self): args = util.rapply(pycompat.bytesurl, self._wsgireq.form.copy()) -postlen = int(self._wsgireq.env.get(r'HTTP_X_HGARGS_POST', 0)) +postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0)) if postlen: args.update(urlreq.parseqs( self._wsgireq.read(postlen), keep_blank_values=True)) return args -argvalue = decodevaluefromheaders(self._wsgireq, r'X-HgArg') +argvalue = decodevaluefromheaders(self._req, b'X-HgArg') args.update(urlreq.parseqs(argvalue, keep_blank_values=True)) return args def forwardpayload(self, fp): -if r'HTTP_CONTENT_LENGTH' in self._wsgireq.env: -length = int(self._wsgireq.env[r'HTTP_CONTENT_LENGTH']) +if b'Content-Length' in self._req.headers: +length = int(self._req.headers[b'Content-Length']) else: length = int(self._wsgireq.env[r'CONTENT_LENGTH']) # If httppostargs is used, we need to read Content-Length # minus the amount that was consumed by args. -length -= int(self._wsgireq.env.get(r'HTTP_X_HGARGS_POST', 0)) +length -= int(self._req.headers.get(b'X-HgArgs-Post', 0)) for s in util.filechunkiter(self._wsgireq, limit=length): fp.write(s) @@ -193,32 +193,32 @@ if req.dispatchpath: res = _handlehttperror( hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq, -cmd) +req, cmd) return True, res -proto = httpv1protocolhandler(wsgireq, repo.ui, +proto = httpv1protocolhandler(wsgireq, req, repo.ui, lambda perm: checkperm(rctx, wsgireq, perm)) # The permissions checker should be the only thing that can raise an # ErrorResponse. It is kind of a layer violation to catch an hgweb # exception here. So consider refactoring into a exception type that # is associated with the wire protocol. try: -res = _callhttp(repo, wsgireq, proto, cmd) +res = _callhttp(repo, wsgireq, req, proto, cmd) except hgwebcommon.ErrorResponse as e: -res = _handlehttperror(e, wsgireq, cmd) +res = _handlehttperror(e, wsgireq, req, cmd) return True, res -def _httpresponsetype(ui, wsgireq, prefer_uncompressed): +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(wsgireq, r'X-HgProto').split(' ') +protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ') if '0.2' in protocaps: # All clients are expected to support uncompressed data. @@ -251,7 +251,7 @@ opts = {'level': ui.configint('server', 'zliblevel')} return HGTYPE, util.compengines['zlib'], opts -def _callhttp(repo, wsgireq, proto, cmd): +def _callhttp(repo, wsgireq, req, proto, cmd): def genversion2(gen, engine, engineopts): # application/mercurial-0.2 always sends a payload header # identifying the compression engine. @@ -289,7 +289,7 @@ # This code for compression should not be streamres specific. It # is here because we only compress streamres at the
D2830: hgweb: remove dead wsgirequest code
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY All responses now go through our modern response type. All code related to response handling can be deleted. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2830 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py mercurial/hgweb/request.py CHANGE DETAILS diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -8,16 +8,9 @@ from __future__ import absolute_import -import errno -import socket import wsgiref.headers as wsgiheaders #import wsgiref.validate -from .common import ( -ErrorResponse, -statusmessage, -) - from ..thirdparty import ( attr, ) @@ -609,100 +602,6 @@ self.env = wsgienv self.req = parserequestfromenv(wsgienv, inp, altbaseurl=altbaseurl) self.res = wsgiresponse(self.req, start_response) -self._start_response = start_response -self.server_write = None -self.headers = [] - -def respond(self, status, type, filename=None, body=None): -if not isinstance(type, str): -type = pycompat.sysstr(type) -if self._start_response is not None: -self.headers.append((r'Content-Type', type)) -if filename: -filename = (filename.rpartition('/')[-1] -.replace('\\', '').replace('"', '\\"')) -self.headers.append(('Content-Disposition', - 'inline; filename="%s"' % filename)) -if body is not None: -self.headers.append((r'Content-Length', str(len(body - -for k, v in self.headers: -if not isinstance(v, str): -raise TypeError('header value must be string: %r' % (v,)) - -if isinstance(status, ErrorResponse): -self.headers.extend(status.headers) -status = statusmessage(status.code, pycompat.bytestr(status)) -elif status == 200: -status = '200 Script output follows' -elif isinstance(status, int): -status = statusmessage(status) - -# Various HTTP clients (notably httplib) won't read the HTTP -# response until the HTTP request has been sent in full. If servers -# (us) send a response before the HTTP request has been fully sent, -# the connection may deadlock because neither end is reading. -# -# We work around this by "draining" the request data before -# sending any response in some conditions. -drain = False -close = False - -# If the client sent Expect: 100-continue, we assume it is smart -# enough to deal with the server sending a response before reading -# the request. (httplib doesn't do this.) -if self.env.get(r'HTTP_EXPECT', r'').lower() == r'100-continue': -pass -# Only tend to request methods that have bodies. Strictly speaking, -# we should sniff for a body. But this is fine for our existing -# WSGI applications. -elif self.env[r'REQUEST_METHOD'] not in (r'POST', r'PUT'): -pass -else: -# If we don't know how much data to read, there's no guarantee -# that we can drain the request responsibly. The WSGI -# specification only says that servers *should* ensure the -# input stream doesn't overrun the actual request. So there's -# no guarantee that reading until EOF won't corrupt the stream -# state. -if not isinstance(self.req.bodyfh, util.cappedreader): -close = True -else: -# We /could/ only drain certain HTTP response codes. But 200 -# and non-200 wire protocol responses both require draining. -# Since we have a capped reader in place for all situations -# where we drain, it is safe to read from that stream. We'll -# either do a drain or no-op if we're already at EOF. -drain = True - -if close: -self.headers.append((r'Connection', r'Close')) - -if drain: -assert isinstance(self.req.bodyfh, util.cappedreader) -while True: -chunk = self.req.bodyfh.read(32768) -if not chunk: -break - -self.server_write = self._start_response( -pycompat.sysstr(status), self.headers) -self._start_response = None -self.headers = [] -if body is not None: -self.write(body) -
D2827: hgweb: use modern response type for index generation
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2827 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -16,7 +16,6 @@ from .common import ( ErrorResponse, HTTP_NOT_FOUND, -HTTP_OK, HTTP_SERVER_ERROR, cspvalues, get_contact, @@ -400,16 +399,14 @@ repos = dict(self.repos) if (not virtual or virtual == 'index') and virtual not in repos: -wsgireq.respond(HTTP_OK, ctype) -return self.makeindex(req, tmpl) +return self.makeindex(req, res, tmpl) # nested indexes and hgwebs if virtual.endswith('/index') and virtual not in repos: subdir = virtual[:-len('index')] if any(r.startswith(subdir) for r in repos): -wsgireq.respond(HTTP_OK, ctype) -return self.makeindex(req, tmpl, subdir) +return self.makeindex(req, res, tmpl, subdir) def _virtualdirs(): # Check the full virtual path, each parent, and the root ('') @@ -442,8 +439,7 @@ # browse subdirectories subdir = virtual + '/' if [r for r in repos if r.startswith(subdir)]: -wsgireq.respond(HTTP_OK, ctype) -return self.makeindex(req, tmpl, subdir) +return self.makeindex(req, res, tmpl, subdir) # prefixes not found wsgireq.respond(HTTP_NOT_FOUND, ctype) @@ -455,7 +451,7 @@ finally: tmpl = None -def makeindex(self, req, tmpl, subdir=""): +def makeindex(self, req, res, tmpl, subdir=""): self.refresh() sortable = ["name", "description", "contact", "lastchange"] sortcolumn, descending = None, False @@ -478,10 +474,16 @@ self.stripecount, sortcolumn=sortcolumn, descending=descending, subdir=subdir) -return tmpl("index", entries=entries, subdir=subdir, -pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix), -sortcolumn=sortcolumn, descending=descending, -**dict(sort)) +res.setbodygen(tmpl( +'index', +entries=entries, +subdir=subdir, +pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix), +sortcolumn=sortcolumn, +descending=descending, +**dict(sort))) + +return res.sendresponse() def templater(self, wsgireq, nonce): 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
D2829: hgweb: port to new response API
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY These were the last consumers of wsgirequest.respond() \o/ REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2829 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -15,22 +15,23 @@ from .common import ( ErrorResponse, -HTTP_NOT_FOUND, HTTP_SERVER_ERROR, cspvalues, get_contact, get_mtime, ismember, paritygen, staticfile, +statusmessage, ) from .. import ( configitems, encoding, error, hg, profiling, +pycompat, scmutil, templater, ui as uimod, @@ -442,12 +443,14 @@ return self.makeindex(req, res, tmpl, subdir) # prefixes not found -wsgireq.respond(HTTP_NOT_FOUND, ctype) -return tmpl("notfound", repo=virtual) +res.status = '404 Not Found' +res.setbodygen(tmpl('notfound', repo=virtual)) +return res.sendresponse() -except ErrorResponse as err: -wsgireq.respond(err, ctype) -return tmpl('error', error=err.message or '') +except ErrorResponse as e: +res.status = statusmessage(e.code, pycompat.bytestr(e)) +res.setbodygen(tmpl('error', error=e.message or '')) +return res.sendresponse() finally: tmpl = None 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
D2814: hgweb: move rawentries() to a standalone function
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY It was only accessing a few variables from the outer scope. Let's make it standalone so there is better clarity about what the inputs are. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2814 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -148,6 +148,131 @@ return archives +def rawindexentries(ui, repos, wsgireq, req, subdir='', **map): +descend = ui.configbool('web', 'descend') +collapse = ui.configbool('web', 'collapse') +seenrepos = set() +seendirs = set() +for name, path in repos: + +if not name.startswith(subdir): +continue +name = name[len(subdir):] +directory = False + +if '/' in name: +if not descend: +continue + +nameparts = name.split('/') +rootname = nameparts[0] + +if not collapse: +pass +elif rootname in seendirs: +continue +elif rootname in seenrepos: +pass +else: +directory = True +name = rootname + +# redefine the path to refer to the directory +discarded = '/'.join(nameparts[1:]) + +# remove name parts plus accompanying slash +path = path[:-len(discarded) - 1] + +try: +r = hg.repository(ui, path) +directory = False +except (IOError, error.RepoError): +pass + +parts = [name] +parts.insert(0, '/' + subdir.rstrip('/')) +if wsgireq.env['SCRIPT_NAME']: +parts.insert(0, wsgireq.env['SCRIPT_NAME']) +url = re.sub(r'/+', '/', '/'.join(parts) + '/') + +# show either a directory entry or a repository +if directory: +# get the directory's time information +try: +d = (get_mtime(path), dateutil.makedate()[1]) +except OSError: +continue + +# add '/' to the name to make it obvious that +# the entry is a directory, not a regular repository +row = {'contact': "", + 'contact_sort': "", + 'name': name + '/', + 'name_sort': name, + 'url': url, + 'description': "", + 'description_sort': "", + 'lastchange': d, + 'lastchange_sort': d[1] - d[0], + 'archives': [], + 'isdirectory': True, + 'labels': [], + } + +seendirs.add(name) +yield row +continue + +u = ui.copy() +try: +u.readconfig(os.path.join(path, '.hg', 'hgrc')) +except Exception as e: +u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e)) +continue + +def get(section, name, default=uimod._unset): +return u.config(section, name, default, untrusted=True) + +if u.configbool("web", "hidden", untrusted=True): +continue + +if not readallowed(u, req): +continue + +# update time with local timezone +try: +r = hg.repository(ui, path) +except IOError: +u.warn(_('error accessing repository at %s\n') % path) +continue +except error.RepoError: +u.warn(_('error accessing repository at %s\n') % path) +continue +try: +d = (get_mtime(r.spath), dateutil.makedate()[1]) +except OSError: +continue + +contact = get_contact(get) +description = get("web", "description") +seenrepos.add(name) +name = get("web", "name", name) +row = {'contact': contact or "unknown", + 'contact_sort': contact.upper() or "unknown", + 'name': name, + 'name_sort': name, + 'url': url, + 'description': description or "unknown", + 'description_sort': description.upper() or "unknown", + 'lastchange': d, + 'lastchange_sort': d[1] - d[0], + 'archives': archivelist(u, "tip", url), + 'isdirectory': None, + 'labels': u.configlist('web', 'labels', untrusted=True), + } + +yield row + class hgwebdir(object): """HTTP server for multiple repositories. @@ -347,134 +472,10 @@ def makeindex(self, wsgireq, tmpl, subdir=""): req = wsgireq.req -def rawentries(subdir="", **map): -
D2818: tests: add test coverage for parsing WSGI requests
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY A subsequent commit will need to make this code more complicated in order to support alternate base URLs. Let's establish some test coverage before we diverge too far from PEP . As part of this, a minor bug related to a missing SCRIPT_NAME key has been squashed. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2818 AFFECTED FILES mercurial/hgweb/request.py tests/test-wsgirequest.py CHANGE DETAILS diff --git a/tests/test-wsgirequest.py b/tests/test-wsgirequest.py new file mode 100644 --- /dev/null +++ b/tests/test-wsgirequest.py @@ -0,0 +1,255 @@ +from __future__ import absolute_import, print_function + +import unittest + +from mercurial.hgweb import ( +request as requestmod, +) + +DEFAULT_ENV = { +r'REQUEST_METHOD': r'GET', +r'SERVER_NAME': r'testserver', +r'SERVER_PORT': r'80', +r'SERVER_PROTOCOL': r'http', +r'wsgi.version': (1, 0), +r'wsgi.url_scheme': r'http', +r'wsgi.input': None, +r'wsgi.errors': None, +r'wsgi.multithread': False, +r'wsgi.multiprocess': True, +r'wsgi.run_once': False, +} + +def parse(env, bodyfh=None, extra=None): +env = dict(env) +env.update(extra or {}) + +return requestmod.parserequestfromenv(env, bodyfh) + +class ParseRequestTests(unittest.TestCase): +def testdefault(self): +r = parse(DEFAULT_ENV) +self.assertEqual(r.url, b'http://testserver') +self.assertEqual(r.baseurl, b'http://testserver') +self.assertEqual(r.advertisedurl, b'http://testserver') +self.assertEqual(r.advertisedbaseurl, b'http://testserver') +self.assertEqual(r.urlscheme, b'http') +self.assertEqual(r.method, b'GET') +self.assertIsNone(r.remoteuser) +self.assertIsNone(r.remotehost) +self.assertEqual(r.apppath, b'') +self.assertEqual(r.dispatchparts, []) +self.assertEqual(r.dispatchpath, b'') +self.assertFalse(r.havepathinfo) +self.assertIsNone(r.reponame) +self.assertEqual(r.querystring, b'') +self.assertEqual(len(r.qsparams), 0) +self.assertEqual(len(r.headers), 0) + +def testcustomport(self): +r = parse(DEFAULT_ENV, extra={ +r'SERVER_PORT': r'8000', +}) + +self.assertEqual(r.url, b'http://testserver:8000') +self.assertEqual(r.baseurl, b'http://testserver:8000') +self.assertEqual(r.advertisedurl, b'http://testserver:8000') +self.assertEqual(r.advertisedbaseurl, b'http://testserver:8000') + +r = parse(DEFAULT_ENV, extra={ +r'SERVER_PORT': r'4000', +r'wsgi.url_scheme': r'https', +}) + +self.assertEqual(r.url, b'https://testserver:4000') +self.assertEqual(r.baseurl, b'https://testserver:4000') +self.assertEqual(r.advertisedurl, b'https://testserver:4000') +self.assertEqual(r.advertisedbaseurl, b'https://testserver:4000') + +def testhttphost(self): +r = parse(DEFAULT_ENV, extra={ +r'HTTP_HOST': r'altserver', +}) + +self.assertEqual(r.url, b'http://altserver') +self.assertEqual(r.baseurl, b'http://altserver') +self.assertEqual(r.advertisedurl, b'http://testserver') +self.assertEqual(r.advertisedbaseurl, b'http://testserver') + +def testscriptname(self): +r = parse(DEFAULT_ENV, extra={ +r'SCRIPT_NAME': r'', +}) + +self.assertEqual(r.url, b'http://testserver') +self.assertEqual(r.baseurl, b'http://testserver') +self.assertEqual(r.advertisedurl, b'http://testserver') +self.assertEqual(r.advertisedbaseurl, b'http://testserver') +self.assertEqual(r.apppath, b'') +self.assertEqual(r.dispatchparts, []) +self.assertEqual(r.dispatchpath, b'') +self.assertFalse(r.havepathinfo) + +r = parse(DEFAULT_ENV, extra={ +r'SCRIPT_NAME': r'/script', +}) + +self.assertEqual(r.url, b'http://testserver/script') +self.assertEqual(r.baseurl, b'http://testserver') +self.assertEqual(r.advertisedurl, b'http://testserver/script') +self.assertEqual(r.advertisedbaseurl, b'http://testserver') +self.assertEqual(r.apppath, b'/script') +self.assertEqual(r.dispatchparts, []) +self.assertEqual(r.dispatchpath, b'') +self.assertFalse(r.havepathinfo) + +r = parse(DEFAULT_ENV, extra={ +r'SCRIPT_NAME': r'/multiple words', +}) + +self.assertEqual(r.url, b'http://testserver/multiple%20words') +self.assertEqual(r.baseurl, b'http://testserver') +self.assertEqual(r.advertisedurl, b'http://testserver/multiple%20words') +self.assertEqual(r.advertisedbaseurl, b'http://testserver') +self.assertEqual(r.apppath, b'/multiple words') +
D2825: hgweb: replace PATH_INFO with dispatchpath
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY This was the last consumer of wsgireq.env from our WSGI applications! (Although indirect consumers of this attribute exist in wsgirequest.respond().) REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2825 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -369,7 +369,7 @@ res.headers['Content-Security-Policy'] = csp wsgireq.headers.append(('Content-Security-Policy', csp)) -virtual = wsgireq.env.get("PATH_INFO", "").strip('/') +virtual = req.dispatchpath.strip('/') tmpl = self.templater(wsgireq, nonce) ctype = tmpl('mimetype', encoding=encoding.encoding) ctype = templater.stringify(ctype) 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
D2831: hgweb: store the raw WSGI environment dict
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY We need this so we can construct a new request instance from the original dict. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2831 AFFECTED FILES mercurial/hgweb/hgweb_mod.py mercurial/hgweb/request.py CHANGE DETAILS diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -149,6 +149,8 @@ headers = attr.ib() # Request body input stream. bodyfh = attr.ib() +# WSGI environment dict, unmodified. +rawenv = attr.ib() def parserequestfromenv(env, bodyfh, reponame=None, altbaseurl=None): """Parse URL components from environment variables. @@ -342,7 +344,8 @@ querystring=querystring, qsparams=qsparams, headers=headers, - bodyfh=bodyfh) + bodyfh=bodyfh, + rawenv=env) class offsettrackingwriter(object): """A file object like object that is append only and tracks write count. 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 @@ -312,7 +312,7 @@ # This state is global across all threads. encoding.encoding = rctx.config('web', 'encoding') -rctx.repo.ui.environ = wsgireq.env +rctx.repo.ui.environ = req.rawenv if rctx.csp: # hgwebdir may have added CSP header. Since we generate our own, 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
D2824: hgweb: rewrite path generation for index entries
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY I think this code is easier to read. But the real reason to do this is to eliminate a consumer of wsgirequest. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2824 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -9,7 +9,6 @@ from __future__ import absolute_import import os -import re import time from ..i18n import _ @@ -161,11 +160,12 @@ except (IOError, error.RepoError): pass -parts = [name] -parts.insert(0, '/' + subdir.rstrip('/')) -if wsgireq.env['SCRIPT_NAME']: -parts.insert(0, wsgireq.env['SCRIPT_NAME']) -url = re.sub(r'/+', '/', '/'.join(parts) + '/') +parts = [ +wsgireq.req.apppath.strip('/'), +subdir.strip('/'), +name.strip('/'), +] +url = '/' + '/'.join(p for p in parts if p) + '/' # show either a directory entry or a repository if directory: 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
D2813: hgweb: move archivelist to standalone function
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY This doesn't need to exist as an inline function in a method. Minor formatting changes were made as part of the move. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2813 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -132,6 +132,22 @@ return False +def archivelist(ui, nodeid, url): +allowed = ui.configlist('web', 'allow_archive', untrusted=True) +archives = [] + +for typ, spec in hgweb_mod.archivespecs.iteritems(): +if typ in allowed or ui.configbool('web', 'allow' + typ, + untrusted=True): +archives.append({ +'type': typ, +'extension': spec[2], +'node': nodeid, +'url': url, +}) + +return archives + class hgwebdir(object): """HTTP server for multiple repositories. @@ -331,16 +347,6 @@ def makeindex(self, wsgireq, tmpl, subdir=""): req = wsgireq.req -def archivelist(ui, nodeid, url): -allowed = ui.configlist("web", "allow_archive", untrusted=True) -archives = [] -for typ, spec in hgweb_mod.archivespecs.iteritems(): -if typ in allowed or ui.configbool("web", "allow" + typ, -untrusted=True): -archives.append({"type": typ, "extension": spec[2], - "node": nodeid, "url": url}) -return archives - def rawentries(subdir="", **map): descend = self.ui.configbool('web', 'descend') 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
D2817: hgweb: construct static URL like hgweb does
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY hgwebdir has a bit of code for constructing URLs. This reinvents wheels from our parsedrequest instance. And sometimes the behavior varies from what hgweb does. We'll want to converge that behavior. This commit changes hgwebdir so its staticurl template keyword is constructed the same way as hgweb's. There's probably room to factor this into a shared function. But let's solve the problem of divergence first. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2817 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -542,7 +542,8 @@ sessionvars = webutil.sessionvars(vars, r'?') logourl = config('web', 'logourl') logoimg = config('web', 'logoimg') -staticurl = config('web', 'staticurl') or url + 'static/' +staticurl = (config('web', 'staticurl') + or wsgireq.req.apppath + '/static/') if not staticurl.endswith('/'): staticurl += '/' 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
D2828: hgweb: pass modern request type into templater()
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY Only a handful of consumers of wsgirequest remaining in this file... REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2828 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -369,7 +369,7 @@ wsgireq.headers.append(('Content-Security-Policy', csp)) virtual = req.dispatchpath.strip('/') -tmpl = self.templater(wsgireq, nonce) +tmpl = self.templater(req, nonce) ctype = tmpl('mimetype', encoding=encoding.encoding) ctype = templater.stringify(ctype) @@ -485,7 +485,7 @@ return res.sendresponse() -def templater(self, wsgireq, nonce): +def templater(self, req, nonce): def motd(**map): if self.motd is not None: @@ -497,23 +497,23 @@ return self.ui.config(section, name, default, untrusted) vars = {} -styles, (style, mapfile) = hgweb_mod.getstyle(wsgireq.req, config, +styles, (style, mapfile) = hgweb_mod.getstyle(req, config, self.templatepath) if style == styles[0]: vars['style'] = style sessionvars = webutil.sessionvars(vars, r'?') logourl = config('web', 'logourl') logoimg = config('web', 'logoimg') staticurl = (config('web', 'staticurl') - or wsgireq.req.apppath + '/static/') + or req.apppath + '/static/') if not staticurl.endswith('/'): staticurl += '/' defaults = { "encoding": encoding.encoding, "motd": motd, -"url": wsgireq.req.apppath + '/', +"url": req.apppath + '/', "logourl": logourl, "logoimg": logoimg, "staticurl": staticurl, 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
D2821: hgweb: clarify that apppath begins with a forward slash
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2821 AFFECTED FILES mercurial/hgweb/request.py CHANGE DETAILS diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -134,7 +134,8 @@ remoteuser = attr.ib() # Value of REMOTE_HOST, if set, or None. remotehost = attr.ib() -# WSGI application path. +# Relative WSGI application path. If defined, will begin with a +# ``/``. apppath = attr.ib() # List of path parts to be used for dispatch. dispatchparts = attr.ib() 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
D2826: hgweb: don't pass wsgireq to makeindex and other functions
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY We only ever access attributes that are available on our newer request type. So we no longer need this argument. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2826 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -119,7 +119,7 @@ return archives -def rawindexentries(ui, repos, wsgireq, req, subdir=''): +def rawindexentries(ui, repos, req, subdir=''): descend = ui.configbool('web', 'descend') collapse = ui.configbool('web', 'collapse') seenrepos = set() @@ -161,7 +161,7 @@ pass parts = [ -wsgireq.req.apppath.strip('/'), +req.apppath.strip('/'), subdir.strip('/'), name.strip('/'), ] @@ -245,10 +245,10 @@ yield row -def indexentries(ui, repos, wsgireq, req, stripecount, sortcolumn='', +def indexentries(ui, repos, req, stripecount, sortcolumn='', descending=False, subdir=''): -rows = rawindexentries(ui, repos, wsgireq, req, subdir=subdir) +rows = rawindexentries(ui, repos, req, subdir=subdir) sortdefault = None, False @@ -401,15 +401,15 @@ if (not virtual or virtual == 'index') and virtual not in repos: wsgireq.respond(HTTP_OK, ctype) -return self.makeindex(wsgireq, tmpl) +return self.makeindex(req, tmpl) # nested indexes and hgwebs if virtual.endswith('/index') and virtual not in repos: subdir = virtual[:-len('index')] if any(r.startswith(subdir) for r in repos): wsgireq.respond(HTTP_OK, ctype) -return self.makeindex(wsgireq, tmpl, subdir) +return self.makeindex(req, tmpl, subdir) def _virtualdirs(): # Check the full virtual path, each parent, and the root ('') @@ -443,7 +443,7 @@ subdir = virtual + '/' if [r for r in repos if r.startswith(subdir)]: wsgireq.respond(HTTP_OK, ctype) -return self.makeindex(wsgireq, tmpl, subdir) +return self.makeindex(req, tmpl, subdir) # prefixes not found wsgireq.respond(HTTP_NOT_FOUND, ctype) @@ -455,9 +455,7 @@ finally: tmpl = None -def makeindex(self, wsgireq, tmpl, subdir=""): -req = wsgireq.req - +def makeindex(self, req, tmpl, subdir=""): self.refresh() sortable = ["name", "description", "contact", "lastchange"] sortcolumn, descending = None, False @@ -476,7 +474,7 @@ self.refresh() -entries = indexentries(self.ui, self.repos, wsgireq, req, +entries = indexentries(self.ui, self.repos, req, self.stripecount, sortcolumn=sortcolumn, descending=descending, subdir=subdir) 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
D2820: hgweb: change how dispatch path is reported
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY When I implemented the new request object, I carried forward some ugly hacks until I could figure out what was happening. One of those was the handling of PATH_INFO to determine how to route hgweb requests. Essentially, if we have PATH_INFO data, we route according to that. But if we don't, we route by the query string. I question if we still need to support query string routing. But that's for another day, I suppose. In this commit, we clean up the ugly "havepathinfo" hack and replace it with a "dispatchpath" attribute that can hold None or empty string to differentiate between the presence of PATH_INFO. This is still a bit hacky. But at least the request parsing and routing code is explicit about the meaning now. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2820 AFFECTED FILES mercurial/hgweb/hgweb_mod.py mercurial/hgweb/request.py tests/test-wsgirequest.py CHANGE DETAILS diff --git a/tests/test-wsgirequest.py b/tests/test-wsgirequest.py --- a/tests/test-wsgirequest.py +++ b/tests/test-wsgirequest.py @@ -42,8 +42,7 @@ self.assertIsNone(r.remotehost) self.assertEqual(r.apppath, b'') self.assertEqual(r.dispatchparts, []) -self.assertEqual(r.dispatchpath, b'') -self.assertFalse(r.havepathinfo) +self.assertIsNone(r.dispatchpath) self.assertIsNone(r.reponame) self.assertEqual(r.querystring, b'') self.assertEqual(len(r.qsparams), 0) @@ -90,8 +89,7 @@ self.assertEqual(r.advertisedbaseurl, b'http://testserver') self.assertEqual(r.apppath, b'') self.assertEqual(r.dispatchparts, []) -self.assertEqual(r.dispatchpath, b'') -self.assertFalse(r.havepathinfo) +self.assertIsNone(r.dispatchpath) r = parse(DEFAULT_ENV, extra={ r'SCRIPT_NAME': r'/script', @@ -103,8 +101,7 @@ self.assertEqual(r.advertisedbaseurl, b'http://testserver') self.assertEqual(r.apppath, b'/script') self.assertEqual(r.dispatchparts, []) -self.assertEqual(r.dispatchpath, b'') -self.assertFalse(r.havepathinfo) +self.assertIsNone(r.dispatchpath) r = parse(DEFAULT_ENV, extra={ r'SCRIPT_NAME': r'/multiple words', @@ -116,8 +113,7 @@ self.assertEqual(r.advertisedbaseurl, b'http://testserver') self.assertEqual(r.apppath, b'/multiple words') self.assertEqual(r.dispatchparts, []) -self.assertEqual(r.dispatchpath, b'') -self.assertFalse(r.havepathinfo) +self.assertIsNone(r.dispatchpath) def testpathinfo(self): r = parse(DEFAULT_ENV, extra={ @@ -131,7 +127,6 @@ self.assertEqual(r.apppath, b'') self.assertEqual(r.dispatchparts, []) self.assertEqual(r.dispatchpath, b'') -self.assertTrue(r.havepathinfo) r = parse(DEFAULT_ENV, extra={ r'PATH_INFO': r'/pathinfo', @@ -144,7 +139,6 @@ self.assertEqual(r.apppath, b'') self.assertEqual(r.dispatchparts, [b'pathinfo']) self.assertEqual(r.dispatchpath, b'pathinfo') -self.assertTrue(r.havepathinfo) r = parse(DEFAULT_ENV, extra={ r'PATH_INFO': r'/one/two/', @@ -157,7 +151,6 @@ self.assertEqual(r.apppath, b'') self.assertEqual(r.dispatchparts, [b'one', b'two']) self.assertEqual(r.dispatchpath, b'one/two') -self.assertTrue(r.havepathinfo) def testscriptandpathinfo(self): r = parse(DEFAULT_ENV, extra={ @@ -172,7 +165,6 @@ self.assertEqual(r.apppath, b'/script') self.assertEqual(r.dispatchparts, [b'pathinfo']) self.assertEqual(r.dispatchpath, b'pathinfo') -self.assertTrue(r.havepathinfo) r = parse(DEFAULT_ENV, extra={ r'SCRIPT_NAME': r'/script1/script2', @@ -188,7 +180,6 @@ self.assertEqual(r.apppath, b'/script1/script2') self.assertEqual(r.dispatchparts, [b'path1', b'path2']) self.assertEqual(r.dispatchpath, b'path1/path2') -self.assertTrue(r.havepathinfo) r = parse(DEFAULT_ENV, extra={ r'HTTP_HOST': r'hostserver', @@ -203,7 +194,6 @@ self.assertEqual(r.apppath, b'/script') self.assertEqual(r.dispatchparts, [b'pathinfo']) self.assertEqual(r.dispatchpath, b'pathinfo') -self.assertTrue(r.havepathinfo) def testreponame(self): """repository path components get stripped from URL.""" @@ -236,7 +226,6 @@ self.assertEqual(r.apppath, b'/repo') self.assertEqual(r.dispatchparts, [b'path1', b'path2']) self.assertEqual(r.dispatchpath, b'path1/path2') -self.assertTrue(r.havepathinfo) self.assertEqual(r.reponame, b'repo') r = parse(DEFAULT_ENV,
D2812: hgweb: move readallowed to a standalone function
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY hgwebdir s kind of large. Let's make the class smaller by moving things that don't need to be there. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2812 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -110,6 +110,28 @@ return name, pycompat.bytestr(port), path +def readallowed(ui, req): +"""Check allow_read and deny_read config options of a repo's ui object +to determine user permissions. By default, with neither option set (or +both empty), allow all users to read the repo. There are two ways a +user can be denied read access: (1) deny_read is not empty, and the +user is unauthenticated or deny_read contains user (or *), and (2) +allow_read is not empty and the user is not in allow_read. Return True +if user is allowed to read the repo, else return False.""" + +user = req.remoteuser + +deny_read = ui.configlist('web', 'deny_read', untrusted=True) +if deny_read and (not user or ismember(ui, user, deny_read)): +return False + +allow_read = ui.configlist('web', 'allow_read', untrusted=True) +# by default, allow reading if no allow_read option has been set +if not allow_read or ismember(ui, user, allow_read): +return True + +return False + class hgwebdir(object): """HTTP server for multiple repositories. @@ -200,28 +222,6 @@ wsgireq = requestmod.wsgirequest(env, respond) return self.run_wsgi(wsgireq) -def readallowed(self, ui, req): -"""Check allow_read and deny_read config options of a repo's ui object -to determine user permissions. By default, with neither option set (or -both empty), allow all users to read the repo. There are two ways a -user can be denied read access: (1) deny_read is not empty, and the -user is unauthenticated or deny_read contains user (or *), and (2) -allow_read is not empty and the user is not in allow_read. Return True -if user is allowed to read the repo, else return False.""" - -user = req.remoteuser - -deny_read = ui.configlist('web', 'deny_read', untrusted=True) -if deny_read and (not user or ismember(ui, user, deny_read)): -return False - -allow_read = ui.configlist('web', 'allow_read', untrusted=True) -# by default, allow reading if no allow_read option has been set -if (not allow_read) or ismember(ui, user, allow_read): -return True - -return False - def run_wsgi(self, wsgireq): profile = self.ui.configbool('profiling', 'enabled') with profiling.profile(self.ui, enabled=profile): @@ -429,7 +429,7 @@ if u.configbool("web", "hidden", untrusted=True): continue -if not self.readallowed(u, req): +if not readallowed(u, req): continue # update time with local timezone 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
D2819: hgweb: refactor repository name URL parsing
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY The hgwebdir WSGI application detects when a requested URL is for a known repository and it effectively forwards the request to the hgweb WSGI application. The hgweb WSGI application needs to route the request based on the base URL for the repository. The way this normally works is SCRIPT_NAME is used to resolve the base URL and PATH_INFO contains the path after the script. But with hgwebdir, SCRIPT_NAME refers to hgwebdir, not the base URL for the repository. So, there was a hacky REPO_NAME environment variable being set to convey the part of the URL that represented the repository so hgweb could ignore this path component for routing purposes. The use of the environment variable for passing internal state is pretty hacky. Plus, it wasn't clear from the perspective of the URL parsing code what was going on. This commit improves matters by making the repository name an explicit argument to the request parser. The logic around handling of this value has been shored up. We add various checks that the argument is used properly - that the repository name does represent the prefix of the PATH_INFO. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2819 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py mercurial/hgweb/request.py tests/test-wsgirequest.py CHANGE DETAILS diff --git a/tests/test-wsgirequest.py b/tests/test-wsgirequest.py --- a/tests/test-wsgirequest.py +++ b/tests/test-wsgirequest.py @@ -5,6 +5,9 @@ from mercurial.hgweb import ( request as requestmod, ) +from mercurial import ( +error, +) DEFAULT_ENV = { r'REQUEST_METHOD': r'GET', @@ -20,11 +23,11 @@ r'wsgi.run_once': False, } -def parse(env, bodyfh=None, extra=None): +def parse(env, bodyfh=None, reponame=None, extra=None): env = dict(env) env.update(extra or {}) -return requestmod.parserequestfromenv(env, bodyfh) +return requestmod.parserequestfromenv(env, bodyfh, reponame=reponame) class ParseRequestTests(unittest.TestCase): def testdefault(self): @@ -203,24 +206,26 @@ self.assertTrue(r.havepathinfo) def testreponame(self): -"""REPO_NAME path components get stripped from URL.""" -r = parse(DEFAULT_ENV, extra={ -r'REPO_NAME': r'repo', -r'PATH_INFO': r'/path1/path2' -}) +"""repository path components get stripped from URL.""" + +with self.assertRaisesRegexp(error.ProgrammingError, + b'reponame requires PATH_INFO'): +parse(DEFAULT_ENV, reponame=b'repo') -self.assertEqual(r.url, b'http://testserver/path1/path2') -self.assertEqual(r.baseurl, b'http://testserver') -self.assertEqual(r.advertisedurl, b'http://testserver/path1/path2') -self.assertEqual(r.advertisedbaseurl, b'http://testserver') -self.assertEqual(r.apppath, b'/repo') -self.assertEqual(r.dispatchparts, [b'path1', b'path2']) -self.assertEqual(r.dispatchpath, b'path1/path2') -self.assertTrue(r.havepathinfo) -self.assertEqual(r.reponame, b'repo') +with self.assertRaisesRegexp(error.ProgrammingError, + b'PATH_INFO does not begin with repo ' + b'name'): +parse(DEFAULT_ENV, reponame=b'repo', extra={ +r'PATH_INFO': r'/pathinfo', +}) -r = parse(DEFAULT_ENV, extra={ -r'REPO_NAME': r'repo', +with self.assertRaisesRegexp(error.ProgrammingError, + b'reponame prefix of PATH_INFO'): +parse(DEFAULT_ENV, reponame=b'repo', extra={ +r'PATH_INFO': r'/repoextra/path', +}) + +r = parse(DEFAULT_ENV, reponame=b'repo', extra={ r'PATH_INFO': r'/repo/path1/path2', }) @@ -234,8 +239,7 @@ self.assertTrue(r.havepathinfo) self.assertEqual(r.reponame, b'repo') -r = parse(DEFAULT_ENV, extra={ -r'REPO_NAME': r'prefix/repo', +r = parse(DEFAULT_ENV, reponame=b'prefix/repo', extra={ r'PATH_INFO': r'/prefix/repo/path1/path2', }) diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -155,11 +155,16 @@ # Request body input stream. bodyfh = attr.ib() -def parserequestfromenv(env, bodyfh): +def parserequestfromenv(env, bodyfh, reponame=None): """Parse URL components from environment variables. WSGI defines request attributes via environment variables. This function parses the environment variables into a data structure. + +If ``reponame`` is defined, the leading path components matching that +string are effectively
D2816: hgweb: remove unused **map argument
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY It was unused before the recent code refactoring AFAICT. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2816 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -148,7 +148,7 @@ return archives -def rawindexentries(ui, repos, wsgireq, req, subdir='', **map): +def rawindexentries(ui, repos, wsgireq, req, subdir=''): descend = ui.configbool('web', 'descend') collapse = ui.configbool('web', 'collapse') seenrepos = set() @@ -274,9 +274,9 @@ yield row def indexentries(ui, repos, wsgireq, req, stripecount, sortcolumn='', - descending=False, subdir='', **map): + descending=False, subdir=''): -rows = rawindexentries(ui, repos, wsgireq, req, subdir=subdir, **map) +rows = rawindexentries(ui, repos, wsgireq, req, subdir=subdir) sortdefault = None, False 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
D2805: hgweb: remove some use of wsgireq in hgwebdir
indygreg updated this revision to Diff 6877. indygreg edited the summary of this revision. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2805?vs=6866=6877 REVISION DETAIL https://phab.mercurial-scm.org/D2805 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -200,16 +200,16 @@ wsgireq = requestmod.wsgirequest(env, respond) return self.run_wsgi(wsgireq) -def read_allowed(self, ui, wsgireq): +def readallowed(self, ui, req): """Check allow_read and deny_read config options of a repo's ui object to determine user permissions. By default, with neither option set (or both empty), allow all users to read the repo. There are two ways a user can be denied read access: (1) deny_read is not empty, and the user is unauthenticated or deny_read contains user (or *), and (2) allow_read is not empty and the user is not in allow_read. Return True if user is allowed to read the repo, else return False.""" -user = wsgireq.env.get('REMOTE_USER') +user = req.remoteuser deny_read = ui.configlist('web', 'deny_read', untrusted=True) if deny_read and (not user or ismember(ui, user, deny_read)): @@ -329,6 +329,7 @@ tmpl = None def makeindex(self, wsgireq, tmpl, subdir=""): +req = wsgireq.req def archivelist(ui, nodeid, url): allowed = ui.configlist("web", "allow_archive", untrusted=True) @@ -428,7 +429,7 @@ if u.configbool("web", "hidden", untrusted=True): continue -if not self.read_allowed(u, wsgireq): +if not self.readallowed(u, req): continue # update time with local timezone @@ -480,8 +481,8 @@ self.refresh() sortable = ["name", "description", "contact", "lastchange"] sortcolumn, descending = sortdefault -if 'sort' in wsgireq.req.qsparams: -sortcolumn = wsgireq.req.qsparams['sort'] +if 'sort' in req.qsparams: +sortcolumn = req.qsparams['sort'] descending = sortcolumn.startswith('-') if descending: sortcolumn = sortcolumn[1:] 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
D2822: hgweb: support constructing URLs from an alternate base URL
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY The web.baseurl config option allows server operators to define a custom URL for hosted content. The way it works today is that hgwebdir parses this config option into URL components then updates the appropriate WSGI environment variables so the request "lies" about its details. For example, SERVER_NAME is updated to reflect the alternate base URL's hostname. The WSGI environment should not be modified because WSGI applications may want to know the original request details (for debugging, etc). This commit teaches our request parser about the existence of an alternate base URL. If defined, the advertised URL and other self-reflected paths will take the alternate base URL into account. The hgweb WSGI application didn't use web.baseurl. But hgwebdir did. We update hgwebdir to alter the environment parsing accordingly. The old code around environment manipulation has been removed. With this change, parserequestfromenv() has grown to a bit unwieldy. Now that practically everyone is using it, it is obvious that there is some unused features that can be trimmed. So look for this in follow-up commits. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2822 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py mercurial/hgweb/request.py tests/test-wsgirequest.py CHANGE DETAILS diff --git a/tests/test-wsgirequest.py b/tests/test-wsgirequest.py --- a/tests/test-wsgirequest.py +++ b/tests/test-wsgirequest.py @@ -23,11 +23,12 @@ r'wsgi.run_once': False, } -def parse(env, bodyfh=None, reponame=None, extra=None): +def parse(env, bodyfh=None, reponame=None, altbaseurl=None, extra=None): env = dict(env) env.update(extra or {}) -return requestmod.parserequestfromenv(env, bodyfh, reponame=reponame) +return requestmod.parserequestfromenv(env, bodyfh, reponame=reponame, + altbaseurl=altbaseurl) class ParseRequestTests(unittest.TestCase): def testdefault(self): @@ -242,6 +243,174 @@ self.assertEqual(r.dispatchpath, b'path1/path2') self.assertEqual(r.reponame, b'prefix/repo') +def testaltbaseurl(self): +# Simple hostname remap. +r = parse(DEFAULT_ENV, altbaseurl='http://altserver') + +self.assertEqual(r.url, b'http://testserver') +self.assertEqual(r.baseurl, b'http://testserver') +self.assertEqual(r.advertisedurl, b'http://altserver') +self.assertEqual(r.advertisedbaseurl, b'http://altserver') +self.assertEqual(r.urlscheme, b'http') +self.assertEqual(r.apppath, b'') +self.assertEqual(r.dispatchparts, []) +self.assertIsNone(r.dispatchpath) +self.assertIsNone(r.reponame) + +# With a custom port. +r = parse(DEFAULT_ENV, altbaseurl='http://altserver:8000') +self.assertEqual(r.url, b'http://testserver') +self.assertEqual(r.baseurl, b'http://testserver') +self.assertEqual(r.advertisedurl, b'http://altserver:8000') +self.assertEqual(r.advertisedbaseurl, b'http://altserver:8000') +self.assertEqual(r.urlscheme, b'http') +self.assertEqual(r.apppath, b'') +self.assertEqual(r.dispatchparts, []) +self.assertIsNone(r.dispatchpath) +self.assertIsNone(r.reponame) + +# With a changed protocol. +r = parse(DEFAULT_ENV, altbaseurl='https://altserver') +self.assertEqual(r.url, b'http://testserver') +self.assertEqual(r.baseurl, b'http://testserver') +self.assertEqual(r.advertisedurl, b'https://altserver') +self.assertEqual(r.advertisedbaseurl, b'https://altserver') +# URL scheme is defined as the actual scheme, not advertised. +self.assertEqual(r.urlscheme, b'http') +self.assertEqual(r.apppath, b'') +self.assertEqual(r.dispatchparts, []) +self.assertIsNone(r.dispatchpath) +self.assertIsNone(r.reponame) + +# Need to specify explicit port number for proper https:// alt URLs. +r = parse(DEFAULT_ENV, altbaseurl='https://altserver:443') +self.assertEqual(r.url, b'http://testserver') +self.assertEqual(r.baseurl, b'http://testserver') +self.assertEqual(r.advertisedurl, b'https://altserver') +self.assertEqual(r.advertisedbaseurl, b'https://altserver') +self.assertEqual(r.urlscheme, b'http') +self.assertEqual(r.apppath, b'') +self.assertEqual(r.dispatchparts, []) +self.assertIsNone(r.dispatchpath) +self.assertIsNone(r.reponame) + +# With only PATH_INFO defined. +r = parse(DEFAULT_ENV, altbaseurl='http://altserver', extra={ +r'PATH_INFO': r'/path1/path2', +}) +self.assertEqual(r.url, b'http://testserver/path1/path2') +
D2815: hgweb: extract entries() to standalone function
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY There was some real wonkiness going on here. Essentially, the inline function was being executed with default arguments because a function reference was passed as-is into the templater. That seemed odd. So now we pass an explicit generator of the function result. Moving this code out of makeindex() makes makeindex() small enough to reason about. This makes it easier to see weird things, like the fact that we're calling self.refresh() twice. Why, I'm not sure. I'm also not sure why we need to call updatereqenv() to possibly update the SERVER_NAME, SERVER_PORT, and SCRIPT_NAME variables as part of rendering an index. I'll dig into these things in subsequent commits. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2815 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -273,6 +273,22 @@ yield row +def indexentries(ui, repos, wsgireq, req, stripecount, sortcolumn='', + descending=False, subdir='', **map): + +rows = rawindexentries(ui, repos, wsgireq, req, subdir=subdir, **map) + +sortdefault = None, False + +if sortcolumn and sortdefault != (sortcolumn, descending): +sortkey = '%s_sort' % sortcolumn +rows = sorted(rows, key=lambda x: x[sortkey], + reverse=descending) + +for row, parity in zip(rows, paritygen(stripecount)): +row['parity'] = parity +yield row + class hgwebdir(object): """HTTP server for multiple repositories. @@ -472,22 +488,9 @@ def makeindex(self, wsgireq, tmpl, subdir=""): req = wsgireq.req -sortdefault = None, False -def entries(sortcolumn="", descending=False, subdir="", **map): -rows = rawindexentries(self.ui, self.repos, wsgireq, req, - subdir=subdir, **map) - -if sortcolumn and sortdefault != (sortcolumn, descending): -sortkey = '%s_sort' % sortcolumn -rows = sorted(rows, key=lambda x: x[sortkey], - reverse=descending) -for row, parity in zip(rows, paritygen(self.stripecount)): -row['parity'] = parity -yield row - self.refresh() sortable = ["name", "description", "contact", "lastchange"] -sortcolumn, descending = sortdefault +sortcolumn, descending = None, False if 'sort' in req.qsparams: sortcolumn = req.qsparams['sort'] descending = sortcolumn.startswith('-') @@ -504,6 +507,10 @@ self.refresh() self.updatereqenv(wsgireq.env) +entries = indexentries(self.ui, self.repos, wsgireq, req, + self.stripecount, sortcolumn=sortcolumn, + descending=descending, subdir=subdir) + return tmpl("index", entries=entries, subdir=subdir, pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix), sortcolumn=sortcolumn, descending=descending, 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
D2823: hgweb: construct {url} with req.apppath
indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY This is how the hgweb WSGI application does it. Let's make the behavior consistent. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2823 AFFECTED FILES mercurial/hgweb/hgwebdir_mod.py CHANGE DETAILS diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -496,10 +496,6 @@ def config(section, name, default=uimod._unset, untrusted=True): return self.ui.config(section, name, default, untrusted) -url = wsgireq.env.get('SCRIPT_NAME', '') -if not url.endswith('/'): -url += '/' - vars = {} styles, (style, mapfile) = hgweb_mod.getstyle(wsgireq.req, config, self.templatepath) @@ -517,7 +513,7 @@ defaults = { "encoding": encoding.encoding, "motd": motd, -"url": url, +"url": wsgireq.req.apppath + '/', "logourl": logourl, "logoimg": logoimg, "staticurl": staticurl, 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
D2776: hgweb: use a multidict for holding query string parameters
durin42 added inline comments. INLINE COMMENTS > request.py:31 > > +class multidict(object): > +"""A dict like object that can store multiple values for a key. I'm a little uncomfortable with this not advertising that it's not a hash table, and htat lookups are O(N). Maybe send a docstring followup? REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2776 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
[PATCH STABLE] hgweb: garbage collect on every request
# HG changeset patch # User Gregory Szorc# Date 1520885700 25200 # Mon Mar 12 13:15:00 2018 -0700 # Branch stable # Node ID 46905416a5f47e9e46aa2db0e2e4f45e7414c979 # Parent 9639c433be54191b4136b48fe70fc8344d2b5db2 hgweb: garbage collect on every request There appears to be a cycle in localrepository or hgweb that is preventing repositories from being garbage collected when hgwebdir dispatches to hgweb. Every request creates a new repository instance and then leaks that object and other referenced objects. A periodic GC to find cycles will eventually collect the old repositories. But these don't run reliably and rapid requests to hgwebdir can result in rapidly increasing memory consumption. With the Firefox repository, repeated requests to raw-file URLs leak ~100 MB per hgwebdir request (most of this appears to be cached manifest data structures). WSGI processes quickly grow to >1 GB RSS. Breaking the cycles in localrepository is going to be a bit of work. Because we know that hgwebdir leaks localrepository instances, let's put a band aid on the problem in the form of an explicit gc.collect() on every hgwebdir request. As the inline comment states, ideally we'd do this in a finally block for the current request iff it dispatches to hgweb. But _runwsgi() returns an explicit value. We need the finally to run after generator exhaustion. So we'd need to refactor _runwsgi() to "yield" instead of "return." That's too much change for a patch to stable. So we implement this hack one function above and run it on every request. The performance impact of this change should be minimal. Any impact should be offset by benefits from not having hgwebdir processes leak memory. diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -8,6 +8,7 @@ from __future__ import absolute_import +import gc import os import re import time @@ -224,8 +225,18 @@ class hgwebdir(object): def run_wsgi(self, req): profile = self.ui.configbool('profiling', 'enabled') with profiling.profile(self.ui, enabled=profile): -for r in self._runwsgi(req): -yield r +try: +for r in self._runwsgi(req): +yield r +finally: +# There are known cycles in localrepository that prevent +# those objects (and tons of held references) from being +# collected through normal refcounting. We mitigate those +# leaks by performing an explicit GC on every request. +# TODO remove this once leaks are fixed. +# TODO only run this on requests that create localrepository +# instances instead of every request. +gc.collect() def _runwsgi(self, req): try: ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2761: rebase: use configoverride context manager for ui.forcemerge
martinvonz updated this revision to Diff 6873. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2761?vs=6789=6873 REVISION DETAIL https://phab.mercurial-scm.org/D2761 AFFECTED FILES hgext/rebase.py CHANGE DETAILS diff --git a/hgext/rebase.py b/hgext/rebase.py --- a/hgext/rebase.py +++ b/hgext/rebase.py @@ -481,9 +481,8 @@ if len(repo[None].parents()) == 2: repo.ui.debug('resuming interrupted rebase\n') else: -try: -ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), - 'rebase') +overrides = {('ui', 'forcemerge'): opts.get('tool', '')} +with ui.configoverride(overrides, 'rebase'): stats = rebasenode(repo, rev, p1, base, self.collapsef, dest, wctx=self.wctx) if stats and stats[3] > 0: @@ -493,8 +492,6 @@ raise error.InterventionRequired( _('unresolved conflicts (see hg ' 'resolve, then hg rebase --continue)')) -finally: -ui.setconfig('ui', 'forcemerge', '', 'rebase') if not self.collapsef: merging = p2 != nullrev editform = cmdutil.mergeeditform(merging, 'rebase') To: martinvonz, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2760: rebase: also restore "ui.allowemptycommit" value
martinvonz updated this revision to Diff 6872. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2760?vs=6788=6872 REVISION DETAIL https://phab.mercurial-scm.org/D2760 AFFECTED FILES hgext/rebase.py CHANGE DETAILS diff --git a/hgext/rebase.py b/hgext/rebase.py --- a/hgext/rebase.py +++ b/hgext/rebase.py @@ -1053,9 +1053,9 @@ destphase = max(ctx.phase(), phases.draft) overrides = {('phases', 'new-commit'): destphase} +if keepbranch: +overrides[('ui', 'allowemptycommit')] = True with repo.ui.configoverride(overrides, 'rebase'): -if keepbranch: -repo.ui.setconfig('ui', 'allowemptycommit', True) # Replicates the empty check in ``repo.commit``. if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'): return None @@ -1095,9 +1095,9 @@ destphase = max(ctx.phase(), phases.draft) overrides = {('phases', 'new-commit'): destphase} +if keepbranch: +overrides[('ui', 'allowemptycommit')] = True with repo.ui.configoverride(overrides, 'rebase'): -if keepbranch: -repo.ui.setconfig('ui', 'allowemptycommit', True) # Commit might fail if unresolved files exist if date is None: date = ctx.date() To: martinvonz, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2810: rebase: inline _performrebasesubset()
martinvonz created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY Now that most of _performrebasesubset() has been moved into _rebasenode(), it's simple enough that we can inline it. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2810 AFFECTED FILES hgext/rebase.py CHANGE DETAILS diff --git a/hgext/rebase.py b/hgext/rebase.py --- a/hgext/rebase.py +++ b/hgext/rebase.py @@ -425,30 +425,27 @@ total = len(cands) pos = 0 for subset in sortsource(self.destmap): -pos = self._performrebasesubset(tr, subset, pos, total) +sortedrevs = self.repo.revs('sort(%ld, -topo)', subset) +allowdivergence = self.ui.configbool( +'experimental', 'evolution.allowdivergence') +if not allowdivergence: +sortedrevs -= self.repo.revs( +'descendants(%ld) and not %ld', +self.obsoletewithoutsuccessorindestination, +self.obsoletewithoutsuccessorindestination, +) +posholder = [pos] +def progress(ctx): +posholder[0] += 1 +self.repo.ui.progress(_("rebasing"), posholder[0], + ("%d:%s" % (ctx.rev(), ctx)), + _('changesets'), total) +for rev in sortedrevs: +self._rebasenode(tr, rev, allowdivergence, progress) +pos = posholder[0] ui.progress(_('rebasing'), None) ui.note(_('rebase merging completed\n')) -def _performrebasesubset(self, tr, subset, pos, total): -sortedrevs = self.repo.revs('sort(%ld, -topo)', subset) -allowdivergence = self.ui.configbool( -'experimental', 'evolution.allowdivergence') -if not allowdivergence: -sortedrevs -= self.repo.revs( -'descendants(%ld) and not %ld', -self.obsoletewithoutsuccessorindestination, -self.obsoletewithoutsuccessorindestination, -) -posholder = [pos] -def progress(ctx): -posholder[0] += 1 -self.repo.ui.progress(_("rebasing"), posholder[0], - ("%d:%s" % (ctx.rev(), ctx)), _('changesets'), - total) -for rev in sortedrevs: -self._rebasenode(tr, rev, allowdivergence, progress) -return posholder[0] - def _rebasenode(self, tr, rev, allowdivergence, progressfn): repo, ui, opts = self.repo, self.ui, self.opts dest = self.destmap[rev] To: martinvonz, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2809: rebase: extract function for rebasing a single node
martinvonz created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY We currently have _performrebase() and _performrebasesubset(), but we don't have a method for rebasing a single node (that's inside a loop in _performrebasesubset()). I think it makes sense to have such a method, so that's what this patch does. I think it may simplify future patches I'm working on that have to do with transactions, but I think this patch makes sense on its own whether or not that future work happens. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2809 AFFECTED FILES hgext/rebase.py CHANGE DETAILS diff --git a/hgext/rebase.py b/hgext/rebase.py --- a/hgext/rebase.py +++ b/hgext/rebase.py @@ -430,115 +430,122 @@ ui.note(_('rebase merging completed\n')) def _performrebasesubset(self, tr, subset, pos, total): -repo, ui, opts = self.repo, self.ui, self.opts -sortedrevs = repo.revs('sort(%ld, -topo)', subset) +sortedrevs = self.repo.revs('sort(%ld, -topo)', subset) allowdivergence = self.ui.configbool( 'experimental', 'evolution.allowdivergence') if not allowdivergence: -sortedrevs -= repo.revs( +sortedrevs -= self.repo.revs( 'descendants(%ld) and not %ld', self.obsoletewithoutsuccessorindestination, self.obsoletewithoutsuccessorindestination, ) +posholder = [pos] +def progress(ctx): +posholder[0] += 1 +self.repo.ui.progress(_("rebasing"), posholder[0], + ("%d:%s" % (ctx.rev(), ctx)), _('changesets'), + total) for rev in sortedrevs: -dest = self.destmap[rev] -ctx = repo[rev] -desc = _ctxdesc(ctx) -if self.state[rev] == rev: -ui.status(_('already rebased %s\n') % desc) -elif (not allowdivergence - and rev in self.obsoletewithoutsuccessorindestination): -msg = _('note: not rebasing %s and its descendants as ' -'this would cause divergence\n') % desc -repo.ui.status(msg) -self.skipped.add(rev) -elif rev in self.obsoletenotrebased: -succ = self.obsoletenotrebased[rev] -if succ is None: -msg = _('note: not rebasing %s, it has no ' -'successor\n') % desc -else: -succdesc = _ctxdesc(repo[succ]) -msg = (_('note: not rebasing %s, already in ' - 'destination as %s\n') % (desc, succdesc)) -repo.ui.status(msg) -# Make clearrebased aware state[rev] is not a true successor -self.skipped.add(rev) -# Record rev as moved to its desired destination in self.state. -# This helps bookmark and working parent movement. -dest = max(adjustdest(repo, rev, self.destmap, self.state, - self.skipped)) -self.state[rev] = dest -elif self.state[rev] == revtodo: -pos += 1 -ui.status(_('rebasing %s\n') % desc) -ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)), -_('changesets'), total) -p1, p2, base = defineparents(repo, rev, self.destmap, - self.state, self.skipped, - self.obsoletenotrebased) -self.storestatus(tr=tr) -if len(repo[None].parents()) == 2: -repo.ui.debug('resuming interrupted rebase\n') +self._rebasenode(tr, rev, allowdivergence, progress) +return posholder[0] + +def _rebasenode(self, tr, rev, allowdivergence, progressfn): +repo, ui, opts = self.repo, self.ui, self.opts +dest = self.destmap[rev] +ctx = repo[rev] +desc = _ctxdesc(ctx) +if self.state[rev] == rev: +ui.status(_('already rebased %s\n') % desc) +elif (not allowdivergence + and rev in self.obsoletewithoutsuccessorindestination): +msg = _('note: not rebasing %s and its descendants as ' +'this would cause divergence\n') % desc +repo.ui.status(msg) +self.skipped.add(rev) +elif rev in self.obsoletenotrebased: +succ = self.obsoletenotrebased[rev] +if succ is None: +msg = _('note: not rebasing %s, it has no ' +'successor\n') % desc +else: +succdesc = _ctxdesc(repo[succ]) +msg = (_('note: not rebasing %s, already in ' +
D2811: rebase: move constant expressions out of inner loop in _performrebase()
martinvonz created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2811 AFFECTED FILES hgext/rebase.py CHANGE DETAILS diff --git a/hgext/rebase.py b/hgext/rebase.py --- a/hgext/rebase.py +++ b/hgext/rebase.py @@ -423,26 +423,24 @@ cands = [k for k, v in self.state.iteritems() if v == revtodo] total = len(cands) -pos = 0 +posholder = [0] +def progress(ctx): +posholder[0] += 1 +self.repo.ui.progress(_("rebasing"), posholder[0], + ("%d:%s" % (ctx.rev(), ctx)), + _('changesets'), total) +allowdivergence = self.ui.configbool( +'experimental', 'evolution.allowdivergence') for subset in sortsource(self.destmap): sortedrevs = self.repo.revs('sort(%ld, -topo)', subset) -allowdivergence = self.ui.configbool( -'experimental', 'evolution.allowdivergence') if not allowdivergence: sortedrevs -= self.repo.revs( 'descendants(%ld) and not %ld', self.obsoletewithoutsuccessorindestination, self.obsoletewithoutsuccessorindestination, ) -posholder = [pos] -def progress(ctx): -posholder[0] += 1 -self.repo.ui.progress(_("rebasing"), posholder[0], - ("%d:%s" % (ctx.rev(), ctx)), - _('changesets'), total) for rev in sortedrevs: self._rebasenode(tr, rev, allowdivergence, progress) -pos = posholder[0] ui.progress(_('rebasing'), None) ui.note(_('rebase merging completed\n')) To: martinvonz, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 3 of 3] sshpeer: drop support for not reading stderr
On Mon, Mar 12, 2018 at 7:17 AM, Yuya Nishiharawrote: > # HG changeset patch > # User Yuya Nishihara > # Date 1520858773 -32400 > # Mon Mar 12 21:46:13 2018 +0900 > # Node ID e6071590cd3e6e376ff45a11204065f08f72eee8 > # Parent d3dd691a3fce0c501a34ed68d1a08b563a78794c > sshpeer: drop support for not reading stderr > > It's handled by caller now. This patch backs out most of 1151c731686e and > 1a36ef7df70a. > I'm OK with this approach if part 2 is the path forward. I considered this feature a bit hacky TBH and would be happy to see the code deleted. > > diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py > --- a/mercurial/debugcommands.py > +++ b/mercurial/debugcommands.py > @@ -2759,19 +2759,16 @@ def debugwireproto(ui, repo, **opts): > > if opts['peer'] == 'ssh1': > ui.write(_('creating ssh peer for wire protocol version 1\n')) > -peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr, > - None, autoreadstderr=autoreadstderr) > +peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, > stderr, None) > elif opts['peer'] == 'ssh2': > ui.write(_('creating ssh peer for wire protocol version 2\n')) > -peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr, > - None, autoreadstderr=autoreadstderr) > +peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, > stderr, None) > elif opts['peer'] == 'raw': > ui.write(_('using raw connection to peer\n')) > peer = None > else: > ui.write(_('creating ssh peer from handshake results\n')) > -peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr, > -autoreadstderr=autoreadstderr) > +peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr) > > else: > raise error.Abort(_('only --localssh is currently supported')) > diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py > --- a/mercurial/sshpeer.py > +++ b/mercurial/sshpeer.py > @@ -339,16 +339,13 @@ def _performhandshake(ui, stdin, stdout, > return protoname, caps > > class sshv1peer(wireproto.wirepeer): > -def __init__(self, ui, url, proc, stdin, stdout, stderr, caps, > - autoreadstderr=True): > +def __init__(self, ui, url, proc, stdin, stdout, stderr, caps): > """Create a peer from an existing SSH connection. > > ``proc`` is a handle on the underlying SSH process. > ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio > -pipes for that process. > +pipes for that process. ``stderr`` may be None. > ``caps`` is a set of capabilities supported by the remote. > -``autoreadstderr`` denotes whether to automatically read from > -stderr and to forward its output. > """ > self._url = url > self._ui = ui > @@ -358,7 +355,7 @@ class sshv1peer(wireproto.wirepeer): > > # And we hook up our "doublepipe" wrapper to allow querying > # stderr any time we perform I/O. > -if autoreadstderr: > +if stderr: > stdout = doublepipe(ui, util.bufferedinputpipe(stdout), > stderr) > stdin = doublepipe(ui, stdin, stderr) > > @@ -366,7 +363,6 @@ class sshv1peer(wireproto.wirepeer): > self._pipei = stdout > self._pipee = stderr > self._caps = caps > -self._autoreadstderr = autoreadstderr > > # Commands that have a "framed" response where the first line of the > # response contains the length of that response. > @@ -512,12 +508,10 @@ class sshv1peer(wireproto.wirepeer): > def _getamount(self): > l = self._pipei.readline() > if l == '\n': > -if self._autoreadstderr: > -self._readerr() > +self._readerr() > msg = _('check previous remote output') > self._abort(error.OutOfBandError(hint=msg)) > -if self._autoreadstderr: > -self._readerr() > +self._readerr() > try: > return int(l) > except ValueError: > @@ -536,8 +530,7 @@ class sshv1peer(wireproto.wirepeer): > self._pipeo.write(data) > if flush: > self._pipeo.flush() > -if self._autoreadstderr: > -self._readerr() > +self._readerr() > > class sshv2peer(sshv1peer): > """A peer that speakers version 2 of the transport protocol.""" > @@ -545,7 +538,7 @@ class sshv2peer(sshv1peer): > # And handshake is performed before the peer is instantiated. So > # we need no custom code. > > -def makepeer(ui, path, proc, stdin, stdout, stderr, autoreadstderr=True): > +def makepeer(ui, path, proc, stdin, stdout, stderr): > """Make a peer instance from existing pipes. > > ``path`` and
Re: [PATCH 2 of 3] debugwireproto: dump server's stderr to temporary file if --noreadstderr
On Mon, Mar 12, 2018 at 7:17 AM, Yuya Nishiharawrote: > # HG changeset patch > # User Yuya Nishihara > # Date 1520858510 -32400 > # Mon Mar 12 21:41:50 2018 +0900 > # Node ID d3dd691a3fce0c501a34ed68d1a08b563a78794c > # Parent 2e2a5376d006ad77ba9a07d341f6bc5418289af1 > debugwireproto: dump server's stderr to temporary file if --noreadstderr > > Otherwise the server process could stall because of writing too many data > to stderr. > > No idea if this is strictly better than the original implementation, but > this allows dropping readstderr=False support from sshpeer. > I like what this is doing by redirecting stderr to a file so the pipe doesn't clog. I'm less keen about losing the ordering of what is read from stderr when. None of the current tests rely on it, but we will presumably want to have tests for exchanges performing multiple commands where there could be multiple writes to stderr. So, we'd want a deterministic way to get all data on stderr so we can log it. With this change, we'd need to read from a temp file. That seems complicated. That being said, we don't actually rely on this behavior yet. So this change feels strictly better. Let me think about it for a bit before queuing. > > diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py > --- a/mercurial/debugcommands.py > +++ b/mercurial/debugcommands.py > @@ -2716,7 +2716,9 @@ def debugwireproto(ui, repo, **opts): > > blocks = list(_parsewirelangblocks(ui.fin)) > > +logging = (ui.verbose or opts['peer'] == 'raw') > proc = None > +stderrfname = None > > if opts['localssh']: > # We start the SSH server in its own process so there is process > @@ -2726,27 +2728,34 @@ def debugwireproto(ui, repo, **opts): > '-R', repo.root, > 'debugserve', '--sshstdio', > ] > +autoreadstderr = not opts['noreadstderr'] > +stderrfd = subprocess.PIPE > +if not autoreadstderr: > +# Dump stderr to file so the server process never stalls > +stderrfd, stderrfname = tempfile.mkstemp(prefix='hg- > debugserve-') > proc = subprocess.Popen(args, stdin=subprocess.PIPE, > -stdout=subprocess.PIPE, > stderr=subprocess.PIPE, > +stdout=subprocess.PIPE, stderr=stderrfd, > bufsize=0) > +if stderrfd != subprocess.PIPE: > +os.close(stderrfd) > > stdin = proc.stdin > stdout = proc.stdout > -stderr = proc.stderr > +stderr = proc.stderr # may be None > > # We turn the pipes into observers so we can log I/O. > -if ui.verbose or opts['peer'] == 'raw': > +if logging: > stdin = util.makeloggingfileobject(ui, proc.stdin, b'i', > logdata=True) > stdout = util.makeloggingfileobject(ui, proc.stdout, b'o', > logdata=True) > +if logging and stderr: > stderr = util.makeloggingfileobject(ui, proc.stderr, b'e', > logdata=True) > > # --localssh also implies the peer connection settings. > > url = 'ssh://localserver' > -autoreadstderr = not opts['noreadstderr'] > > if opts['peer'] == 'ssh1': > ui.write(_('creating ssh peer for wire protocol version 1\n')) > @@ -2838,7 +2847,8 @@ def debugwireproto(ui, repo, **opts): > elif action == 'readavailable': > stdin.close() > stdout.read() > -stderr.read() > +if stderr: > +stderr.read() > elif action == 'readline': > stdout.readline() > else: > @@ -2852,3 +2862,10 @@ def debugwireproto(ui, repo, **opts): > > if proc: > proc.kill() > + > +if stderrfname: > +with open(stderrfname, 'rb') as f: > +if logging: > +f = util.makeloggingfileobject(ui, f, b'e', logdata=True) > +f.read() > +os.unlink(stderrfname) > ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 1 of 3] debugwireproto: close the write end before consuming all available data
On Mon, Mar 12, 2018 at 7:17 AM, Yuya Nishiharawrote: > # HG changeset patch > # User Yuya Nishihara > # Date 1520862453 -32400 > # Mon Mar 12 22:47:33 2018 +0900 > # Node ID 2e2a5376d006ad77ba9a07d341f6bc5418289af1 > # Parent ff541b8cdee0cf9b75874639388bdc8b9854ac20 > debugwireproto: close the write end before consuming all available data > I queued this one. > > And make it read all available data deterministically. Otherwise > util.poll() > may deadlock because both stdout and stderr could have no data. > > Spotted by the next patch which removes stderr from the fds. > > diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py > --- a/mercurial/debugcommands.py > +++ b/mercurial/debugcommands.py > @@ -2690,7 +2690,8 @@ def debugwireproto(ui, repo, **opts): > readavailable > - > > -Read all available data from the server. > +Close the write end of the connection and read all available data from > +the server. > > If the connection to the server encompasses multiple pipes, we poll > both > pipes and read available data. > @@ -2835,17 +2836,9 @@ def debugwireproto(ui, repo, **opts): > elif action == 'close': > peer.close() > elif action == 'readavailable': > -fds = [stdout.fileno(), stderr.fileno()] > -try: > -act = util.poll(fds) > -except NotImplementedError: > -# non supported yet case, assume all have data. > -act = fds > - > -if stdout.fileno() in act: > -util.readpipe(stdout) > -if stderr.fileno() in act: > -util.readpipe(stderr) > +stdin.close() > +stdout.read() > +stderr.read() > elif action == 'readline': > stdout.readline() > else: > diff --git a/tests/test-ssh-proto-unbundle.t b/tests/test-ssh-proto- > unbundle.t > --- a/tests/test-ssh-proto-unbundle.t > +++ b/tests/test-ssh-proto-unbundle.t > @@ -93,6 +93,7 @@ Test pushing bundle1 payload to a server >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 115: >e> abort: incompatible Mercurial client; bundle2 required\n >e> (see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n > @@ -143,6 +144,7 @@ Test pushing bundle1 payload to a server >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 115: >e> abort: incompatible Mercurial client; bundle2 required\n >e> (see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n > @@ -260,6 +262,7 @@ ui.write() in hook is redirected to stde >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 196: >e> adding changesets\n >e> adding manifests\n > @@ -316,6 +319,7 @@ ui.write() in hook is redirected to stde >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 196: >e> adding changesets\n >e> adding manifests\n > @@ -386,6 +390,7 @@ And a variation that writes multiple lin >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 218: >e> adding changesets\n >e> adding manifests\n > @@ -443,6 +448,7 @@ And a variation that writes multiple lin >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 218: >e> adding changesets\n >e> adding manifests\n > @@ -514,6 +520,7 @@ And a variation that does a ui.flush() a >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 202: >e> adding changesets\n >e> adding manifests\n > @@ -570,6 +577,7 @@ And a variation that does a ui.flush() a >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 202: >e> adding changesets\n >e> adding manifests\n > @@ -640,6 +648,7 @@ Multiple writes + flush >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 206: >e> adding changesets\n >e> adding manifests\n > @@ -697,6 +706,7 @@ Multiple writes + flush >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 206: >e> adding changesets\n >e> adding manifests\n > @@ -768,6 +778,7 @@ ui.write() + ui.write_err() output is ca >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 232: >e> adding changesets\n >e> adding manifests\n > @@ -827,6 +838,7 @@ ui.write() + ui.write_err() output is ca >o> read(1) -> 1: 0 >result: 0 >remote output: > + o> read(-1) -> 0: >e> read(-1) -> 232: >e> adding changesets\n >e> adding manifests\n > @@
[Bug 5816] New: mq doesn't correctly block hg commit --amend on mq-managed commits
https://bz.mercurial-scm.org/show_bug.cgi?id=5816 Bug ID: 5816 Summary: mq doesn't correctly block hg commit --amend on mq-managed commits Product: Mercurial Version: unspecified Hardware: PC OS: Linux Status: UNCONFIRMED Severity: bug Priority: wish Component: mq Assignee: bugzi...@mercurial-scm.org Reporter: fah...@faheem.info CC: mercurial-devel@mercurial-scm.org As mentioned in https://stackoverflow.com/q/49219477/350713 and a couple of Mercurial mailing lists, the following script breaks MQ. #!/bin/sh hg init test cd test echo "This is foo" >> foo hg add hg ci -m "Add foo" hg init --mq echo "Line 2 of foo" >> foo hg qnew p hg ci --mq -m "Add patch p" hg ref Giving the following log hg log -vG --hidden @ changeset: 2:f7f038d3aab5 | tag: tip | parent: 0:9d3a95922194 | user:Faheem Mitha| date:Sun Mar 11 16:38:51 2018 +0530 | files: foo | description: | [mq]: p | | | x changeset: 1:e467a2433c7f |/ tag: p |tag: qbase |tag: qtip |user:Faheem Mitha |date:Sun Mar 11 16:38:50 2018 +0530 |obsolete:rewritten using amend as 2:f7f038d3aab5 by Faheem Mitha (at 2018-03-11 16:38 +0530) |obsolete:rewritten by Faheem Mitha as f7f038d3aab5 (at 2018-03-11 16:38 +0530) |files: foo |description: |[mq]: p | | o changeset: 0:9d3a95922194 tag: qparent user:Faheem Mitha date:Sun Mar 11 16:38:50 2018 +0530 files: foo description: Add foo Additionally, Augie Fackler provided the following reproduction recipe - see https://bitbucket.org/snippets/durin42/Le4Mrn This recipe does not use evolve. [augie-macbookpro2:~] augie% hg init mqbug [default ef14] [augie-macbookpro2:~] augie% cd mqbug[default ef14] [augie-macbookpro2:~/mqbug] augie% make-greek-tree.sh[default ] ### Making a Greek Tree for import... ### Done. [augie-macbookpro2:~/mqbug] augie% hg add[default ] adding A/B/E/alpha adding A/B/E/beta adding A/B/lambda adding A/D/G/pi adding A/D/G/rho adding A/D/G/tau adding A/D/H/chi adding A/D/H/omega adding A/D/H/psi adding A/D/gamma adding A/mu adding iota [augie-macbookpro2:~/mqbug] augie% hg ci -m 'greek tree' [default ] [augie-macbookpro2:~/mqbug] augie% echo foo >> iota [default 934e] [augie-macbookpro2:~/mqbug] augie% hg qnew --force iota [default 934e] [augie-macbookpro2:~/mqbug] augie% echo foo >> iota [default 7b80] [augie-macbookpro2:~/mqbug] augie% hg ci --amend [default 7b80] [augie-macbookpro2:~/mqbug] augie% hg qser [default 0930] iota [augie-macbookpro2:~/mqbug] augie% hg log[default 0930] changeset: 2:09300d122196 tag: tip parent: 0:934ee418e81d user:Augie Fackler date:Mon Mar 12 11:17:20 2018 -0400 summary: [mq]: iota changeset: 0:934ee418e81d tag: qparent user:Augie Fackler date:Mon Mar 12 11:17:12 2018 -0400 summary: greek tree where make-greek-tree.sh is at https://bitbucket.org/durin42/dotfiles/src/tip/unixSoft/bin/make-greek-tree.sh -- 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
D2744: hgweb: handle CONTENT_LENGTH
mharbison72 added inline comments. INLINE COMMENTS > request.py:205 > +# sent. But for all intents and purposes it should be OK to lie about > +# this, since a consumer will either either value to determine how many > +# bytes are available to read. typo: "... will either either value..." REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2744 To: indygreg, #hg-reviewers, durin42 Cc: mharbison72, durin42, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[PATCH 1 of 3] debugwireproto: close the write end before consuming all available data
# HG changeset patch # User Yuya Nishihara# Date 1520862453 -32400 # Mon Mar 12 22:47:33 2018 +0900 # Node ID 2e2a5376d006ad77ba9a07d341f6bc5418289af1 # Parent ff541b8cdee0cf9b75874639388bdc8b9854ac20 debugwireproto: close the write end before consuming all available data And make it read all available data deterministically. Otherwise util.poll() may deadlock because both stdout and stderr could have no data. Spotted by the next patch which removes stderr from the fds. diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -2690,7 +2690,8 @@ def debugwireproto(ui, repo, **opts): readavailable - -Read all available data from the server. +Close the write end of the connection and read all available data from +the server. If the connection to the server encompasses multiple pipes, we poll both pipes and read available data. @@ -2835,17 +2836,9 @@ def debugwireproto(ui, repo, **opts): elif action == 'close': peer.close() elif action == 'readavailable': -fds = [stdout.fileno(), stderr.fileno()] -try: -act = util.poll(fds) -except NotImplementedError: -# non supported yet case, assume all have data. -act = fds - -if stdout.fileno() in act: -util.readpipe(stdout) -if stderr.fileno() in act: -util.readpipe(stderr) +stdin.close() +stdout.read() +stderr.read() elif action == 'readline': stdout.readline() else: diff --git a/tests/test-ssh-proto-unbundle.t b/tests/test-ssh-proto-unbundle.t --- a/tests/test-ssh-proto-unbundle.t +++ b/tests/test-ssh-proto-unbundle.t @@ -93,6 +93,7 @@ Test pushing bundle1 payload to a server o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 115: e> abort: incompatible Mercurial client; bundle2 required\n e> (see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n @@ -143,6 +144,7 @@ Test pushing bundle1 payload to a server o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 115: e> abort: incompatible Mercurial client; bundle2 required\n e> (see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n @@ -260,6 +262,7 @@ ui.write() in hook is redirected to stde o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 196: e> adding changesets\n e> adding manifests\n @@ -316,6 +319,7 @@ ui.write() in hook is redirected to stde o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 196: e> adding changesets\n e> adding manifests\n @@ -386,6 +390,7 @@ And a variation that writes multiple lin o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 218: e> adding changesets\n e> adding manifests\n @@ -443,6 +448,7 @@ And a variation that writes multiple lin o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 218: e> adding changesets\n e> adding manifests\n @@ -514,6 +520,7 @@ And a variation that does a ui.flush() a o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 202: e> adding changesets\n e> adding manifests\n @@ -570,6 +577,7 @@ And a variation that does a ui.flush() a o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 202: e> adding changesets\n e> adding manifests\n @@ -640,6 +648,7 @@ Multiple writes + flush o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 206: e> adding changesets\n e> adding manifests\n @@ -697,6 +706,7 @@ Multiple writes + flush o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 206: e> adding changesets\n e> adding manifests\n @@ -768,6 +778,7 @@ ui.write() + ui.write_err() output is ca o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 232: e> adding changesets\n e> adding manifests\n @@ -827,6 +838,7 @@ ui.write() + ui.write_err() output is ca o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 232: e> adding changesets\n e> adding manifests\n @@ -900,6 +912,7 @@ print() output is captured o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 193: e> adding changesets\n e> adding manifests\n @@ -956,6 +969,7 @@ print() output is captured o> read(1) -> 1: 0 result: 0 remote output: + o> read(-1) -> 0: e> read(-1) -> 193: e> adding
[PATCH 3 of 3] sshpeer: drop support for not reading stderr
# HG changeset patch # User Yuya Nishihara# Date 1520858773 -32400 # Mon Mar 12 21:46:13 2018 +0900 # Node ID e6071590cd3e6e376ff45a11204065f08f72eee8 # Parent d3dd691a3fce0c501a34ed68d1a08b563a78794c sshpeer: drop support for not reading stderr It's handled by caller now. This patch backs out most of 1151c731686e and 1a36ef7df70a. diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -2759,19 +2759,16 @@ def debugwireproto(ui, repo, **opts): if opts['peer'] == 'ssh1': ui.write(_('creating ssh peer for wire protocol version 1\n')) -peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr, - None, autoreadstderr=autoreadstderr) +peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr, None) elif opts['peer'] == 'ssh2': ui.write(_('creating ssh peer for wire protocol version 2\n')) -peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr, - None, autoreadstderr=autoreadstderr) +peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr, None) elif opts['peer'] == 'raw': ui.write(_('using raw connection to peer\n')) peer = None else: ui.write(_('creating ssh peer from handshake results\n')) -peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr, -autoreadstderr=autoreadstderr) +peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr) else: raise error.Abort(_('only --localssh is currently supported')) diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py --- a/mercurial/sshpeer.py +++ b/mercurial/sshpeer.py @@ -339,16 +339,13 @@ def _performhandshake(ui, stdin, stdout, return protoname, caps class sshv1peer(wireproto.wirepeer): -def __init__(self, ui, url, proc, stdin, stdout, stderr, caps, - autoreadstderr=True): +def __init__(self, ui, url, proc, stdin, stdout, stderr, caps): """Create a peer from an existing SSH connection. ``proc`` is a handle on the underlying SSH process. ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio -pipes for that process. +pipes for that process. ``stderr`` may be None. ``caps`` is a set of capabilities supported by the remote. -``autoreadstderr`` denotes whether to automatically read from -stderr and to forward its output. """ self._url = url self._ui = ui @@ -358,7 +355,7 @@ class sshv1peer(wireproto.wirepeer): # And we hook up our "doublepipe" wrapper to allow querying # stderr any time we perform I/O. -if autoreadstderr: +if stderr: stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr) stdin = doublepipe(ui, stdin, stderr) @@ -366,7 +363,6 @@ class sshv1peer(wireproto.wirepeer): self._pipei = stdout self._pipee = stderr self._caps = caps -self._autoreadstderr = autoreadstderr # Commands that have a "framed" response where the first line of the # response contains the length of that response. @@ -512,12 +508,10 @@ class sshv1peer(wireproto.wirepeer): def _getamount(self): l = self._pipei.readline() if l == '\n': -if self._autoreadstderr: -self._readerr() +self._readerr() msg = _('check previous remote output') self._abort(error.OutOfBandError(hint=msg)) -if self._autoreadstderr: -self._readerr() +self._readerr() try: return int(l) except ValueError: @@ -536,8 +530,7 @@ class sshv1peer(wireproto.wirepeer): self._pipeo.write(data) if flush: self._pipeo.flush() -if self._autoreadstderr: -self._readerr() +self._readerr() class sshv2peer(sshv1peer): """A peer that speakers version 2 of the transport protocol.""" @@ -545,7 +538,7 @@ class sshv2peer(sshv1peer): # And handshake is performed before the peer is instantiated. So # we need no custom code. -def makepeer(ui, path, proc, stdin, stdout, stderr, autoreadstderr=True): +def makepeer(ui, path, proc, stdin, stdout, stderr): """Make a peer instance from existing pipes. ``path`` and ``proc`` are stored on the eventual peer instance and may @@ -566,11 +559,9 @@ def makepeer(ui, path, proc, stdin, stdo raise if protoname == wireprototypes.SSHV1: -return sshv1peer(ui, path, proc, stdin, stdout, stderr, caps, - autoreadstderr=autoreadstderr) +return sshv1peer(ui, path, proc, stdin, stdout, stderr, caps) elif protoname == wireprototypes.SSHV2:
[PATCH 2 of 3] debugwireproto: dump server's stderr to temporary file if --noreadstderr
# HG changeset patch # User Yuya Nishihara# Date 1520858510 -32400 # Mon Mar 12 21:41:50 2018 +0900 # Node ID d3dd691a3fce0c501a34ed68d1a08b563a78794c # Parent 2e2a5376d006ad77ba9a07d341f6bc5418289af1 debugwireproto: dump server's stderr to temporary file if --noreadstderr Otherwise the server process could stall because of writing too many data to stderr. No idea if this is strictly better than the original implementation, but this allows dropping readstderr=False support from sshpeer. diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -2716,7 +2716,9 @@ def debugwireproto(ui, repo, **opts): blocks = list(_parsewirelangblocks(ui.fin)) +logging = (ui.verbose or opts['peer'] == 'raw') proc = None +stderrfname = None if opts['localssh']: # We start the SSH server in its own process so there is process @@ -2726,27 +2728,34 @@ def debugwireproto(ui, repo, **opts): '-R', repo.root, 'debugserve', '--sshstdio', ] +autoreadstderr = not opts['noreadstderr'] +stderrfd = subprocess.PIPE +if not autoreadstderr: +# Dump stderr to file so the server process never stalls +stderrfd, stderrfname = tempfile.mkstemp(prefix='hg-debugserve-') proc = subprocess.Popen(args, stdin=subprocess.PIPE, -stdout=subprocess.PIPE, stderr=subprocess.PIPE, +stdout=subprocess.PIPE, stderr=stderrfd, bufsize=0) +if stderrfd != subprocess.PIPE: +os.close(stderrfd) stdin = proc.stdin stdout = proc.stdout -stderr = proc.stderr +stderr = proc.stderr # may be None # We turn the pipes into observers so we can log I/O. -if ui.verbose or opts['peer'] == 'raw': +if logging: stdin = util.makeloggingfileobject(ui, proc.stdin, b'i', logdata=True) stdout = util.makeloggingfileobject(ui, proc.stdout, b'o', logdata=True) +if logging and stderr: stderr = util.makeloggingfileobject(ui, proc.stderr, b'e', logdata=True) # --localssh also implies the peer connection settings. url = 'ssh://localserver' -autoreadstderr = not opts['noreadstderr'] if opts['peer'] == 'ssh1': ui.write(_('creating ssh peer for wire protocol version 1\n')) @@ -2838,7 +2847,8 @@ def debugwireproto(ui, repo, **opts): elif action == 'readavailable': stdin.close() stdout.read() -stderr.read() +if stderr: +stderr.read() elif action == 'readline': stdout.readline() else: @@ -2852,3 +2862,10 @@ def debugwireproto(ui, repo, **opts): if proc: proc.kill() + +if stderrfname: +with open(stderrfname, 'rb') as f: +if logging: +f = util.makeloggingfileobject(ui, f, b'e', logdata=True) +f.read() +os.unlink(stderrfname) ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2808: remotenames: show remote bookmarks in `hg bookmarks`
pulkit created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY This patch adds functionality to show list of remote bookmarks in `hg bookmarks` command. There is some indenting problem in the test output as the current bookmark printing code in core can handle bookmark names of size 25 only gracefully. The idea is taken from hgremotenames extension which has --remote and --all flags to show remote bookmarks. However, this patch by defaults support showing list of remote bookmarks if remotenames extension is enabled and remotebookmarks are turned on. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2808 AFFECTED FILES hgext/remotenames.py tests/test-logexchange.t CHANGE DETAILS diff --git a/tests/test-logexchange.t b/tests/test-logexchange.t --- a/tests/test-logexchange.t +++ b/tests/test-logexchange.t @@ -327,3 +327,9 @@ date:Thu Jan 01 00:00:00 1970 + summary: added bar + $ hg bookmarks + $TESTTMP/server2/bar 6:87d6d6676308 + $TESTTMP/server2/foo 3:62615734edd5 + default/bar 6:87d6d6676308 + default/foo 3:62615734edd5 + * foo 8:3e1487808078 diff --git a/hgext/remotenames.py b/hgext/remotenames.py --- a/hgext/remotenames.py +++ b/hgext/remotenames.py @@ -34,6 +34,8 @@ bin, ) from mercurial import ( +bookmarks, +extensions, logexchange, namespaces, registrar, @@ -222,6 +224,24 @@ self._nodetohoists.setdefault(node[0], []).append(name) return self._nodetohoists +def wrapprintbookmarks(orig, ui, repo, bmarks, **opts): +if 'remotebookmarks' not in repo.names: +return +ns = repo.names['remotebookmarks'] + +for name in ns.listnames(repo): +nodes = ns.nodes(repo, name) +if not nodes: +continue +node = nodes[0] + +bmarks[name] = (node, ' ', '') + +return orig(ui, repo, bmarks, **opts) + +def extsetup(ui): +extensions.wrapfunction(bookmarks, '_printbookmarks', wrapprintbookmarks) + def reposetup(ui, repo): if not repo.local(): return To: pulkit, #hg-reviewers Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D2395: stack: add a new module for stack-related commands
lothiraldan added a comment. I don't remember a particular point that needed to be cleaned before merging this series. I think the next steps are to introduce a Stack object, update core commands to works with stacks and introduce new commands related to stacks according to my memories and sprint notes. Do you have specific in mind that needs to be cleaned that I can't remember? REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D2395 To: lothiraldan, #hg-reviewers, indygreg Cc: indygreg, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel