D2768: hgweb: use a capped reader for WSGI input stream

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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)

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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()

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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()

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread indygreg (Gregory Szorc)
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

2018-03-12 Thread durin42 (Augie Fackler)
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

2018-03-12 Thread Gregory Szorc
# 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

2018-03-12 Thread martinvonz (Martin von Zweigbergk)
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

2018-03-12 Thread martinvonz (Martin von Zweigbergk)
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()

2018-03-12 Thread martinvonz (Martin von Zweigbergk)
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

2018-03-12 Thread martinvonz (Martin von Zweigbergk)
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()

2018-03-12 Thread martinvonz (Martin von Zweigbergk)
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

2018-03-12 Thread Gregory Szorc
On Mon, Mar 12, 2018 at 7:17 AM, Yuya Nishihara  wrote:

> # 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

2018-03-12 Thread Gregory Szorc
On Mon, Mar 12, 2018 at 7:17 AM, Yuya Nishihara  wrote:

> # 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

2018-03-12 Thread Gregory Szorc
On Mon, Mar 12, 2018 at 7:17 AM, Yuya Nishihara  wrote:

> # 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

2018-03-12 Thread mercurial-bugs
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

2018-03-12 Thread mharbison72 (Matt Harbison)
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

2018-03-12 Thread Yuya Nishihara
# 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

2018-03-12 Thread Yuya Nishihara
# 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

2018-03-12 Thread Yuya Nishihara
# 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`

2018-03-12 Thread pulkit (Pulkit Goyal)
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

2018-03-12 Thread lothiraldan (Boris Feld)
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


<    1   2